1 | ########################################################################### |
---|
2 | # A module to create API dump from disassembled code |
---|
3 | # |
---|
4 | # Copyright (C) 2016-2017 Andrey Ponomarenko's ABI Laboratory |
---|
5 | # |
---|
6 | # Written by Andrey Ponomarenko |
---|
7 | # |
---|
8 | # This program is free software: you can redistribute it and/or modify |
---|
9 | # it under the terms of the GNU General Public License or the GNU Lesser |
---|
10 | # General Public License as published by the Free Software Foundation. |
---|
11 | # |
---|
12 | # This program is distributed in the hope that it will be useful, |
---|
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
15 | # GNU General Public License for more details. |
---|
16 | # |
---|
17 | # You should have received a copy of the GNU General Public License |
---|
18 | # and the GNU Lesser General Public License along with this program. |
---|
19 | # If not, see <http://www.gnu.org/licenses/>. |
---|
20 | ########################################################################### |
---|
21 | use strict; |
---|
22 | use IPC::Open3; |
---|
23 | |
---|
24 | my $ExtractCounter = 0; |
---|
25 | |
---|
26 | my %MName_Mid; |
---|
27 | my %Mid_MName; |
---|
28 | |
---|
29 | my $T_ID = 0; |
---|
30 | my $M_ID = 0; |
---|
31 | my $U_ID = 0; |
---|
32 | |
---|
33 | # Aliases |
---|
34 | my (%MethodInfo, %TypeInfo, %TName_Tid) = (); |
---|
35 | |
---|
36 | foreach (1, 2) |
---|
37 | { |
---|
38 | $MethodInfo{$_} = $In::API{$_}{"MethodInfo"}; |
---|
39 | $TypeInfo{$_} = $In::API{$_}{"TypeInfo"}; |
---|
40 | $TName_Tid{$_} = $In::API{$_}{"TName_Tid"}; |
---|
41 | } |
---|
42 | |
---|
43 | sub createAPIDump($) |
---|
44 | { |
---|
45 | my $LVer = $_[0]; |
---|
46 | |
---|
47 | readArchives($LVer); |
---|
48 | |
---|
49 | if(not keys(%{$MethodInfo{$LVer}})) { |
---|
50 | printMsg("WARNING", "empty dump"); |
---|
51 | } |
---|
52 | |
---|
53 | $In::API{$LVer}{"LibraryVersion"} = $In::Desc{$LVer}{"Version"}; |
---|
54 | $In::API{$LVer}{"LibraryName"} = $In::Opt{"TargetLib"}; |
---|
55 | $In::API{$LVer}{"Language"} = "Java"; |
---|
56 | } |
---|
57 | |
---|
58 | sub readArchives($) |
---|
59 | { |
---|
60 | my $LVer = $_[0]; |
---|
61 | my @ArchivePaths = getArchives($LVer); |
---|
62 | if($#ArchivePaths==-1) { |
---|
63 | exitStatus("Error", "Java archives are not found in ".$In::Desc{$LVer}{"Version"}); |
---|
64 | } |
---|
65 | printMsg("INFO", "Reading classes ".$In::Desc{$LVer}{"Version"}." ..."); |
---|
66 | |
---|
67 | $T_ID = 0; |
---|
68 | $M_ID = 0; |
---|
69 | $U_ID = 0; |
---|
70 | |
---|
71 | %MName_Mid = (); |
---|
72 | %Mid_MName = (); |
---|
73 | |
---|
74 | foreach my $ArchivePath (sort {length($a)<=>length($b)} @ArchivePaths) { |
---|
75 | readArchive($LVer, $ArchivePath); |
---|
76 | } |
---|
77 | foreach my $TName (keys(%{$TName_Tid{$LVer}})) |
---|
78 | { |
---|
79 | my $Tid = $TName_Tid{$LVer}{$TName}; |
---|
80 | if(not $TypeInfo{$LVer}{$Tid}{"Type"}) |
---|
81 | { |
---|
82 | if($TName=~/\A(void|boolean|char|byte|short|int|float|long|double)\Z/) { |
---|
83 | $TypeInfo{$LVer}{$Tid}{"Type"} = "primitive"; |
---|
84 | } |
---|
85 | else { |
---|
86 | $TypeInfo{$LVer}{$Tid}{"Type"} = "class"; |
---|
87 | } |
---|
88 | } |
---|
89 | } |
---|
90 | } |
---|
91 | |
---|
92 | sub getArchives($) |
---|
93 | { |
---|
94 | my $LVer = $_[0]; |
---|
95 | my @Paths = (); |
---|
96 | foreach my $Path (keys(%{$In::Desc{$LVer}{"Archives"}})) |
---|
97 | { |
---|
98 | if(not -e $Path) { |
---|
99 | exitStatus("Access_Error", "can't access \'$Path\'"); |
---|
100 | } |
---|
101 | |
---|
102 | foreach (getArchivePaths($Path, $LVer)) { |
---|
103 | push(@Paths, $_); |
---|
104 | } |
---|
105 | } |
---|
106 | return @Paths; |
---|
107 | } |
---|
108 | |
---|
109 | sub readArchive($$) |
---|
110 | { # 1, 2 - library, 0 - client |
---|
111 | my ($LVer, $Path) = @_; |
---|
112 | |
---|
113 | $Path = getAbsPath($Path); |
---|
114 | my $JarCmd = getCmdPath("jar"); |
---|
115 | if(not $JarCmd) { |
---|
116 | exitStatus("Not_Found", "can't find \"jar\" command"); |
---|
117 | } |
---|
118 | my $ExtractPath = join_P($In::Opt{"Tmp"}, $ExtractCounter); |
---|
119 | if(-d $ExtractPath) { |
---|
120 | rmtree($ExtractPath); |
---|
121 | } |
---|
122 | mkpath($ExtractPath); |
---|
123 | chdir($ExtractPath); |
---|
124 | system($JarCmd." -xf \"$Path\""); |
---|
125 | if($?) { |
---|
126 | exitStatus("Error", "can't extract \'$Path\'"); |
---|
127 | } |
---|
128 | chdir($In::Opt{"OrigDir"}); |
---|
129 | my @Classes = (); |
---|
130 | foreach my $ClassPath (cmdFind($ExtractPath, "", "*\\.class")) |
---|
131 | { |
---|
132 | if($In::Opt{"OS"} ne "windows") { |
---|
133 | $ClassPath=~s/\.class\Z//g; |
---|
134 | } |
---|
135 | |
---|
136 | my $ClassName = getFilename($ClassPath); |
---|
137 | if($ClassName=~/\$\d/ or $ClassName eq "module-info") { |
---|
138 | next; |
---|
139 | } |
---|
140 | |
---|
141 | $ClassPath = cutPrefix($ClassPath, $ExtractPath); # javap decompiler accepts relative paths only |
---|
142 | |
---|
143 | my $ClassDir = getDirname($ClassPath); |
---|
144 | if($ClassDir=~/\./) |
---|
145 | { # jaxb-osgi.jar/1.0/org/apache |
---|
146 | next; |
---|
147 | } |
---|
148 | |
---|
149 | my $Package = getPFormat($ClassDir); |
---|
150 | if($LVer) |
---|
151 | { |
---|
152 | if(skipPackage($Package, $LVer)) |
---|
153 | { # internal packages |
---|
154 | next; |
---|
155 | } |
---|
156 | } |
---|
157 | |
---|
158 | push(@Classes, $ClassPath); |
---|
159 | } |
---|
160 | |
---|
161 | if($#Classes!=-1) |
---|
162 | { |
---|
163 | foreach my $PartRef (divideArray(\@Classes)) |
---|
164 | { |
---|
165 | if($LVer) { |
---|
166 | readClasses($PartRef, $LVer, getFilename($Path)); |
---|
167 | } |
---|
168 | else { |
---|
169 | readClasses_Usage($PartRef); |
---|
170 | } |
---|
171 | } |
---|
172 | } |
---|
173 | |
---|
174 | $ExtractCounter += 1; |
---|
175 | |
---|
176 | if($LVer) |
---|
177 | { |
---|
178 | foreach my $SubArchive (cmdFind($ExtractPath, "", "*\\.jar")) |
---|
179 | { # recursive step |
---|
180 | readArchive($LVer, $SubArchive); |
---|
181 | } |
---|
182 | } |
---|
183 | |
---|
184 | rmtree($ExtractPath); |
---|
185 | } |
---|
186 | |
---|
187 | sub sepParams($$$) |
---|
188 | { |
---|
189 | my ($Params, $Comma, $Sp) = @_; |
---|
190 | my @Parts = (); |
---|
191 | my %B = ( "("=>0, "<"=>0, ")"=>0, ">"=>0 ); |
---|
192 | my $Part = 0; |
---|
193 | foreach my $Pos (0 .. length($Params) - 1) |
---|
194 | { |
---|
195 | my $S = substr($Params, $Pos, 1); |
---|
196 | if(defined $B{$S}) { |
---|
197 | $B{$S} += 1; |
---|
198 | } |
---|
199 | if($S eq "," and |
---|
200 | $B{"("}==$B{")"} and $B{"<"}==$B{">"}) |
---|
201 | { |
---|
202 | if($Comma) |
---|
203 | { # include comma |
---|
204 | $Parts[$Part] .= $S; |
---|
205 | } |
---|
206 | $Part += 1; |
---|
207 | } |
---|
208 | else { |
---|
209 | $Parts[$Part] .= $S; |
---|
210 | } |
---|
211 | } |
---|
212 | if(not $Sp) |
---|
213 | { # remove spaces |
---|
214 | foreach (@Parts) |
---|
215 | { |
---|
216 | s/\A //g; |
---|
217 | s/ \Z//g; |
---|
218 | } |
---|
219 | } |
---|
220 | return @Parts; |
---|
221 | } |
---|
222 | |
---|
223 | sub simpleDecl($$) |
---|
224 | { |
---|
225 | my ($Line, $LVer) = @_; |
---|
226 | |
---|
227 | my %B = ( "<"=>0, ">"=>0 ); |
---|
228 | my @Chars = split("", $Line); |
---|
229 | |
---|
230 | my $Extends = undef; |
---|
231 | my ($Pre, $Post) = ("", ""); |
---|
232 | my @Replace = (); |
---|
233 | |
---|
234 | foreach my $Pos (0 .. $#Chars) |
---|
235 | { |
---|
236 | my $S = $Chars[$Pos]; |
---|
237 | if(defined $B{$S}) { |
---|
238 | $B{$S} += 1; |
---|
239 | } |
---|
240 | |
---|
241 | if($B{"<"}!=0) |
---|
242 | { |
---|
243 | if(defined $Extends) |
---|
244 | { |
---|
245 | my $E = 0; |
---|
246 | |
---|
247 | if($S eq ",") |
---|
248 | { |
---|
249 | if($B{"<"}-$B{">"}==$Extends) { |
---|
250 | $E = 1; |
---|
251 | } |
---|
252 | } |
---|
253 | elsif($S eq ">") |
---|
254 | { |
---|
255 | if($B{"<"}==$B{">"} or $B{"<"}-$B{">"}+1==$Extends) { |
---|
256 | $E = 1; |
---|
257 | } |
---|
258 | } |
---|
259 | |
---|
260 | if($E) |
---|
261 | { |
---|
262 | if($Post) { |
---|
263 | push(@Replace, $Post); |
---|
264 | } |
---|
265 | |
---|
266 | $Extends = undef; |
---|
267 | ($Pre, $Post) = ("", ""); |
---|
268 | } |
---|
269 | } |
---|
270 | elsif($B{"<"}!=$B{">"}) |
---|
271 | { |
---|
272 | if(substr($Pre, -9) eq " extends ") |
---|
273 | { |
---|
274 | $Extends = $B{"<"}-$B{">"}; |
---|
275 | } |
---|
276 | } |
---|
277 | } |
---|
278 | |
---|
279 | $Pre .= $S; |
---|
280 | if(defined $Extends) { |
---|
281 | $Post .= $S; |
---|
282 | } |
---|
283 | } |
---|
284 | |
---|
285 | my %Tmpl = (); |
---|
286 | |
---|
287 | foreach my $R (@Replace) |
---|
288 | { |
---|
289 | if($Line=~s/([A-Za-z\d\?]+) extends \Q$R\E/$1/) { |
---|
290 | $Tmpl{$1} = registerType($R, $LVer); |
---|
291 | } |
---|
292 | } |
---|
293 | |
---|
294 | return ($Line, \%Tmpl); |
---|
295 | } |
---|
296 | |
---|
297 | sub readClasses($$$) |
---|
298 | { |
---|
299 | my ($Paths, $LVer, $ArchiveName) = @_; |
---|
300 | |
---|
301 | my $JavapCmd = getCmdPath("javap"); |
---|
302 | if(not $JavapCmd) { |
---|
303 | exitStatus("Not_Found", "can't find \"javap\" command"); |
---|
304 | } |
---|
305 | |
---|
306 | my $TmpDir = $In::Opt{"Tmp"}; |
---|
307 | |
---|
308 | # ! private info should be processed |
---|
309 | my @Cmd = ($JavapCmd, "-s", "-private"); |
---|
310 | if(not $In::Opt{"Quick"}) { |
---|
311 | @Cmd = (@Cmd, "-c", "-verbose"); |
---|
312 | } |
---|
313 | |
---|
314 | @Cmd = (@Cmd, @{$Paths}); |
---|
315 | |
---|
316 | chdir($TmpDir."/".$ExtractCounter); |
---|
317 | my $Pid = open3(*IN, *OUT, *ERR, @Cmd); |
---|
318 | close(IN); |
---|
319 | |
---|
320 | my (%TypeAttr, $CurrentMethod, $CurrentPackage, $CurrentClass, $CurrentClass_Short) = (); |
---|
321 | my ($InParamTable, $InVarTypeTable, $InExceptionTable, $InCode) = (0, 0, 0, 0); |
---|
322 | |
---|
323 | my $InAnnotations = undef; |
---|
324 | my $InAnnotations_Class = undef; |
---|
325 | my $InAnnotations_Method = undef; |
---|
326 | my %AnnotationName = (); |
---|
327 | my %AnnotationNum = (); # support for Java 7 |
---|
328 | |
---|
329 | my ($ParamPos, $FieldPos) = (0, 0); |
---|
330 | my ($LINE, $Stay, $Run, $NonEmpty) = (undef, 0, 1, undef); |
---|
331 | |
---|
332 | my $DContent = ""; |
---|
333 | my $Debug = (defined $In::Opt{"Debug"}); |
---|
334 | |
---|
335 | while($Run) |
---|
336 | { |
---|
337 | if(not $Stay) |
---|
338 | { |
---|
339 | $LINE = <OUT>; |
---|
340 | |
---|
341 | if(not defined $NonEmpty and $LINE) { |
---|
342 | $NonEmpty = 1; |
---|
343 | } |
---|
344 | |
---|
345 | if($Debug) { |
---|
346 | $DContent .= $LINE; |
---|
347 | } |
---|
348 | } |
---|
349 | |
---|
350 | if(not $LINE) |
---|
351 | { |
---|
352 | $Run = 0; |
---|
353 | last; |
---|
354 | } |
---|
355 | |
---|
356 | $Stay = 0; |
---|
357 | |
---|
358 | if($LINE=~/\A\s*const/) { |
---|
359 | next; |
---|
360 | } |
---|
361 | |
---|
362 | if(index($LINE, 'Start Length')!=-1 |
---|
363 | or index($LINE, 'Compiled from')!=-1 |
---|
364 | or index($LINE, 'Last modified')!=-1 |
---|
365 | or index($LINE, 'MD5 checksum')!=-1 |
---|
366 | or index($LINE, 'Classfile /')==0 |
---|
367 | or index($LINE, 'Classfile jar')==0) |
---|
368 | { |
---|
369 | next; |
---|
370 | } |
---|
371 | |
---|
372 | if(index($LINE, '=')!=-1) |
---|
373 | { |
---|
374 | if(index($LINE, ' stack=')!=-1 |
---|
375 | or index($LINE, 'frame_type =')!=-1 |
---|
376 | or index($LINE, 'offset_delta =')!=-1) |
---|
377 | { |
---|
378 | next; |
---|
379 | } |
---|
380 | } |
---|
381 | |
---|
382 | if(index($LINE, ':')!=-1) |
---|
383 | { |
---|
384 | if(index($LINE, ' LineNumberTable:')!=-1 |
---|
385 | or index($LINE, 'SourceFile:')==0 |
---|
386 | or index($LINE, ' StackMapTable:')!=-1 |
---|
387 | or index($LINE, ' Exceptions:')!=-1 |
---|
388 | or index($LINE, 'Constant pool:')!=-1 |
---|
389 | or index($LINE, 'minor version:')!=-1 |
---|
390 | or index($LINE, 'major version:')!=-1 |
---|
391 | or index($LINE, ' AnnotationDefault:')!=-1) |
---|
392 | { |
---|
393 | next; |
---|
394 | } |
---|
395 | } |
---|
396 | |
---|
397 | if(index($LINE, " of ")!=-1 |
---|
398 | or index($LINE, "= [")!=-1) { |
---|
399 | next; |
---|
400 | } |
---|
401 | |
---|
402 | if($LINE=~/ line \d+:|\[\s*class|\$\d|\._\d/) |
---|
403 | { # artificial methods and code |
---|
404 | next; |
---|
405 | } |
---|
406 | |
---|
407 | # $LINE=~s/ \$(\w)/ $1/g; |
---|
408 | |
---|
409 | if(index($LINE, '$')!=-1) |
---|
410 | { |
---|
411 | if(index($LINE, ' class$')!=-1 |
---|
412 | or index($LINE, '$eq')!=-1 |
---|
413 | or index($LINE, '.$')!=-1 |
---|
414 | or index($LINE, '/$')!=-1 |
---|
415 | or index($LINE, '$$')!=-1 |
---|
416 | or index($LINE, '$(')!=-1 |
---|
417 | or index($LINE, '$:')!=-1 |
---|
418 | or index($LINE, '$.')!=-1 |
---|
419 | or index($LINE, '$;')!=-1) { |
---|
420 | next; |
---|
421 | } |
---|
422 | |
---|
423 | if($LINE=~/ (\w+\$|)\w+\$\w+[\(:]/) { |
---|
424 | next; |
---|
425 | } |
---|
426 | |
---|
427 | if(not $InParamTable and not $InVarTypeTable) |
---|
428 | { |
---|
429 | if(index($LINE, ' $')!=-1) { |
---|
430 | next; |
---|
431 | } |
---|
432 | } |
---|
433 | |
---|
434 | $LINE=~s/\$([\> ]|\s*\Z)/$1/g; |
---|
435 | } |
---|
436 | |
---|
437 | my $EndBr = ($LINE eq "}\n" or $LINE eq "}\r\n"); |
---|
438 | |
---|
439 | if($EndBr) { |
---|
440 | $InAnnotations_Class = 1; |
---|
441 | } |
---|
442 | |
---|
443 | if($EndBr or $LINE eq "\n" or $LINE eq "\r\n") |
---|
444 | { |
---|
445 | $CurrentMethod = undef; |
---|
446 | $InCode = 0; |
---|
447 | $InAnnotations_Method = 0; |
---|
448 | $InParamTable = 0; |
---|
449 | $InVarTypeTable = 0; |
---|
450 | next; |
---|
451 | } |
---|
452 | |
---|
453 | if(index($LINE, '#')!=-1) |
---|
454 | { |
---|
455 | if($LINE=~/\A\s*#(\d+)/) |
---|
456 | { # Constant pool |
---|
457 | my $CNum = $1; |
---|
458 | if($LINE=~/\s+([^ ]+?);/) |
---|
459 | { |
---|
460 | my $AName = $1; |
---|
461 | $AName=~s/\AL//; |
---|
462 | $AName=~s/\$/./g; |
---|
463 | $AName=~s/\//./g; |
---|
464 | |
---|
465 | $AnnotationName{$CNum} = $AName; |
---|
466 | |
---|
467 | if(defined $AnnotationNum{$CNum}) |
---|
468 | { # support for Java 7 |
---|
469 | if($InAnnotations_Class) { |
---|
470 | $TypeAttr{"Annotations"}{registerType($AName, $LVer)} = 1; |
---|
471 | } |
---|
472 | delete($AnnotationNum{$CNum}); |
---|
473 | } |
---|
474 | } |
---|
475 | |
---|
476 | next; |
---|
477 | } |
---|
478 | |
---|
479 | if(index($LINE, ": #")!=-1 and index($LINE, "//")!=-1) { |
---|
480 | next; |
---|
481 | } |
---|
482 | } |
---|
483 | |
---|
484 | my $TmplP = undef; |
---|
485 | |
---|
486 | # Java 7: templates |
---|
487 | if(index($LINE, "<")!=-1) |
---|
488 | { # <T extends java.lang.Object> |
---|
489 | # <KEYIN extends java.lang.Object ... |
---|
490 | if($LINE=~/<[A-Z\d\?]+ /i) { |
---|
491 | ($LINE, $TmplP) = simpleDecl($LINE, $LVer); |
---|
492 | } |
---|
493 | } |
---|
494 | |
---|
495 | if(index($LINE, ',')!=-1) { |
---|
496 | $LINE=~s/\s*,\s*/,/g; |
---|
497 | } |
---|
498 | |
---|
499 | if(index($LINE, '$')!=-1) { |
---|
500 | $LINE=~s/\$/#/g; |
---|
501 | } |
---|
502 | |
---|
503 | if(index($LINE, "LocalVariableTable")!=-1) { |
---|
504 | $InParamTable += 1; |
---|
505 | } |
---|
506 | elsif(index($LINE, "LocalVariableTypeTable")!=-1) { |
---|
507 | $InVarTypeTable += 1; |
---|
508 | } |
---|
509 | elsif(index($LINE, "Exception table")!=-1) { |
---|
510 | $InExceptionTable = 1; |
---|
511 | } |
---|
512 | elsif(index($LINE, " Code:")!=-1) |
---|
513 | { |
---|
514 | $InCode += 1; |
---|
515 | $InAnnotations = undef; |
---|
516 | } |
---|
517 | elsif(index($LINE, ':')!=-1 |
---|
518 | and $LINE=~/\A\s*\d+:\s*/) |
---|
519 | { # read Code |
---|
520 | if($InCode==1) |
---|
521 | { |
---|
522 | if($CurrentMethod) |
---|
523 | { |
---|
524 | if(index($LINE, "invoke")!=-1) |
---|
525 | { |
---|
526 | if($LINE=~/ invoke(\w+) .* \/\/\s*(Method|InterfaceMethod)\s+(.+?)\s*\Z/) |
---|
527 | { # 3: invokevirtual #2; //Method "[Lcom/sleepycat/je/Database#DbState;".clone:()Ljava/lang/Object; |
---|
528 | my ($InvokeType, $InvokedName) = ($1, $3); |
---|
529 | |
---|
530 | if($InvokedName!~/\A(\w+:|java\/(lang|util|io)\/)/ |
---|
531 | and index($InvokedName, '"<init>":')!=0) |
---|
532 | { |
---|
533 | $InvokedName=~s/#/\$/g; |
---|
534 | |
---|
535 | my $ID = undef; |
---|
536 | if($In::Opt{"Reproducible"}) { |
---|
537 | $ID = getMd5($InvokedName); |
---|
538 | } |
---|
539 | else { |
---|
540 | $ID = ++$U_ID; |
---|
541 | } |
---|
542 | |
---|
543 | $In::API{$LVer}{"MethodUsed"}{$ID}{"Name"} = $InvokedName; |
---|
544 | $In::API{$LVer}{"MethodUsed"}{$ID}{"Used"}{$CurrentMethod} = $InvokeType; |
---|
545 | } |
---|
546 | } |
---|
547 | } |
---|
548 | # elsif($LINE=~/ (getstatic|putstatic) .* \/\/\s*Field\s+(.+?)\s*\Z/) |
---|
549 | # { |
---|
550 | # my $UsedFieldName = $2; |
---|
551 | # $In::API{$LVer}{"FieldUsed"}{$UsedFieldName}{$CurrentMethod} = 1; |
---|
552 | # } |
---|
553 | } |
---|
554 | } |
---|
555 | elsif(defined $InAnnotations) |
---|
556 | { |
---|
557 | if($LINE=~/\A\s*\d+\:\s*#(\d+)/) |
---|
558 | { |
---|
559 | if(my $AName = $AnnotationName{$1}) |
---|
560 | { |
---|
561 | if($InAnnotations_Class) { |
---|
562 | $TypeAttr{"Annotations"}{registerType($AName, $LVer)} = 1; |
---|
563 | } |
---|
564 | elsif($InAnnotations_Method) { |
---|
565 | $MethodInfo{$LVer}{$MName_Mid{$CurrentMethod}}{"Annotations"}{registerType($AName, $LVer)} = 1; |
---|
566 | } |
---|
567 | } |
---|
568 | else |
---|
569 | { # suport for Java 7 |
---|
570 | $AnnotationNum{$1} = 1; |
---|
571 | } |
---|
572 | } |
---|
573 | } |
---|
574 | } |
---|
575 | elsif($InParamTable==1 and $LINE=~/\A\s+\d/) |
---|
576 | { # read parameter names from LocalVariableTable |
---|
577 | if($CurrentMethod and $LINE=~/\A\s+0\s+\d+\s+\d+\s+(\#?)(\w+)/) |
---|
578 | { |
---|
579 | my $Art = $1; |
---|
580 | my $PName = $2; |
---|
581 | |
---|
582 | if(($PName ne "this" or $Art) and $PName=~/[a-z]/i) |
---|
583 | { |
---|
584 | if($CurrentMethod) |
---|
585 | { |
---|
586 | my $ID = $MName_Mid{$CurrentMethod}; |
---|
587 | |
---|
588 | if(defined $MethodInfo{$LVer}{$ID} |
---|
589 | and defined $MethodInfo{$LVer}{$ID}{"Param"} |
---|
590 | and defined $MethodInfo{$LVer}{$ID}{"Param"}{$ParamPos} |
---|
591 | and defined $MethodInfo{$LVer}{$ID}{"Param"}{$ParamPos}{"Type"}) |
---|
592 | { |
---|
593 | $MethodInfo{$LVer}{$ID}{"Param"}{$ParamPos}{"Name"} = $PName; |
---|
594 | $ParamPos++; |
---|
595 | } |
---|
596 | } |
---|
597 | } |
---|
598 | } |
---|
599 | } |
---|
600 | elsif($InVarTypeTable==1 and $LINE=~/\A\s+\d/) |
---|
601 | { |
---|
602 | # skip |
---|
603 | } |
---|
604 | elsif($CurrentClass and index($LINE, '(')!=-1 |
---|
605 | and $LINE=~/(\A|\s+)([^\s]+)\s+([^\s]+)\s*\((.*)\)\s*(throws\s*([^\s]+)|)\s*;\s*\Z/) |
---|
606 | { # attributes of methods and constructors |
---|
607 | my (%MethodAttr, $ParamsLine, $Exceptions) = (); |
---|
608 | |
---|
609 | $InParamTable = 0; # read the first local variable table |
---|
610 | $InVarTypeTable = 0; |
---|
611 | $InCode = 0; # read the first code |
---|
612 | $InAnnotations_Method = 1; |
---|
613 | $InAnnotations_Class = 0; |
---|
614 | |
---|
615 | ($MethodAttr{"Return"}, $MethodAttr{"ShortName"}, $ParamsLine, $Exceptions) = ($2, $3, $4, $6); |
---|
616 | $MethodAttr{"ShortName"}=~s/#/./g; |
---|
617 | |
---|
618 | if($Exceptions) |
---|
619 | { |
---|
620 | foreach my $E (split(/,/, $Exceptions)) { |
---|
621 | $MethodAttr{"Exceptions"}{registerType($E, $LVer)} = 1; |
---|
622 | } |
---|
623 | } |
---|
624 | if($LINE=~/(\A|\s+)(public|protected|private)\s+/) { |
---|
625 | $MethodAttr{"Access"} = $2; |
---|
626 | } |
---|
627 | else { |
---|
628 | $MethodAttr{"Access"} = "package-private"; |
---|
629 | } |
---|
630 | $MethodAttr{"Class"} = registerType($TypeAttr{"Name"}, $LVer); |
---|
631 | if($MethodAttr{"ShortName"}=~/\A(|(.+)\.)(\Q$CurrentClass\E|\Q$CurrentClass_Short\E)\Z/) |
---|
632 | { |
---|
633 | if($2) |
---|
634 | { |
---|
635 | $MethodAttr{"Package"} = $2; |
---|
636 | $CurrentPackage = $MethodAttr{"Package"}; |
---|
637 | $MethodAttr{"ShortName"} = $CurrentClass; |
---|
638 | } |
---|
639 | $MethodAttr{"Constructor"} = 1; |
---|
640 | delete($MethodAttr{"Return"}); |
---|
641 | } |
---|
642 | else |
---|
643 | { |
---|
644 | $MethodAttr{"Return"} = registerType($MethodAttr{"Return"}, $LVer); |
---|
645 | } |
---|
646 | |
---|
647 | my @Params = sepParams($ParamsLine, 0, 1); |
---|
648 | |
---|
649 | $ParamPos = 0; |
---|
650 | foreach my $ParamTName (@Params) |
---|
651 | { |
---|
652 | %{$MethodAttr{"Param"}{$ParamPos}} = ("Type"=>registerType($ParamTName, $LVer), "Name"=>"p".($ParamPos+1)); |
---|
653 | $ParamPos++; |
---|
654 | } |
---|
655 | $ParamPos = 0; |
---|
656 | if(not $MethodAttr{"Constructor"}) |
---|
657 | { # methods |
---|
658 | if($CurrentPackage) { |
---|
659 | $MethodAttr{"Package"} = $CurrentPackage; |
---|
660 | } |
---|
661 | if($LINE=~/(\A|\s+)abstract\s+/) { |
---|
662 | $MethodAttr{"Abstract"} = 1; |
---|
663 | } |
---|
664 | if($LINE=~/(\A|\s+)final\s+/) { |
---|
665 | $MethodAttr{"Final"} = 1; |
---|
666 | } |
---|
667 | if($LINE=~/(\A|\s+)static\s+/) { |
---|
668 | $MethodAttr{"Static"} = 1; |
---|
669 | } |
---|
670 | if($LINE=~/(\A|\s+)native\s+/) { |
---|
671 | $MethodAttr{"Native"} = 1; |
---|
672 | } |
---|
673 | if($LINE=~/(\A|\s+)synchronized\s+/) { |
---|
674 | $MethodAttr{"Synchronized"} = 1; |
---|
675 | } |
---|
676 | } |
---|
677 | |
---|
678 | my $LINE_N = <OUT>; |
---|
679 | |
---|
680 | if($Debug) { |
---|
681 | $DContent .= $LINE_N; |
---|
682 | } |
---|
683 | |
---|
684 | # $LINE_N=~s/ \$(\w)/ $1/g; |
---|
685 | $LINE_N=~s/\$([\> ]|\s*\Z)/$1/g; |
---|
686 | |
---|
687 | # read the Signature |
---|
688 | if(index($LINE_N, ": #")==-1 |
---|
689 | and $LINE_N=~/(Signature|descriptor):\s*(.+?)\s*\Z/i) |
---|
690 | { # create run-time unique name ( java/io/PrintStream.println (Ljava/lang/String;)V ) |
---|
691 | my $SignParams = $2; |
---|
692 | |
---|
693 | # Generic classes |
---|
694 | my $ShortClass = $CurrentClass; |
---|
695 | $ShortClass=~s/<.*>//g; |
---|
696 | |
---|
697 | if($MethodAttr{"Constructor"}) { |
---|
698 | $CurrentMethod = $ShortClass.".\"<init>\":".$SignParams; |
---|
699 | } |
---|
700 | else { |
---|
701 | $CurrentMethod = $ShortClass.".".$MethodAttr{"ShortName"}.":".$SignParams; |
---|
702 | } |
---|
703 | if(my $PackageName = getSFormat($CurrentPackage)) { |
---|
704 | $CurrentMethod = $PackageName."/".$CurrentMethod; |
---|
705 | } |
---|
706 | } |
---|
707 | else { |
---|
708 | exitStatus("Error", "internal error - can't read method signature"); |
---|
709 | } |
---|
710 | |
---|
711 | $MethodAttr{"Archive"} = $ArchiveName; |
---|
712 | if($CurrentMethod) |
---|
713 | { |
---|
714 | my $ID = undef; |
---|
715 | if($In::Opt{"Reproducible"}) { |
---|
716 | $ID = getMd5($CurrentMethod); |
---|
717 | } |
---|
718 | else { |
---|
719 | $ID = ++$M_ID; |
---|
720 | } |
---|
721 | |
---|
722 | $MName_Mid{$CurrentMethod} = $ID; |
---|
723 | |
---|
724 | if(defined $Mid_MName{$ID} and $Mid_MName{$ID} ne $CurrentMethod) { |
---|
725 | printMsg("ERROR", "md5 collision on \'$ID\', please increase ID length (MD5_LEN in Basic.pm)"); |
---|
726 | } |
---|
727 | |
---|
728 | $Mid_MName{$ID} = $CurrentMethod; |
---|
729 | |
---|
730 | $MethodAttr{"Name"} = $CurrentMethod; |
---|
731 | $MethodInfo{$LVer}{$ID} = \%MethodAttr; |
---|
732 | } |
---|
733 | } |
---|
734 | elsif($CurrentClass and $LINE=~/(\A|\s+)([^\s]+)\s+(\w+);\s*\Z/) |
---|
735 | { # fields |
---|
736 | my ($TName, $FName) = ($2, $3); |
---|
737 | $TypeAttr{"Fields"}{$FName}{"Type"} = registerType($TName, $LVer); |
---|
738 | if($LINE=~/(\A|\s+)final\s+/) { |
---|
739 | $TypeAttr{"Fields"}{$FName}{"Final"} = 1; |
---|
740 | } |
---|
741 | if($LINE=~/(\A|\s+)static\s+/) { |
---|
742 | $TypeAttr{"Fields"}{$FName}{"Static"} = 1; |
---|
743 | } |
---|
744 | if($LINE=~/(\A|\s+)transient\s+/) { |
---|
745 | $TypeAttr{"Fields"}{$FName}{"Transient"} = 1; |
---|
746 | } |
---|
747 | if($LINE=~/(\A|\s+)volatile\s+/) { |
---|
748 | $TypeAttr{"Fields"}{$FName}{"Volatile"} = 1; |
---|
749 | } |
---|
750 | if($LINE=~/(\A|\s+)(public|protected|private)\s+/) { |
---|
751 | $TypeAttr{"Fields"}{$FName}{"Access"} = $2; |
---|
752 | } |
---|
753 | else { |
---|
754 | $TypeAttr{"Fields"}{$FName}{"Access"} = "package-private"; |
---|
755 | } |
---|
756 | |
---|
757 | $TypeAttr{"Fields"}{$FName}{"Pos"} = $FieldPos++; |
---|
758 | |
---|
759 | my $LINE_NP = <OUT>; |
---|
760 | if($Debug) { |
---|
761 | $DContent .= $LINE_NP; |
---|
762 | } |
---|
763 | |
---|
764 | # read the Signature |
---|
765 | if(index($LINE_NP, ": #")==-1 |
---|
766 | and $LINE_NP=~/(Signature|descriptor):\s*(.+?)\s*\Z/i) |
---|
767 | { |
---|
768 | my $FSignature = $2; |
---|
769 | if(my $PackageName = getSFormat($CurrentPackage)) { |
---|
770 | $TypeAttr{"Fields"}{$FName}{"Mangled"} = $PackageName."/".$CurrentClass.".".$FName.":".$FSignature; |
---|
771 | } |
---|
772 | } |
---|
773 | |
---|
774 | $LINE_NP = <OUT>; |
---|
775 | if($Debug) { |
---|
776 | $DContent .= $LINE_NP; |
---|
777 | } |
---|
778 | |
---|
779 | if($LINE_NP=~/flags:/i) |
---|
780 | { # flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ANNOTATION |
---|
781 | $LINE_NP = <OUT>; |
---|
782 | if($Debug) { |
---|
783 | $DContent .= $LINE_NP; |
---|
784 | } |
---|
785 | } |
---|
786 | else |
---|
787 | { |
---|
788 | $LINE = $LINE_NP; |
---|
789 | $Stay = 1; |
---|
790 | } |
---|
791 | |
---|
792 | # read the Value |
---|
793 | if($LINE_NP=~/Constant\s*value:\s*([^\s]+)\s(.*?)\s*\Z/i) |
---|
794 | { |
---|
795 | # Java 6: Constant value: ... |
---|
796 | # Java 7: ConstantValue: ... |
---|
797 | my ($TName, $Value) = ($1, $2); |
---|
798 | if($Value) |
---|
799 | { |
---|
800 | if($Value=~s/Deprecated:\s*true\Z//g) { |
---|
801 | # deprecated values: ? |
---|
802 | } |
---|
803 | $TypeAttr{"Fields"}{$FName}{"Value"} = $Value; |
---|
804 | } |
---|
805 | elsif($TName eq "String") { |
---|
806 | $TypeAttr{"Fields"}{$FName}{"Value"} = "\@EMPTY_STRING\@"; |
---|
807 | } |
---|
808 | } |
---|
809 | else |
---|
810 | { |
---|
811 | $LINE = $LINE_NP; |
---|
812 | $Stay = 1; |
---|
813 | } |
---|
814 | } |
---|
815 | elsif($LINE=~/(\A|\s+)(class|interface)\s+([^\s\{]+)(\s+|\{|\s*\Z)/) |
---|
816 | { # properties of classes and interfaces |
---|
817 | if($TypeAttr{"Name"}) |
---|
818 | { # register previous |
---|
819 | %{$TypeInfo{$LVer}{registerType($TypeAttr{"Name"}, $LVer)}} = %TypeAttr; |
---|
820 | } |
---|
821 | |
---|
822 | %TypeAttr = ("Type"=>$2, "Name"=>$3); # reset previous class |
---|
823 | %AnnotationName = (); # reset annotations of the class |
---|
824 | %AnnotationNum = (); # support for Java 7 |
---|
825 | $InAnnotations_Class = 1; |
---|
826 | |
---|
827 | $FieldPos = 0; # reset field position |
---|
828 | $CurrentMethod = ""; # reset current method |
---|
829 | $TypeAttr{"Archive"} = $ArchiveName; |
---|
830 | if($TypeAttr{"Name"}=~/\A(.+)\.([^.]+)\Z/) |
---|
831 | { |
---|
832 | $CurrentClass = $2; |
---|
833 | $TypeAttr{"Package"} = $1; |
---|
834 | $CurrentPackage = $TypeAttr{"Package"}; |
---|
835 | } |
---|
836 | else |
---|
837 | { |
---|
838 | $CurrentClass = $TypeAttr{"Name"}; |
---|
839 | $CurrentPackage = ""; |
---|
840 | } |
---|
841 | |
---|
842 | if($CurrentClass=~s/#/./g) |
---|
843 | { # javax.swing.text.GlyphView.GlyphPainter <=> GlyphView$GlyphPainter |
---|
844 | $TypeAttr{"Name"}=~s/#/./g; |
---|
845 | } |
---|
846 | |
---|
847 | $CurrentClass_Short = $CurrentClass; |
---|
848 | if(index($CurrentClass_Short, "<")!=-1) { |
---|
849 | $CurrentClass_Short=~s/<.+>//g; |
---|
850 | } |
---|
851 | |
---|
852 | if($LINE=~/(\A|\s+)(public|protected|private)\s+/) { |
---|
853 | $TypeAttr{"Access"} = $2; |
---|
854 | } |
---|
855 | else { |
---|
856 | $TypeAttr{"Access"} = "package-private"; |
---|
857 | } |
---|
858 | if($LINE=~/\s+extends\s+([^\s\{]+)/) |
---|
859 | { |
---|
860 | my $Extended = $1; |
---|
861 | |
---|
862 | if($TypeAttr{"Type"} eq "class") |
---|
863 | { |
---|
864 | if($Extended ne $CurrentPackage.".".$CurrentClass) { |
---|
865 | $TypeAttr{"SuperClass"} = registerType($Extended, $LVer); |
---|
866 | } |
---|
867 | } |
---|
868 | elsif($TypeAttr{"Type"} eq "interface") |
---|
869 | { |
---|
870 | my @Elems = sepParams($Extended, 0, 0); |
---|
871 | foreach my $SuperInterface (@Elems) |
---|
872 | { |
---|
873 | if($SuperInterface ne $CurrentPackage.".".$CurrentClass) { |
---|
874 | $TypeAttr{"SuperInterface"}{registerType($SuperInterface, $LVer)} = 1; |
---|
875 | } |
---|
876 | |
---|
877 | if($SuperInterface eq "java.lang.annotation.Annotation") { |
---|
878 | $TypeAttr{"Annotation"} = 1; |
---|
879 | } |
---|
880 | } |
---|
881 | } |
---|
882 | } |
---|
883 | if($LINE=~/\s+implements\s+([^\s\{]+)/) |
---|
884 | { |
---|
885 | my $Implemented = $1; |
---|
886 | my @Elems = sepParams($Implemented, 0, 0); |
---|
887 | |
---|
888 | foreach my $SuperInterface (@Elems) { |
---|
889 | $TypeAttr{"SuperInterface"}{registerType($SuperInterface, $LVer)} = 1; |
---|
890 | } |
---|
891 | } |
---|
892 | if($LINE=~/(\A|\s+)abstract\s+/) { |
---|
893 | $TypeAttr{"Abstract"} = 1; |
---|
894 | } |
---|
895 | if($LINE=~/(\A|\s+)final\s+/) { |
---|
896 | $TypeAttr{"Final"} = 1; |
---|
897 | } |
---|
898 | if($LINE=~/(\A|\s+)static\s+/) { |
---|
899 | $TypeAttr{"Static"} = 1; |
---|
900 | } |
---|
901 | |
---|
902 | if($TmplP) { |
---|
903 | $TypeAttr{"GenericParam"} = $TmplP; |
---|
904 | } |
---|
905 | } |
---|
906 | elsif(index($LINE, "Deprecated: true")!=-1 |
---|
907 | or index($LINE, "Deprecated: length")!=-1) |
---|
908 | { # deprecated method or class |
---|
909 | if($CurrentMethod) { |
---|
910 | $MethodInfo{$LVer}{$MName_Mid{$CurrentMethod}}{"Deprecated"} = 1; |
---|
911 | } |
---|
912 | elsif($CurrentClass) { |
---|
913 | $TypeAttr{"Deprecated"} = 1; |
---|
914 | } |
---|
915 | } |
---|
916 | elsif(index($LINE, "RuntimeInvisibleAnnotations")!=-1 |
---|
917 | or index($LINE, "RuntimeVisibleAnnotations")!=-1) |
---|
918 | { |
---|
919 | $InAnnotations = 1; |
---|
920 | $InCode = 0; |
---|
921 | } |
---|
922 | elsif(defined $InAnnotations and index($LINE, "InnerClasses")!=-1) { |
---|
923 | $InAnnotations = undef; |
---|
924 | } |
---|
925 | else |
---|
926 | { |
---|
927 | # unparsed |
---|
928 | } |
---|
929 | } |
---|
930 | |
---|
931 | if($TypeAttr{"Name"}) |
---|
932 | { # register last |
---|
933 | %{$TypeInfo{$LVer}{registerType($TypeAttr{"Name"}, $LVer)}} = %TypeAttr; |
---|
934 | } |
---|
935 | |
---|
936 | waitpid($Pid, 0); |
---|
937 | chdir($In::Opt{"OrigDir"}); |
---|
938 | |
---|
939 | if(my $Err = $?>>8) { |
---|
940 | exitStatus("Error", "failed to run javap"); |
---|
941 | } |
---|
942 | |
---|
943 | if(not $NonEmpty) { |
---|
944 | exitStatus("Error", "internal error in parser"); |
---|
945 | } |
---|
946 | |
---|
947 | if($Debug) { |
---|
948 | appendFile(getDebugDir($LVer)."/class-dump.txt", $DContent); |
---|
949 | } |
---|
950 | } |
---|
951 | |
---|
952 | sub registerType($$) |
---|
953 | { |
---|
954 | my ($TName, $LVer) = @_; |
---|
955 | |
---|
956 | if(not $TName) { |
---|
957 | return 0; |
---|
958 | } |
---|
959 | |
---|
960 | $TName=~s/#/./g; |
---|
961 | if($TName_Tid{$LVer}{$TName}) { |
---|
962 | return $TName_Tid{$LVer}{$TName}; |
---|
963 | } |
---|
964 | |
---|
965 | if(not $TName_Tid{$LVer}{$TName}) |
---|
966 | { |
---|
967 | my $ID = undef; |
---|
968 | if($In::Opt{"Reproducible"}) { |
---|
969 | $ID = getMd5($TName); |
---|
970 | } |
---|
971 | else { |
---|
972 | $ID = ++$T_ID; |
---|
973 | } |
---|
974 | $TName_Tid{$LVer}{$TName} = "$ID"; |
---|
975 | } |
---|
976 | |
---|
977 | my $Tid = $TName_Tid{$LVer}{$TName}; |
---|
978 | $TypeInfo{$LVer}{$Tid}{"Name"} = $TName; |
---|
979 | if($TName=~/(.+)\[\]\Z/) |
---|
980 | { |
---|
981 | if(my $BaseTypeId = registerType($1, $LVer)) |
---|
982 | { |
---|
983 | $TypeInfo{$LVer}{$Tid}{"BaseType"} = $BaseTypeId; |
---|
984 | $TypeInfo{$LVer}{$Tid}{"Type"} = "array"; |
---|
985 | } |
---|
986 | } |
---|
987 | elsif($TName=~/(.+)\.\.\.\Z/) |
---|
988 | { |
---|
989 | if(my $BaseTypeId = registerType($1, $LVer)) |
---|
990 | { |
---|
991 | $TypeInfo{$LVer}{$Tid}{"BaseType"} = $BaseTypeId; |
---|
992 | $TypeInfo{$LVer}{$Tid}{"Type"} = "variable-arity"; |
---|
993 | } |
---|
994 | } |
---|
995 | |
---|
996 | return $Tid; |
---|
997 | } |
---|
998 | |
---|
999 | sub readClasses_Usage($) |
---|
1000 | { |
---|
1001 | my $Paths = $_[0]; |
---|
1002 | |
---|
1003 | my $JavapCmd = getCmdPath("javap"); |
---|
1004 | if(not $JavapCmd) { |
---|
1005 | exitStatus("Not_Found", "can't find \"javap\" command"); |
---|
1006 | } |
---|
1007 | |
---|
1008 | my $Input = join(" ", @{$Paths}); |
---|
1009 | if($In::Opt{"OS"} ne "windows") |
---|
1010 | { # on unix ensure that the system does not try and interpret the $, by escaping it |
---|
1011 | $Input=~s/\$/\\\$/g; |
---|
1012 | } |
---|
1013 | |
---|
1014 | my $TmpDir = $In::Opt{"Tmp"}; |
---|
1015 | |
---|
1016 | chdir($TmpDir."/".$ExtractCounter); |
---|
1017 | open(CONTENT, "$JavapCmd -c -private $Input 2>\"$TmpDir/warn\" |"); |
---|
1018 | while(<CONTENT>) |
---|
1019 | { |
---|
1020 | if(/\/\/\s*(Method|InterfaceMethod)\s+(.+)\s*\Z/) |
---|
1021 | { |
---|
1022 | my $M = $2; |
---|
1023 | $In::Opt{"UsedMethods_Client"}{$M} = 1; |
---|
1024 | |
---|
1025 | if($M=~/\A(.*)+\.\w+\:\(/) |
---|
1026 | { |
---|
1027 | my $C = $1; |
---|
1028 | $C=~s/\//./g; |
---|
1029 | $In::Opt{"UsedClasses_Client"}{$C} = 1; |
---|
1030 | } |
---|
1031 | } |
---|
1032 | elsif(/\/\/\s*Field\s+(.+)\s*\Z/) |
---|
1033 | { |
---|
1034 | # my $FieldName = $1; |
---|
1035 | # if(/\s+(putfield|getfield|getstatic|putstatic)\s+/) { |
---|
1036 | # $UsedFields_Client{$FieldName} = $1; |
---|
1037 | # } |
---|
1038 | } |
---|
1039 | elsif(/ ([^\s]+) [^: ]+\(([^()]+)\)/) |
---|
1040 | { |
---|
1041 | my ($Ret, $Params) = ($1, $2); |
---|
1042 | |
---|
1043 | $Ret=~s/\[\]//g; # quals |
---|
1044 | $In::Opt{"UsedClasses_Client"}{$Ret} = 1; |
---|
1045 | |
---|
1046 | foreach my $Param (split(/\s*,\s*/, $Params)) |
---|
1047 | { |
---|
1048 | $Param=~s/\[\]//g; # quals |
---|
1049 | $In::Opt{"UsedClasses_Client"}{$Param} = 1; |
---|
1050 | } |
---|
1051 | } |
---|
1052 | elsif(/ class /) |
---|
1053 | { |
---|
1054 | if(/extends ([^\s{]+)/) |
---|
1055 | { |
---|
1056 | foreach my $Class (split(/\s*,\s*/, $1)) { |
---|
1057 | $In::Opt{"UsedClasses_Client"}{$Class} = 1; |
---|
1058 | } |
---|
1059 | } |
---|
1060 | |
---|
1061 | if(/implements ([^\s{]+)/) |
---|
1062 | { |
---|
1063 | foreach my $Interface (split(/\s*,\s*/, $1)) { |
---|
1064 | $In::Opt{"UsedClasses_Client"}{$Interface} = 1; |
---|
1065 | } |
---|
1066 | } |
---|
1067 | } |
---|
1068 | } |
---|
1069 | close(CONTENT); |
---|
1070 | chdir($In::Opt{"OrigDir"}); |
---|
1071 | } |
---|
1072 | |
---|
1073 | return 1; |
---|