########################################################################### # A module to create API dump from disassembled code # # Copyright (C) 2016-2018 Andrey Ponomarenko's ABI Laboratory # # Written by Andrey Ponomarenko # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301 USA. ########################################################################### use strict; use IPC::Open3; my $ExtractCounter = 0; my %MName_Mid; my %Mid_MName; my $T_ID = 0; my $M_ID = 0; my $U_ID = 0; # Aliases my (%MethodInfo, %TypeInfo, %TName_Tid) = (); foreach (1, 2) { $MethodInfo{$_} = $In::API{$_}{"MethodInfo"}; $TypeInfo{$_} = $In::API{$_}{"TypeInfo"}; $TName_Tid{$_} = $In::API{$_}{"TName_Tid"}; } sub createAPIDump($) { my $LVer = $_[0]; readArchives($LVer); if(not keys(%{$MethodInfo{$LVer}})) { printMsg("WARNING", "empty dump"); } $In::API{$LVer}{"LibraryVersion"} = $In::Desc{$LVer}{"Version"}; $In::API{$LVer}{"LibraryName"} = $In::Opt{"TargetLib"}; $In::API{$LVer}{"Language"} = "Java"; } sub readArchives($) { my $LVer = $_[0]; my @ArchivePaths = getArchives($LVer); if($#ArchivePaths==-1) { exitStatus("Error", "Java archives are not found in ".$In::Desc{$LVer}{"Version"}); } printMsg("INFO", "Reading classes ".$In::Desc{$LVer}{"Version"}." ..."); $T_ID = 0; $M_ID = 0; $U_ID = 0; %MName_Mid = (); %Mid_MName = (); foreach my $ArchivePath (sort {length($a)<=>length($b)} @ArchivePaths) { readArchive($LVer, $ArchivePath); } foreach my $TName (keys(%{$TName_Tid{$LVer}})) { my $Tid = $TName_Tid{$LVer}{$TName}; if(not $TypeInfo{$LVer}{$Tid}{"Type"}) { if($TName=~/\A(void|boolean|char|byte|short|int|float|long|double)\Z/) { $TypeInfo{$LVer}{$Tid}{"Type"} = "primitive"; } else { $TypeInfo{$LVer}{$Tid}{"Type"} = "class"; } } } } sub getArchives($) { my $LVer = $_[0]; my @Paths = (); foreach my $Path (keys(%{$In::Desc{$LVer}{"Archives"}})) { if(not -e $Path) { exitStatus("Access_Error", "can't access \'$Path\'"); } foreach (getArchivePaths($Path, $LVer)) { push(@Paths, $_); } } return @Paths; } sub readArchive($$) { # 1, 2 - library, 0 - client my ($LVer, $Path) = @_; $Path = getAbsPath($Path); my $ExtractCmd = undef; if($Path=~/\.jar\Z/) { $ExtractCmd = getCmdPath("jar"); if(not $ExtractCmd) { exitStatus("Not_Found", "can't find \"jar\" command"); } $ExtractCmd .= " -xf \"$Path\""; } elsif($Path=~/\.jmod\Z/) { $ExtractCmd = getCmdPath("jmod"); if(not $ExtractCmd) { exitStatus("Not_Found", "can't find \"jmod\" command"); } $ExtractCmd .= " extract \"$Path\""; } else { exitStatus("Error", "unknown format of \'$Path\'"); } my $ExtractPath = join_P($In::Opt{"Tmp"}, $ExtractCounter); if(-d $ExtractPath) { rmtree($ExtractPath); } mkpath($ExtractPath); chdir($ExtractPath); system($ExtractCmd); if($?) { exitStatus("Error", "can't extract \'$Path\'"); } chdir($In::Opt{"OrigDir"}); my @Classes = (); foreach my $ClassPath (cmdFind($ExtractPath, "", "*\\.class")) { if($In::Opt{"OS"} ne "windows") { $ClassPath=~s/\.class\Z//g; } my $ClassName = getFilename($ClassPath); if($ClassName=~/\$\d/ or $ClassName eq "module-info") { next; } $ClassPath = cutPrefix($ClassPath, $ExtractPath); # javap decompiler accepts relative paths only my $ClassDir = getDirname($ClassPath); if($ClassDir=~/\./) { # jaxb-osgi.jar/1.0/org/apache next; } my $Package = getPFormat($ClassDir); if($LVer) { if(skipPackage($Package, $LVer)) { # internal packages next; } } push(@Classes, $ClassPath); } if($#Classes!=-1) { foreach my $PartRef (divideArray(\@Classes)) { if($LVer) { readClasses($PartRef, $LVer, getFilename($Path)); } else { readClasses_Usage($PartRef); } } } $ExtractCounter += 1; if($LVer) { foreach my $SubArchive (cmdFind($ExtractPath, "", "*\\.jar")) { # recursive step readArchive($LVer, $SubArchive); } foreach my $SubArchive (cmdFind($ExtractPath, "", "*\\.jmod")) { # recursive step readArchive($LVer, $SubArchive); } } rmtree($ExtractPath); } sub sepParams($$$) { my ($Params, $Comma, $Sp) = @_; my @Parts = (); my %B = ( "("=>0, "<"=>0, ")"=>0, ">"=>0 ); my $Part = 0; foreach my $Pos (0 .. length($Params) - 1) { my $S = substr($Params, $Pos, 1); if(defined $B{$S}) { $B{$S} += 1; } if($S eq "," and $B{"("}==$B{")"} and $B{"<"}==$B{">"}) { if($Comma) { # include comma $Parts[$Part] .= $S; } $Part += 1; } else { $Parts[$Part] .= $S; } } if(not $Sp) { # remove spaces foreach (@Parts) { s/\A //g; s/ \Z//g; } } return @Parts; } sub simpleDecl($$) { my ($Line, $LVer) = @_; my %B = ( "<"=>0, ">"=>0 ); my @Chars = split("", $Line); my $Extends = undef; my ($Pre, $Post) = ("", ""); my @Replace = (); foreach my $Pos (0 .. $#Chars) { my $S = $Chars[$Pos]; if(defined $B{$S}) { $B{$S} += 1; } if($B{"<"}!=0) { if(defined $Extends) { my $E = 0; if($S eq ",") { if($B{"<"}-$B{">"}==$Extends) { $E = 1; } } elsif($S eq ">") { if($B{"<"}==$B{">"} or $B{"<"}-$B{">"}+1==$Extends) { $E = 1; } } if($E) { if($Post) { push(@Replace, $Post); } $Extends = undef; ($Pre, $Post) = ("", ""); } } elsif($B{"<"}!=$B{">"}) { if(substr($Pre, -9) eq " extends ") { $Extends = $B{"<"}-$B{">"}; } } } $Pre .= $S; if(defined $Extends) { $Post .= $S; } } my %Tmpl = (); foreach my $R (@Replace) { if($Line=~s/([A-Za-z\d\?]+) extends \Q$R\E/$1/) { $Tmpl{$1} = registerType($R, $LVer); } } return ($Line, \%Tmpl); } sub readClasses($$$) { my ($Paths, $LVer, $ArchiveName) = @_; my $JavapCmd = getCmdPath("javap"); if(not $JavapCmd) { exitStatus("Not_Found", "can't find \"javap\" command"); } my $TmpDir = $In::Opt{"Tmp"}; my $DumpFile = undef; if(defined $In::Opt{"Debug"}) { if(my $DebugDir = getDebugDir($LVer)) { mkpath($DebugDir); $DumpFile = $DebugDir."/class-dump.txt"; } } # ! private info should be processed my @Cmd = ($JavapCmd, "-s", "-private"); if(not $In::Opt{"Quick"}) { @Cmd = (@Cmd, "-c", "-verbose"); } @Cmd = (@Cmd, @{$Paths}); chdir($TmpDir."/".$ExtractCounter); my ($Err, $ErrMsg) = (); my $Pid = open3(*IN, *OUT, *ERR, @Cmd); ($Err, $ErrMsg) = ($?, $!); close(IN); close(ERR); chdir($In::Opt{"OrigDir"}); if($Err==-1 and $Err>>8 and $ErrMsg) { exitStatus("Error", "failed to run javap (".$ErrMsg.")"); } my (%TypeAttr, $CurrentMethod, $CurrentPackage, $CurrentClass, $CurrentClass_Short) = (); my ($InParamTable, $InVarTypeTable, $InExceptionTable, $InCode) = (0, 0, 0, 0); my $InAnnotations = undef; my $InAnnotations_Class = undef; my $InAnnotations_Method = undef; my %ConstantTypeName = (); my %AnnotationNum = (); # support for Java 7 my %ConstantName = (); my ($ParamPos, $FieldPos) = (0, 0); my ($LINE, $Stay, $Run, $NonEmpty) = (undef, 0, 1, undef); my $DContent = ""; while($Run) { if(not $Stay) { $LINE = ; if(not defined $NonEmpty and $LINE) { $NonEmpty = 1; } if(defined $In::Opt{"Debug"}) { $DContent .= $LINE; } } if(not $LINE) { $Run = 0; last; } $Stay = 0; if($LINE=~/\A\s*const/) { next; } if(index($LINE, 'Start Length')!=-1 or index($LINE, 'Compiled from')!=-1 or index($LINE, 'Last modified')!=-1 or index($LINE, 'MD5 checksum')!=-1 or index($LINE, 'Classfile /')==0 or index($LINE, 'Classfile jar')==0) { next; } if(index($LINE, '=')!=-1) { if(index($LINE, ' stack=')!=-1 or index($LINE, 'frame_type =')!=-1 or index($LINE, 'offset_delta =')!=-1) { next; } } if(index($LINE, ':')!=-1) { if(index($LINE, ' LineNumberTable:')!=-1 or index($LINE, 'SourceFile:')==0 or index($LINE, ' StackMapTable:')!=-1 or index($LINE, ' Exceptions:')!=-1 or index($LINE, 'Constant pool:')!=-1 or index($LINE, 'minor version:')!=-1 or index($LINE, 'major version:')!=-1 or index($LINE, ' AnnotationDefault:')!=-1) { next; } } if(index($LINE, " of ")!=-1 or index($LINE, "= [")!=-1) { next; } if($LINE=~/ line \d+:|\[\s*class|\$\d|\._\d/) { # artificial methods and code next; } # $LINE=~s/ \$(\w)/ $1/g; if(index($LINE, '$')!=-1) { if(index($LINE, ' class$')!=-1 or index($LINE, '$eq')!=-1 or index($LINE, '.$')!=-1 or index($LINE, '/$')!=-1 or index($LINE, '$$')!=-1 or index($LINE, '$(')!=-1 or index($LINE, '$:')!=-1 or index($LINE, '$.')!=-1 or index($LINE, '$;')!=-1) { next; } if($LINE=~/ (\w+\$|)\w+\$\w+[\(:]/) { next; } if(not $InParamTable and not $InVarTypeTable) { if(index($LINE, ' $')!=-1) { next; } } $LINE=~s/\$([\> ]|\s*\Z)/$1/g; } my $EndBr = ($LINE eq "}\n" or $LINE eq "}\r\n"); if($EndBr) { $InAnnotations_Class = 1; } if($EndBr or $LINE eq "\n" or $LINE eq "\r\n") { $CurrentMethod = undef; $InCode = 0; $InAnnotations_Method = 0; $InParamTable = 0; $InVarTypeTable = 0; next; } if(index($LINE, '#')!=-1) { if($LINE=~/\A\s*#(\d+)/) { # Constant pool my $CNum = $1; if($LINE=~/\s+([^ ]+?);/) { my $AName = $1; $AName=~s/\AL//; $AName=~s/\$/./g; $AName=~s/\//./g; $ConstantTypeName{$CNum} = $AName; if(defined $AnnotationNum{$CNum}) { # support for Java 7 if($InAnnotations_Class) { $TypeAttr{"Annotations"}{registerType($AName, $LVer)} = 1; } delete($AnnotationNum{$CNum}); } } elsif($LINE=~/=\s*(Utf8|Integer|Long|Float|Double)\s+(.*?)\Z/) { if($1 eq "Utf8") { $ConstantName{$CNum} = "\"".$2."\""; } else { $ConstantName{$CNum} = $2; } } next; } if(index($LINE, ": #")!=-1 and index($LINE, "//")!=-1) { next; } } my $TmplP = undef; # Java 7: templates if(index($LINE, "<")!=-1) { # # ":')!=0) { $InvokedName=~s/#/\$/g; my $ID = undef; if($In::Opt{"Reproducible"}) { $ID = getMd5($InvokedName); } else { $ID = ++$U_ID; } $In::API{$LVer}{"MethodUsed"}{$ID}{"Name"} = $InvokedName; $In::API{$LVer}{"MethodUsed"}{$ID}{"Used"}{$CurrentMethod} = $InvokeType; } } } # elsif($LINE=~/ (getstatic|putstatic) .* \/\/\s*Field\s+(.+?)\s*\Z/) # { # my $UsedFieldName = $2; # $In::API{$LVer}{"FieldUsed"}{$UsedFieldName}{$CurrentMethod} = 1; # } } } elsif(defined $InAnnotations) { if($LINE=~/\A\s*\d+\:\s*#(\d+)/) { if(my $AName = $ConstantTypeName{$1}) { if($InAnnotations_Class) { $TypeAttr{"Annotations"}{registerType($AName, $LVer)} = 1; } elsif($InAnnotations_Method) { $MethodInfo{$LVer}{$MName_Mid{$CurrentMethod}}{"Annotations"}{registerType($AName, $LVer)} = 1; } } else { # suport for Java 7 $AnnotationNum{$1} = 1; } } } } elsif($InParamTable==1 and $LINE=~/\A\s+\d/) { # read parameter names from LocalVariableTable if($CurrentMethod and $LINE=~/\A\s+0\s+\d+\s+\d+\s+(\#?)(\w+)/) { my $Art = $1; my $PName = $2; if(($PName ne "this" or $Art) and $PName=~/[a-z]/i) { if($CurrentMethod) { my $ID = $MName_Mid{$CurrentMethod}; if(defined $MethodInfo{$LVer}{$ID} and defined $MethodInfo{$LVer}{$ID}{"Param"} and defined $MethodInfo{$LVer}{$ID}{"Param"}{$ParamPos} and defined $MethodInfo{$LVer}{$ID}{"Param"}{$ParamPos}{"Type"}) { $MethodInfo{$LVer}{$ID}{"Param"}{$ParamPos}{"Name"} = $PName; $ParamPos++; } } } } } elsif($InVarTypeTable==1 and $LINE=~/\A\s+\d/) { # skip } elsif($CurrentClass and index($LINE, '(')!=-1 and $LINE=~/(\A|\s+)([^\s]+)\s+([^\s]+)\s*\((.*)\)\s*(throws\s*([^\s]+)|)\s*;\s*\Z/) { # attributes of methods and constructors my (%MethodAttr, $ParamsLine, $Exceptions) = (); $InParamTable = 0; # read the first local variable table $InVarTypeTable = 0; $InCode = 0; # read the first code $InAnnotations_Method = 1; $InAnnotations_Class = 0; ($MethodAttr{"Return"}, $MethodAttr{"ShortName"}, $ParamsLine, $Exceptions) = ($2, $3, $4, $6); $MethodAttr{"ShortName"}=~s/#/./g; if($Exceptions) { foreach my $E (split(/,/, $Exceptions)) { $MethodAttr{"Exceptions"}{registerType($E, $LVer)} = 1; } } if($LINE=~/(\A|\s+)(public|protected|private)\s+/) { $MethodAttr{"Access"} = $2; } else { $MethodAttr{"Access"} = "package-private"; } $MethodAttr{"Class"} = registerType($TypeAttr{"Name"}, $LVer); if($MethodAttr{"ShortName"}=~/\A(|(.+)\.)(\Q$CurrentClass\E|\Q$CurrentClass_Short\E)\Z/) { if($2) { $MethodAttr{"Package"} = $2; $CurrentPackage = $MethodAttr{"Package"}; $MethodAttr{"ShortName"} = $CurrentClass; } $MethodAttr{"Constructor"} = 1; delete($MethodAttr{"Return"}); } else { $MethodAttr{"Return"} = registerType($MethodAttr{"Return"}, $LVer); } my @Params = sepParams($ParamsLine, 0, 1); $ParamPos = 0; foreach my $ParamTName (@Params) { %{$MethodAttr{"Param"}{$ParamPos}} = ("Type"=>registerType($ParamTName, $LVer), "Name"=>"p".($ParamPos+1)); $ParamPos++; } $ParamPos = 0; if(not $MethodAttr{"Constructor"}) { # methods if($CurrentPackage) { $MethodAttr{"Package"} = $CurrentPackage; } if($LINE=~/(\A|\s+)abstract\s+/) { $MethodAttr{"Abstract"} = 1; } if($LINE=~/(\A|\s+)final\s+/) { $MethodAttr{"Final"} = 1; } if($LINE=~/(\A|\s+)static\s+/) { $MethodAttr{"Static"} = 1; } if($LINE=~/(\A|\s+)native\s+/) { $MethodAttr{"Native"} = 1; } if($LINE=~/(\A|\s+)synchronized\s+/) { $MethodAttr{"Synchronized"} = 1; } } my $LINE_N = ; if(defined $In::Opt{"Debug"}) { $DContent .= $LINE_N; } # $LINE_N=~s/ \$(\w)/ $1/g; $LINE_N=~s/\$([\> ]|\s*\Z)/$1/g; # read the Signature if(index($LINE_N, ": #")==-1 and $LINE_N=~/(Signature|descriptor):\s*(.+?)\s*\Z/i) { # create run-time unique name ( java/io/PrintStream.println (Ljava/lang/String;)V ) my $SignParams = $2; # Generic classes my $ShortClass = $CurrentClass; $ShortClass=~s/<.*>//g; if($MethodAttr{"Constructor"}) { $CurrentMethod = $ShortClass.".\"\":".$SignParams; } else { $CurrentMethod = $ShortClass.".".$MethodAttr{"ShortName"}.":".$SignParams; } if(my $PackageName = getSFormat($CurrentPackage)) { $CurrentMethod = $PackageName."/".$CurrentMethod; } } else { exitStatus("Error", "internal error - can't read method signature"); } $MethodAttr{"Archive"} = $ArchiveName; if($CurrentMethod) { my $ID = undef; if($In::Opt{"Reproducible"}) { $ID = getMd5($CurrentMethod); } else { $ID = ++$M_ID; } $MName_Mid{$CurrentMethod} = $ID; if(defined $Mid_MName{$ID} and $Mid_MName{$ID} ne $CurrentMethod) { printMsg("ERROR", "md5 collision on \'$ID\', please increase ID length (MD5_LEN in Basic.pm)"); } $Mid_MName{$ID} = $CurrentMethod; $MethodAttr{"Name"} = $CurrentMethod; $MethodInfo{$LVer}{$ID} = \%MethodAttr; } } elsif($CurrentClass and $LINE=~/(\A|\s+)([^\s]+)\s+(\w+);\s*\Z/) { # fields my ($TName, $FName) = ($2, $3); $TypeAttr{"Fields"}{$FName}{"Type"} = registerType($TName, $LVer); if($LINE=~/(\A|\s+)final\s+/) { $TypeAttr{"Fields"}{$FName}{"Final"} = 1; } if($LINE=~/(\A|\s+)static\s+/) { $TypeAttr{"Fields"}{$FName}{"Static"} = 1; } if($LINE=~/(\A|\s+)transient\s+/) { $TypeAttr{"Fields"}{$FName}{"Transient"} = 1; } if($LINE=~/(\A|\s+)volatile\s+/) { $TypeAttr{"Fields"}{$FName}{"Volatile"} = 1; } if($LINE=~/(\A|\s+)(public|protected|private)\s+/) { $TypeAttr{"Fields"}{$FName}{"Access"} = $2; } else { $TypeAttr{"Fields"}{$FName}{"Access"} = "package-private"; } $TypeAttr{"Fields"}{$FName}{"Pos"} = $FieldPos++; my $LINE_NP = ; if(defined $In::Opt{"Debug"}) { $DContent .= $LINE_NP; } # read the Signature if(index($LINE_NP, ": #")==-1 and $LINE_NP=~/(Signature|descriptor):\s*(.+?)\s*\Z/i) { my $FSignature = $2; if(my $PackageName = getSFormat($CurrentPackage)) { $TypeAttr{"Fields"}{$FName}{"Mangled"} = $PackageName."/".$CurrentClass.".".$FName.":".$FSignature; } } $LINE_NP = ; if(defined $In::Opt{"Debug"}) { $DContent .= $LINE_NP; } if($LINE_NP=~/flags:/i) { # flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ANNOTATION $LINE_NP = ; if(defined $In::Opt{"Debug"}) { $DContent .= $LINE_NP; } } else { $LINE = $LINE_NP; $Stay = 1; } # read the Value if($LINE_NP=~/Constant\s*value:\s*([^\s]+)\s(.*?)\s*\Z/i) { # Java 6: Constant value: ... # Java 7: ConstantValue: ... my ($TName, $Value) = ($1, $2); if($Value) { if($Value=~s/Deprecated:\s*true\Z//g) { # deprecated values: ? } $TypeAttr{"Fields"}{$FName}{"Value"} = $Value; } elsif($TName eq "String") { $TypeAttr{"Fields"}{$FName}{"Value"} = "\@EMPTY_STRING\@"; } } else { $LINE = $LINE_NP; $Stay = 1; } } elsif($LINE=~/(\A|\s+)(class|interface)\s+([^\s\{]+)(\s+|\{|\s*\Z)/) { # properties of classes and interfaces if($TypeAttr{"Name"}) { # register previous %{$TypeInfo{$LVer}{registerType($TypeAttr{"Name"}, $LVer)}} = %TypeAttr; } %TypeAttr = ("Type"=>$2, "Name"=>$3); # reset previous class %ConstantTypeName = (); # reset annotations of the class %AnnotationNum = (); # support for Java 7 %ConstantName = (); $InAnnotations_Class = 1; $FieldPos = 0; # reset field position $CurrentMethod = ""; # reset current method $TypeAttr{"Archive"} = $ArchiveName; if($TypeAttr{"Name"}=~/\A(.+)\.([^.]+)\Z/) { $CurrentClass = $2; $TypeAttr{"Package"} = $1; $CurrentPackage = $TypeAttr{"Package"}; } else { $CurrentClass = $TypeAttr{"Name"}; $CurrentPackage = ""; } if($CurrentClass=~s/#/./g) { # javax.swing.text.GlyphView.GlyphPainter <=> GlyphView$GlyphPainter $TypeAttr{"Name"}=~s/#/./g; } $CurrentClass_Short = $CurrentClass; if(index($CurrentClass_Short, "<")!=-1) { $CurrentClass_Short=~s/<.+>//g; } if($LINE=~/(\A|\s+)(public|protected|private)\s+/) { $TypeAttr{"Access"} = $2; } else { $TypeAttr{"Access"} = "package-private"; } if($LINE=~/\s+extends\s+([^\s\{]+)/) { my $Extended = $1; if($TypeAttr{"Type"} eq "class") { if($Extended ne $CurrentPackage.".".$CurrentClass) { $TypeAttr{"SuperClass"} = registerType($Extended, $LVer); } } elsif($TypeAttr{"Type"} eq "interface") { my @Elems = sepParams($Extended, 0, 0); foreach my $SuperInterface (@Elems) { if($SuperInterface ne $CurrentPackage.".".$CurrentClass) { $TypeAttr{"SuperInterface"}{registerType($SuperInterface, $LVer)} = 1; } if($SuperInterface eq "java.lang.annotation.Annotation") { $TypeAttr{"Annotation"} = 1; } } } } if($LINE=~/\s+implements\s+([^\s\{]+)/) { my $Implemented = $1; my @Elems = sepParams($Implemented, 0, 0); foreach my $SuperInterface (@Elems) { $TypeAttr{"SuperInterface"}{registerType($SuperInterface, $LVer)} = 1; } } if($LINE=~/(\A|\s+)abstract\s+/) { $TypeAttr{"Abstract"} = 1; } if($LINE=~/(\A|\s+)final\s+/) { $TypeAttr{"Final"} = 1; } if($LINE=~/(\A|\s+)static\s+/) { $TypeAttr{"Static"} = 1; } if($TmplP) { $TypeAttr{"GenericParam"} = $TmplP; } } elsif(index($LINE, "Deprecated: true")!=-1 or index($LINE, "Deprecated: length")!=-1) { # deprecated method or class if($CurrentMethod) { $MethodInfo{$LVer}{$MName_Mid{$CurrentMethod}}{"Deprecated"} = 1; } elsif($CurrentClass) { $TypeAttr{"Deprecated"} = 1; } } elsif(index($LINE, "RuntimeInvisibleAnnotations")!=-1 or index($LINE, "RuntimeVisibleAnnotations")!=-1) { $InAnnotations = 1; $InCode = 0; } elsif(defined $InAnnotations and index($LINE, "InnerClasses")!=-1) { $InAnnotations = undef; } elsif($CurrentMethod and index($LINE, "default_value")!=-1) { if($LINE=~/default_value:\s*[sISJBFDCZ]#(\d+)/) { if(defined $ConstantName{$1}) { $MethodInfo{$LVer}{$MName_Mid{$CurrentMethod}}{"Default"} = $ConstantName{$1}; } } elsif($LINE=~/default_value:\s*e#(\d+)\.#(\d+)/) { my ($ET, $EV) = ($1, $2); if(defined $ConstantTypeName{$ET} and defined $ConstantName{$EV}) { $ET = $ConstantTypeName{$ET}; $EV = $ConstantName{$EV}; $EV=~s/\"//g; $MethodInfo{$LVer}{$MName_Mid{$CurrentMethod}}{"Default"} = $ET.".".$EV; } } elsif($LINE=~/default_value:\s*\[(.*)\]/) { my $Arr = $1; if($Arr) { my @ArrU = (); foreach my $ArrP (split(/\s*,\s*/, $Arr)) { if($ArrP=~/[sISJBFDCZ]#(\d+)/) { push(@ArrU, $ConstantName{$1}); } } $MethodInfo{$LVer}{$MName_Mid{$CurrentMethod}}{"Default"} = "{".join(",", @ArrU)."}"; } else { $MethodInfo{$LVer}{$MName_Mid{$CurrentMethod}}{"Default"} = "{}"; } } } else { # unparsed } } if($TypeAttr{"Name"}) { # register last %{$TypeInfo{$LVer}{registerType($TypeAttr{"Name"}, $LVer)}} = %TypeAttr; } waitpid($Pid, 0); close(OUT); if(not $NonEmpty) { exitStatus("Error", "internal error in parser"); } if(defined $In::Opt{"Debug"}) { appendFile($DumpFile, $DContent); } } sub registerType($$) { my ($TName, $LVer) = @_; if(not $TName) { return 0; } $TName=~s/#/./g; if($TName_Tid{$LVer}{$TName}) { return $TName_Tid{$LVer}{$TName}; } if(not $TName_Tid{$LVer}{$TName}) { my $ID = undef; if($In::Opt{"Reproducible"}) { $ID = getMd5($TName); } else { $ID = ++$T_ID; } $TName_Tid{$LVer}{$TName} = "$ID"; } my $Tid = $TName_Tid{$LVer}{$TName}; $TypeInfo{$LVer}{$Tid}{"Name"} = $TName; if($TName=~/(.+)\[\]\Z/) { if(my $BaseTypeId = registerType($1, $LVer)) { $TypeInfo{$LVer}{$Tid}{"BaseType"} = $BaseTypeId; $TypeInfo{$LVer}{$Tid}{"Type"} = "array"; } } elsif($TName=~/(.+)\.\.\.\Z/) { if(my $BaseTypeId = registerType($1, $LVer)) { $TypeInfo{$LVer}{$Tid}{"BaseType"} = $BaseTypeId; $TypeInfo{$LVer}{$Tid}{"Type"} = "variable-arity"; } } return $Tid; } sub readClasses_Usage($) { my $Paths = $_[0]; my $JavapCmd = getCmdPath("javap"); if(not $JavapCmd) { exitStatus("Not_Found", "can't find \"javap\" command"); } my $Input = join(" ", @{$Paths}); if($In::Opt{"OS"} ne "windows") { # on unix ensure that the system does not try and interpret the $, by escaping it $Input=~s/\$/\\\$/g; } my $TmpDir = $In::Opt{"Tmp"}; chdir($TmpDir."/".$ExtractCounter); open(CONTENT, "$JavapCmd -c -private $Input 2>\"$TmpDir/warn\" |"); while() { if(/\/\/\s*(Method|InterfaceMethod)\s+(.+)\s*\Z/) { my $M = $2; $In::Opt{"UsedMethods_Client"}{$M} = 1; if($M=~/\A(.*)+\.\w+\:\(/) { my $C = $1; $C=~s/\//./g; $In::Opt{"UsedClasses_Client"}{$C} = 1; } } elsif(/\/\/\s*Field\s+(.+)\s*\Z/) { # my $FieldName = $1; # if(/\s+(putfield|getfield|getstatic|putstatic)\s+/) { # $UsedFields_Client{$FieldName} = $1; # } } elsif(/ ([^\s]+) [^: ]+\(([^()]+)\)/) { my ($Ret, $Params) = ($1, $2); $Ret=~s/\[\]//g; # quals $In::Opt{"UsedClasses_Client"}{$Ret} = 1; foreach my $Param (split(/\s*,\s*/, $Params)) { $Param=~s/\[\]//g; # quals $In::Opt{"UsedClasses_Client"}{$Param} = 1; } } elsif(/ class /) { if(/extends ([^\s{]+)/) { foreach my $Class (split(/\s*,\s*/, $1)) { $In::Opt{"UsedClasses_Client"}{$Class} = 1; } } if(/implements ([^\s{]+)/) { foreach my $Interface (split(/\s*,\s*/, $1)) { $In::Opt{"UsedClasses_Client"}{$Interface} = 1; } } } } close(CONTENT); chdir($In::Opt{"OrigDir"}); } return 1;