Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
J
jadx
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
open-source
jadx
Commits
ed8c6626
Commit
ed8c6626
authored
Jul 06, 2019
by
Skylot
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: add generic types propagation (#695)
parent
850df18d
Hide whitespace changes
Inline
Side-by-side
Showing
34 changed files
with
815 additions
and
177 deletions
+815
-177
build.gradle
jadx-core/build.gradle
+2
-3
android-29-clst.jar
jadx-core/clsp-data/android-29-clst.jar
+0
-0
android-29-res.jar
jadx-core/clsp-data/android-29-res.jar
+0
-0
ClsSet.java
jadx-core/src/main/java/jadx/core/clsp/ClsSet.java
+124
-91
ClspGraph.java
jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java
+18
-2
ConvertToClsSet.java
jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java
+1
-1
NClass.java
jadx-core/src/main/java/jadx/core/clsp/NClass.java
+43
-10
NMethod.java
jadx-core/src/main/java/jadx/core/clsp/NMethod.java
+59
-6
ClassGen.java
jadx-core/src/main/java/jadx/core/codegen/ClassGen.java
+7
-8
MethodGen.java
jadx-core/src/main/java/jadx/core/codegen/MethodGen.java
+1
-1
ArgType.java
...rc/main/java/jadx/core/dex/instructions/args/ArgType.java
+18
-0
InsnArg.java
...rc/main/java/jadx/core/dex/instructions/args/InsnArg.java
+13
-0
RegisterArg.java
...ain/java/jadx/core/dex/instructions/args/RegisterArg.java
+6
-0
ClassNode.java
jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
+4
-4
GenericInfo.java
jadx-core/src/main/java/jadx/core/dex/nodes/GenericInfo.java
+46
-0
MethodNode.java
jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java
+5
-6
RootNode.java
jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
+28
-0
SignatureParser.java
...main/java/jadx/core/dex/nodes/parser/SignatureParser.java
+9
-9
PrepareForCodeGen.java
...c/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java
+2
-2
LoopRegionVisitor.java
...ava/jadx/core/dex/visitors/regions/LoopRegionVisitor.java
+1
-1
ProcessVariables.java
...core/dex/visitors/regions/variables/ProcessVariables.java
+19
-11
ITypeBound.java
...java/jadx/core/dex/visitors/typeinference/ITypeBound.java
+4
-0
ITypeBoundDynamic.java
...dx/core/dex/visitors/typeinference/ITypeBoundDynamic.java
+16
-0
TypeBoundInvokeAssign.java
...ore/dex/visitors/typeinference/TypeBoundInvokeAssign.java
+77
-0
TypeInferenceVisitor.java
...core/dex/visitors/typeinference/TypeInferenceVisitor.java
+28
-1
TypeUpdate.java
...java/jadx/core/dex/visitors/typeinference/TypeUpdate.java
+97
-6
TypeUpdateEntry.java
...jadx/core/dex/visitors/typeinference/TypeUpdateEntry.java
+0
-4
TypeUpdateInfo.java
.../jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java
+16
-0
IntegrationTest.java
jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java
+1
-1
SignatureParserTest.java
.../test/java/jadx/tests/functional/SignatureParserTest.java
+8
-9
TestNestedLoops2.java
...t/java/jadx/tests/integration/loops/TestNestedLoops2.java
+1
-1
TestGenerics2.java
...test/java/jadx/tests/integration/types/TestGenerics2.java
+36
-0
TestGenerics3.java
...test/java/jadx/tests/integration/types/TestGenerics3.java
+57
-0
TestGenerics2.smali
jadx-core/src/test/smali/types/TestGenerics2.smali
+68
-0
No files found.
jadx-core/build.gradle
View file @
ed8c6626
ext
.
jadxClasspath
=
'clsp-data/android-5.1.jar'
dependencies
{
runtime
files
(
jadxClasspath
)
runtime
files
(
'clsp-data/android-29-clst.jar'
)
runtime
files
(
'clsp-data/android-29-res.jar'
)
compile
files
(
'lib/dx-1.16.jar'
)
// TODO: dx don't support java version > 9 (53)
...
...
jadx-core/clsp-data/android-29-clst.jar
0 → 100644
View file @
ed8c6626
File added
jadx-core/clsp-data/android-
5.1
.jar
→
jadx-core/clsp-data/android-
29-res
.jar
View file @
ed8c6626
No preview for this file type
jadx-core/src/main/java/jadx/core/clsp/ClsSet.java
View file @
ed8c6626
...
...
@@ -12,6 +12,7 @@ import java.nio.file.Files;
import
java.nio.file.Path
;
import
java.nio.file.StandardCopyOption
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
...
...
@@ -22,9 +23,11 @@ import java.util.zip.ZipOutputStream;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
jadx.core.dex.info.AccessInfo
;
import
jadx.core.dex.instructions.args.ArgType
;
import
jadx.core.dex.instructions.args.RegisterArg
;
import
jadx.core.dex.nodes.ClassNode
;
import
jadx.core.dex.nodes.GenericInfo
;
import
jadx.core.dex.nodes.MethodNode
;
import
jadx.core.dex.nodes.RootNode
;
import
jadx.core.utils.exceptions.DecodeException
;
...
...
@@ -55,7 +58,16 @@ public class ClsSet {
private
NClass
[]
classes
;
public
void
load
(
RootNode
root
)
{
public
void
loadFromClstFile
()
throws
IOException
,
DecodeException
{
try
(
InputStream
input
=
getClass
().
getResourceAsStream
(
CLST_FILENAME
))
{
if
(
input
==
null
)
{
throw
new
JadxRuntimeException
(
"Can't load classpath file: "
+
CLST_FILENAME
);
}
load
(
input
);
}
}
public
void
loadFrom
(
RootNode
root
)
{
List
<
ClassNode
>
list
=
root
.
getClasses
(
true
);
Map
<
String
,
NClass
>
names
=
new
HashMap
<>(
list
.
size
());
int
k
=
0
;
...
...
@@ -68,7 +80,8 @@ public class ClsSet {
throw
new
JadxRuntimeException
(
"Duplicate class: "
+
clsRawName
);
}
k
++;
nClass
.
setMethods
(
loadMethods
(
cls
,
nClass
));
nClass
.
setGenerics
(
cls
.
getGenerics
());
nClass
.
setMethods
(
getMethodsDetails
(
cls
));
}
else
{
names
.
put
(
clsRawName
,
null
);
}
...
...
@@ -88,45 +101,43 @@ public class ClsSet {
}
}
private
NMethod
[]
loadMethods
(
ClassNode
cls
,
NClass
nClas
s
)
{
private
List
<
NMethod
>
getMethodsDetails
(
ClassNode
cl
s
)
{
List
<
NMethod
>
methods
=
new
ArrayList
<>();
for
(
MethodNode
m
:
cls
.
getMethods
())
{
if
(!
m
.
getAccessFlags
().
isPublic
()
&&
!
m
.
getAccessFlags
()
.
isProtected
())
{
continue
;
AccessInfo
accessFlags
=
m
.
getAccessFlags
();
if
(
accessFlags
.
isPublic
()
||
accessFlags
.
isProtected
())
{
processMethodDetails
(
methods
,
m
,
accessFlags
)
;
}
}
return
methods
;
}
List
<
ArgType
>
args
=
new
ArrayList
<>();
boolean
genericArg
=
false
;
for
(
RegisterArg
r
:
m
.
getArguments
(
false
))
{
ArgType
argType
=
r
.
getType
();
private
void
processMethodDetails
(
List
<
NMethod
>
methods
,
MethodNode
mth
,
AccessInfo
accessFlags
)
{
List
<
RegisterArg
>
args
=
mth
.
getArguments
(
false
);
boolean
genericArg
=
false
;
ArgType
[]
genericArgs
;
if
(
args
.
isEmpty
())
{
genericArgs
=
null
;
}
else
{
int
argsCount
=
args
.
size
();
genericArgs
=
new
ArgType
[
argsCount
];
for
(
int
i
=
0
;
i
<
argsCount
;
i
++)
{
RegisterArg
arg
=
args
.
get
(
i
);
ArgType
argType
=
arg
.
getType
();
if
(
argType
.
isGeneric
()
||
argType
.
isGenericType
())
{
args
.
add
(
argType
)
;
genericArgs
[
i
]
=
argType
;
genericArg
=
true
;
}
else
{
args
.
add
(
null
);
}
}
ArgType
retType
=
m
.
getReturnType
();
if
(!
retType
.
isGeneric
()
&&
!
retType
.
isGenericType
())
{
retType
=
null
;
}
boolean
varArgs
=
m
.
getAccessFlags
().
isVarArgs
();
if
(
genericArg
||
retType
!=
null
||
varArgs
)
{
methods
.
add
(
new
NMethod
(
m
.
getMethodInfo
().
getShortId
(),
args
.
isEmpty
()
?
new
ArgType
[
0
]
:
args
.
toArray
(
new
ArgType
[
args
.
size
()]),
retType
,
varArgs
));
}
}
return
methods
.
toArray
(
new
NMethod
[
methods
.
size
()]);
ArgType
retType
=
mth
.
getReturnType
();
if
(!
retType
.
isGeneric
()
&&
!
retType
.
isGenericType
())
{
retType
=
null
;
}
boolean
varArgs
=
accessFlags
.
isVarArgs
();
if
(
genericArg
||
retType
!=
null
||
varArgs
)
{
methods
.
add
(
new
NMethod
(
mth
.
getMethodInfo
().
getShortId
(),
genericArgs
,
retType
,
varArgs
));
}
}
public
static
NClass
[]
makeParentsArray
(
ClassNode
cls
,
Map
<
String
,
NClass
>
names
)
{
...
...
@@ -207,42 +218,58 @@ public class ClsSet {
for
(
NClass
parent
:
parents
)
{
out
.
writeInt
(
parent
.
getId
());
}
NMethod
[]
methods
=
cls
.
getMethods
();
out
.
writeByte
(
methods
.
length
);
writeGenerics
(
out
,
cls
,
names
);
List
<
NMethod
>
methods
=
cls
.
getMethodsList
();
out
.
writeByte
(
methods
.
size
());
for
(
NMethod
method
:
methods
)
{
writeMethod
(
out
,
method
,
names
);
}
}
}
private
static
void
writeMethod
(
DataOutputStream
out
,
NMethod
method
,
Map
<
String
,
NClass
>
names
)
throws
IOException
{
int
argCount
=
0
;
ArgType
[]
argTypes
=
method
.
getArgType
();
for
(
ArgType
arg
:
argTypes
)
{
if
(
arg
!=
null
)
{
argCount
++;
private
static
void
writeGenerics
(
DataOutputStream
out
,
NClass
cls
,
Map
<
String
,
NClass
>
names
)
throws
IOException
{
List
<
GenericInfo
>
genericsList
=
cls
.
getGenerics
();
out
.
writeByte
(
genericsList
.
size
());
for
(
GenericInfo
genericInfo
:
genericsList
)
{
writeArgType
(
out
,
genericInfo
.
getGenericType
(),
names
);
List
<
ArgType
>
extendsList
=
genericInfo
.
getExtendsList
();
out
.
writeByte
(
extendsList
.
size
());
for
(
ArgType
type
:
extendsList
)
{
writeArgType
(
out
,
type
,
names
);
}
}
}
private
static
void
writeMethod
(
DataOutputStream
out
,
NMethod
method
,
Map
<
String
,
NClass
>
names
)
throws
IOException
{
writeLongString
(
out
,
method
.
getShortId
());
out
.
writeByte
(
argCount
);
// last argument first
for
(
int
i
=
argTypes
.
length
-
1
;
i
>=
0
;
i
--)
{
ArgType
argType
=
argTypes
[
i
];
if
(
argType
!=
null
)
{
out
.
writeByte
(
i
);
writeArgType
(
out
,
argType
,
names
);
ArgType
[]
argTypes
=
method
.
getGenericArgs
();
if
(
argTypes
==
null
)
{
out
.
writeByte
(
0
);
}
else
{
int
argCount
=
0
;
for
(
ArgType
arg
:
argTypes
)
{
if
(
arg
!=
null
)
{
argCount
++;
}
}
out
.
writeByte
(
argCount
);
// last argument first
for
(
int
i
=
argTypes
.
length
-
1
;
i
>=
0
;
i
--)
{
ArgType
argType
=
argTypes
[
i
];
if
(
argType
!=
null
)
{
out
.
writeByte
(
i
);
writeArgType
(
out
,
argType
,
names
);
}
}
}
if
(
method
.
getReturnType
()
!=
null
)
{
if
(
method
.
getReturnType
()
==
null
)
{
out
.
writeBoolean
(
false
);
}
else
{
out
.
writeBoolean
(
true
);
writeArgType
(
out
,
method
.
getReturnType
(),
names
);
}
else
{
out
.
writeBoolean
(
false
);
}
out
.
writeBoolean
(
method
.
isVarArgs
());
}
...
...
@@ -283,16 +310,7 @@ public class ClsSet {
}
}
public
void
load
()
throws
IOException
,
DecodeException
{
try
(
InputStream
input
=
getClass
().
getResourceAsStream
(
CLST_FILENAME
))
{
if
(
input
==
null
)
{
throw
new
JadxRuntimeException
(
"Can't load classpath file: "
+
CLST_FILENAME
);
}
load
(
input
);
}
}
public
void
load
(
File
input
)
throws
IOException
,
DecodeException
{
private
void
load
(
File
input
)
throws
IOException
,
DecodeException
{
String
name
=
input
.
getName
();
try
(
InputStream
inputStream
=
new
FileInputStream
(
input
))
{
if
(
name
.
endsWith
(
CLST_EXTENSION
))
{
...
...
@@ -313,7 +331,7 @@ public class ClsSet {
}
}
p
ublic
void
load
(
InputStream
input
)
throws
IOException
,
DecodeException
{
p
rivate
void
load
(
InputStream
input
)
throws
IOException
,
DecodeException
{
try
(
DataInputStream
in
=
new
DataInputStream
(
input
))
{
byte
[]
header
=
new
byte
[
JADX_CLS_SET_HEADER
.
length
()];
int
readHeaderLength
=
in
.
read
(
header
);
...
...
@@ -335,16 +353,44 @@ public class ClsSet {
for
(
int
j
=
0
;
j
<
pCount
;
j
++)
{
parents
[
j
]
=
classes
[
in
.
readInt
()];
}
classes
[
i
].
setParents
(
parents
);
NClass
nClass
=
classes
[
i
];
nClass
.
setParents
(
parents
);
nClass
.
setGenerics
(
readGenerics
(
in
));
nClass
.
setMethods
(
readClsMethods
(
in
));
}
}
}
int
mCount
=
in
.
readByte
();
NMethod
[]
methods
=
new
NMethod
[
mCount
];
for
(
int
j
=
0
;
j
<
mCount
;
j
++)
{
methods
[
j
]
=
readMethod
(
in
);
private
List
<
GenericInfo
>
readGenerics
(
DataInputStream
in
)
throws
IOException
{
int
count
=
in
.
readByte
();
if
(
count
==
0
)
{
return
Collections
.
emptyList
();
}
List
<
GenericInfo
>
list
=
new
ArrayList
<>(
count
);
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
ArgType
genericType
=
readArgType
(
in
);
List
<
ArgType
>
extendsList
;
byte
extCount
=
in
.
readByte
();
if
(
extCount
==
0
)
{
extendsList
=
Collections
.
emptyList
();
}
else
{
extendsList
=
new
ArrayList
<>(
extCount
);
for
(
int
j
=
0
;
j
<
extCount
;
j
++)
{
extendsList
.
add
(
readArgType
(
in
));
}
classes
[
i
].
setMethods
(
methods
);
}
list
.
add
(
new
GenericInfo
(
genericType
,
extendsList
));
}
return
list
;
}
private
List
<
NMethod
>
readClsMethods
(
DataInputStream
in
)
throws
IOException
{
int
mCount
=
in
.
readByte
();
List
<
NMethod
>
methods
=
new
ArrayList
<>(
mCount
);
for
(
int
j
=
0
;
j
<
mCount
;
j
++)
{
methods
.
add
(
readMethod
(
in
));
}
return
methods
;
}
private
NMethod
readMethod
(
DataInputStream
in
)
throws
IOException
{
...
...
@@ -372,6 +418,7 @@ public class ClsSet {
return
bounds
==
0
?
ArgType
.
wildcard
()
:
ArgType
.
wildcard
(
readArgType
(
in
),
bounds
);
case
GENERIC:
String
obj
=
classes
[
in
.
readInt
()].
getName
();
int
typeLength
=
in
.
readByte
();
...
...
@@ -385,34 +432,20 @@ public class ClsSet {
}
}
return
ArgType
.
generic
(
obj
,
generics
);
case
GENERIC_TYPE:
return
ArgType
.
genericType
(
readString
(
in
));
case
OBJECT:
return
ArgType
.
object
(
classes
[
in
.
readInt
()].
getName
());
case
ARRAY:
return
ArgType
.
array
(
readArgType
(
in
));
case
PRIMITIVE:
int
shortName
=
in
.
readByte
();
switch
(
shortName
)
{
case
'Z'
:
return
ArgType
.
BOOLEAN
;
case
'C'
:
return
ArgType
.
CHAR
;
case
'B'
:
return
ArgType
.
BYTE
;
case
'S'
:
return
ArgType
.
SHORT
;
case
'I'
:
return
ArgType
.
INT
;
case
'F'
:
return
ArgType
.
FLOAT
;
case
'J'
:
return
ArgType
.
LONG
;
case
'D'
:
return
ArgType
.
DOUBLE
;
default
:
return
ArgType
.
VOID
;
}
char
shortName
=
(
char
)
in
.
readByte
();
return
ArgType
.
parse
(
shortName
);
default
:
throw
new
JadxRuntimeException
(
"Unsupported Arg Type: "
+
ordinal
);
}
...
...
jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java
View file @
ed8c6626
...
...
@@ -10,15 +10,18 @@ import java.util.Map;
import
java.util.Set
;
import
java.util.WeakHashMap
;
import
org.jetbrains.annotations.Nullable
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
jadx.core.dex.info.MethodInfo
;
import
jadx.core.dex.instructions.args.ArgType
;
import
jadx.core.dex.nodes.ClassNode
;
import
jadx.core.utils.exceptions.DecodeException
;
import
jadx.core.utils.exceptions.JadxRuntimeException
;
/**
* Classes hierarchy graph
* Classes hierarchy graph
with methods additional info
*/
public
class
ClspGraph
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
ClspGraph
.
class
);
...
...
@@ -30,7 +33,7 @@ public class ClspGraph {
public
void
load
()
throws
IOException
,
DecodeException
{
ClsSet
set
=
new
ClsSet
();
set
.
load
();
set
.
load
FromClstFile
();
addClasspath
(
set
);
}
...
...
@@ -62,6 +65,19 @@ public class ClspGraph {
return
nameMap
.
containsKey
(
fullName
);
}
public
NClass
getClsDetails
(
ArgType
type
)
{
return
nameMap
.
get
(
type
.
getObject
());
}
@Nullable
public
NMethod
getMethodDetails
(
MethodInfo
methodInfo
)
{
NClass
cls
=
nameMap
.
get
(
methodInfo
.
getDeclClass
().
getRawName
());
if
(
cls
==
null
)
{
return
null
;
}
return
cls
.
getMethodsMap
().
get
(
methodInfo
.
getShortId
());
}
private
NClass
addClass
(
ClassNode
cls
)
{
String
rawName
=
cls
.
getRawName
();
NClass
nClass
=
new
NClass
(
rawName
,
-
1
);
...
...
jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java
View file @
ed8c6626
...
...
@@ -49,7 +49,7 @@ public class ConvertToClsSet {
root
.
load
(
inputFiles
);
ClsSet
set
=
new
ClsSet
();
set
.
load
(
root
);
set
.
load
From
(
root
);
set
.
save
(
output
);
LOG
.
info
(
"Output: {}"
,
output
);
LOG
.
info
(
"done"
);
...
...
jadx-core/src/main/java/jadx/core/clsp/NClass.java
View file @
ed8c6626
package
jadx
.
core
.
clsp
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Objects
;
import
jadx.core.dex.nodes.GenericInfo
;
/**
* Class node in classpath graph
*/
public
class
NClass
{
private
final
String
name
;
private
NClass
[]
parents
;
private
NMethod
[]
methods
;
private
final
int
id
;
private
NClass
[]
parents
;
private
Map
<
String
,
NMethod
>
methodsMap
=
Collections
.
emptyMap
();
private
List
<
GenericInfo
>
generics
=
Collections
.
emptyList
();
public
NClass
(
String
name
,
int
id
)
{
this
.
name
=
name
;
...
...
@@ -31,6 +41,37 @@ public class NClass {
this
.
parents
=
parents
;
}
public
Map
<
String
,
NMethod
>
getMethodsMap
()
{
return
methodsMap
;
}
public
List
<
NMethod
>
getMethodsList
()
{
List
<
NMethod
>
list
=
new
ArrayList
<>(
methodsMap
.
size
());
list
.
addAll
(
methodsMap
.
values
());
Collections
.
sort
(
list
);
return
list
;
}
public
void
setMethodsMap
(
Map
<
String
,
NMethod
>
methodsMap
)
{
this
.
methodsMap
=
Objects
.
requireNonNull
(
methodsMap
);
}
public
void
setMethods
(
List
<
NMethod
>
methods
)
{
Map
<
String
,
NMethod
>
map
=
new
HashMap
<>(
methods
.
size
());
for
(
NMethod
mth
:
methods
)
{
map
.
put
(
mth
.
getShortId
(),
mth
);
}
setMethodsMap
(
map
);
}
public
List
<
GenericInfo
>
getGenerics
()
{
return
generics
;
}
public
void
setGenerics
(
List
<
GenericInfo
>
generics
)
{
this
.
generics
=
generics
;
}
@Override
public
int
hashCode
()
{
return
name
.
hashCode
();
...
...
@@ -52,12 +93,4 @@ public class NClass {
public
String
toString
()
{
return
name
;
}
public
void
setMethods
(
NMethod
[]
methods
)
{
this
.
methods
=
methods
;
}
public
NMethod
[]
getMethods
()
{
return
methods
;
}
}
jadx-core/src/main/java/jadx/core/clsp/NMethod.java
View file @
ed8c6626
package
jadx
.
core
.
clsp
;
import
org.jetbrains.annotations.NotNull
;
import
org.jetbrains.annotations.Nullable
;
import
jadx.core.dex.instructions.args.ArgType
;
/**
* Generic method node in classpath graph.
*/
public
class
NMethod
{
public
class
NMethod
implements
Comparable
<
NMethod
>
{
private
final
String
shortId
;
private
final
ArgType
[]
argType
;
/**
* Array contains only generic args, others set to 'null', size can be less than total args count
*/
@Nullable
private
final
ArgType
[]
genericArgs
;
@Nullable
private
final
ArgType
retType
;
private
final
boolean
varArgs
;
public
NMethod
(
String
shortId
,
ArgType
[]
argType
,
ArgType
retType
,
boolean
varArgs
)
{
public
NMethod
(
String
shortId
,
@Nullable
ArgType
[]
genericArgs
,
@Nullable
ArgType
retType
,
boolean
varArgs
)
{
this
.
shortId
=
shortId
;
this
.
argType
=
argType
;
this
.
genericArgs
=
genericArgs
;
this
.
retType
=
retType
;
this
.
varArgs
=
varArgs
;
}
...
...
@@ -23,10 +34,21 @@ public class NMethod {
return
shortId
;
}
public
ArgType
[]
getArgType
()
{
return
argType
;
@Nullable
public
ArgType
[]
getGenericArgs
()
{
return
genericArgs
;
}
@Nullable
public
ArgType
getGenericArg
(
int
i
)
{
ArgType
[]
args
=
this
.
genericArgs
;
if
(
args
!=
null
&&
i
<
args
.
length
)
{
return
args
[
i
];
}
return
null
;
}
@Nullable
public
ArgType
getReturnType
()
{
return
retType
;
}
...
...
@@ -34,4 +56,35 @@ public class NMethod {
public
boolean
isVarArgs
()
{
return
varArgs
;
}
@Override
public
boolean
equals
(
Object
o
)
{
if
(
this
==
o
)
{
return
true
;
}
if
(!(
o
instanceof
NMethod
))
{
return
false
;
}
NMethod
other
=
(
NMethod
)
o
;
return
shortId
.
equals
(
other
.
shortId
);
}
@Override
public
int
hashCode
()
{
return
shortId
.
hashCode
();
}
@Override
public
int
compareTo
(
@NotNull
NMethod
other
)
{
return
this
.
shortId
.
compareTo
(
other
.
shortId
);
}
@Override
public
String
toString
()
{
return
"NMethod{'"
+
shortId
+
'\''
+
", argTypes="
+
genericArgs
+
", retType="
+
retType
+
", varArgs="
+
varArgs
+
'}'
;
}
}
jadx-core/src/main/java/jadx/core/codegen/ClassGen.java
View file @
ed8c6626
...
...
@@ -5,8 +5,6 @@ import java.util.Comparator;
import
java.util.HashSet
;
import
java.util.Iterator
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Map.Entry
;
import
java.util.Set
;
import
com.android.dx.rop.code.AccessFlags
;
...
...
@@ -27,6 +25,7 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
import
jadx.core.dex.nodes.ClassNode
;
import
jadx.core.dex.nodes.DexNode
;
import
jadx.core.dex.nodes.FieldNode
;
import
jadx.core.dex.nodes.GenericInfo
;
import
jadx.core.dex.nodes.InsnNode
;
import
jadx.core.dex.nodes.MethodNode
;
import
jadx.core.dex.nodes.parser.FieldInitAttr
;
...
...
@@ -143,7 +142,7 @@ public class ClassGen {
clsCode
.
attachDefinition
(
cls
);
clsCode
.
add
(
cls
.
getClassInfo
().
getAliasShortName
());
addGenericMap
(
clsCode
,
cls
.
getGeneric
Map
(),
true
);
addGenericMap
(
clsCode
,
cls
.
getGeneric
s
(),
true
);
clsCode
.
add
(
' '
);
ArgType
sup
=
cls
.
getSuperClass
();
...
...
@@ -174,23 +173,23 @@ public class ClassGen {
}
}
public
boolean
addGenericMap
(
CodeWriter
code
,
Map
<
ArgType
,
List
<
ArgType
>>
gmap
,
boolean
classDeclaration
)
{
if
(
g
map
==
null
||
gmap
.
isEmpty
())
{
public
boolean
addGenericMap
(
CodeWriter
code
,
List
<
GenericInfo
>
generics
,
boolean
classDeclaration
)
{
if
(
g
enerics
==
null
||
generics
.
isEmpty
())
{
return
false
;
}
code
.
add
(
'<'
);
int
i
=
0
;
for
(
Entry
<
ArgType
,
List
<
ArgType
>>
e
:
gmap
.
entrySet
())
{
ArgType
type
=
e
.
getKey
();
List
<
ArgType
>
list
=
e
.
getValue
();
for
(
GenericInfo
genericInfo
:
generics
)
{
if
(
i
!=
0
)
{
code
.
add
(
", "
);
}
ArgType
type
=
genericInfo
.
getGenericType
();
if
(
type
.
isGenericType
())
{
code
.
add
(
type
.
getObject
());
}
else
{
useClass
(
code
,
type
);
}
List
<
ArgType
>
list
=
genericInfo
.
getExtendsList
();
if
(
list
!=
null
&&
!
list
.
isEmpty
())
{
code
.
add
(
" extends "
);
for
(
Iterator
<
ArgType
>
it
=
list
.
iterator
();
it
.
hasNext
();)
{
...
...
jadx-core/src/main/java/jadx/core/codegen/MethodGen.java
View file @
ed8c6626
...
...
@@ -99,7 +99,7 @@ public class MethodGen {
code
.
add
(
mth
.
isVirtual
()
?
"/* virtual */ "
:
"/* direct */ "
);
}
if
(
classGen
.
addGenericMap
(
code
,
mth
.
getGeneric
Map
(),
false
))
{
if
(
classGen
.
addGenericMap
(
code
,
mth
.
getGeneric
s
(),
false
))
{
code
.
add
(
' '
);
}
if
(
ai
.
isConstructor
())
{
...
...
jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java
View file @
ed8c6626
...
...
@@ -622,6 +622,24 @@ public abstract class ArgType {
return
1
;
}
public
boolean
containsGenericType
()
{
if
(
isGenericType
())
{
return
true
;
}
if
(
isGeneric
())
{
ArgType
[]
genericTypes
=
getGenericTypes
();
if
(
genericTypes
!=
null
)
{
for
(
ArgType
genericType
:
genericTypes
)
{
if
(
genericType
.
containsGenericType
())
{
return
true
;
}
}
}
return
false
;
}
return
false
;
}
public
static
ArgType
tryToResolveClassAlias
(
DexNode
dex
,
ArgType
type
)
{
if
(!
type
.
isObject
()
||
type
.
isGenericType
())
{
return
type
;
...
...
jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java
View file @
ed8c6626
...
...
@@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory;
import
com.android.dx.io.instructions.DecodedInstruction
;
import
jadx.core.dex.attributes.AFlag
;
import
jadx.core.dex.instructions.InsnType
;
import
jadx.core.dex.nodes.InsnNode
;
import
jadx.core.utils.InsnUtils
;
...
...
@@ -109,6 +110,18 @@ public abstract class InsnArg extends Typed {
if
(
i
==
-
1
)
{
return
null
;
}
if
(
insn
.
getType
()
==
InsnType
.
MOVE
&&
this
.
isRegister
())
{
// preserve variable name for move insn (needed in `for-each` loop for iteration variable)
String
name
=
((
RegisterArg
)
this
).
getName
();
if
(
name
!=
null
)
{
InsnArg
arg
=
insn
.
getArg
(
0
);
if
(
arg
.
isRegister
())
{
((
RegisterArg
)
arg
).
setNameIfUnknown
(
name
);
}
else
if
(
arg
.
isInsnWrap
())
{
((
InsnWrapArg
)
arg
).
getWrapInsn
().
getResult
().
setNameIfUnknown
(
name
);
}
}
}
insn
.
add
(
AFlag
.
WRAPPED
);
InsnArg
arg
=
wrapArg
(
insn
);
parent
.
setArg
(
i
,
arg
);
...
...
jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java
View file @
ed8c6626
...
...
@@ -97,6 +97,12 @@ public class RegisterArg extends InsnArg implements Named {
}
}
public
void
setNameIfUnknown
(
String
name
)
{
if
(
getName
()
==
null
)
{
setName
(
name
);
}
}
public
boolean
isNameEquals
(
InsnArg
arg
)
{
String
n
=
getName
();
if
(
n
==
null
||
!(
arg
instanceof
Named
))
{
...
...
jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
View file @
ed8c6626
...
...
@@ -46,7 +46,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
private
AccessInfo
accessFlags
;
private
ArgType
superClass
;
private
List
<
ArgType
>
interfaces
;
private
Map
<
ArgType
,
List
<
ArgType
>>
genericMap
;
private
List
<
GenericInfo
>
generics
=
Collections
.
emptyList
()
;
private
final
List
<
MethodNode
>
methods
;
private
final
List
<
FieldNode
>
fields
;
...
...
@@ -180,7 +180,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
}
try
{
// parse class generic map
generic
Map
=
sp
.
consumeGenericMap
();
generic
s
=
sp
.
consumeGenericMap
();
// parse super class signature
superClass
=
sp
.
consumeType
();
// parse interfaces signatures
...
...
@@ -283,8 +283,8 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
return
interfaces
;
}
public
Map
<
ArgType
,
List
<
ArgType
>>
getGenericMap
()
{
return
generic
Map
;
public
List
<
GenericInfo
>
getGenerics
()
{
return
generic
s
;
}
public
List
<
MethodNode
>
getMethods
()
{
...
...
jadx-core/src/main/java/jadx/core/dex/nodes/GenericInfo.java
0 → 100644
View file @
ed8c6626
package
jadx
.
core
.
dex
.
nodes
;
import
java.util.List
;
import
jadx.core.dex.instructions.args.ArgType
;
public
class
GenericInfo
{
private
final
ArgType
genericType
;
private
final
List
<
ArgType
>
extendsList
;
public
GenericInfo
(
ArgType
genericType
,
List
<
ArgType
>
extendsList
)
{
this
.
genericType
=
genericType
;
this
.
extendsList
=
extendsList
;
}
public
ArgType
getGenericType
()
{
return
genericType
;
}
public
List
<
ArgType
>
getExtendsList
()
{
return
extendsList
;
}
@Override
public
boolean
equals
(
Object
o
)
{
if
(
this
==
o
)
{
return
true
;
}
if
(
o
==
null
||
getClass
()
!=
o
.
getClass
())
{
return
false
;
}
GenericInfo
other
=
(
GenericInfo
)
o
;
return
genericType
.
equals
(
other
.
genericType
)
&&
extendsList
.
equals
(
other
.
extendsList
);
}
@Override
public
int
hashCode
()
{
return
31
*
genericType
.
hashCode
()
+
extendsList
.
hashCode
();
}
@Override
public
String
toString
()
{
return
"GenericInfo{"
+
genericType
+
" extends: "
+
extendsList
+
'}'
;
}
}
jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java
View file @
ed8c6626
...
...
@@ -4,7 +4,6 @@ import java.util.ArrayList;
import
java.util.Collections
;
import
java.util.HashSet
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Set
;
import
org.jetbrains.annotations.NotNull
;
...
...
@@ -67,7 +66,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
private
RegisterArg
thisArg
;
private
List
<
RegisterArg
>
argsList
;
private
List
<
SSAVar
>
sVars
;
private
Map
<
ArgType
,
List
<
ArgType
>>
genericMap
;
private
List
<
GenericInfo
>
generics
;
private
List
<
BlockNode
>
blocks
;
private
BlockNode
enterBlock
;
...
...
@@ -95,7 +94,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
// don't unload retType and argsList, will be used in jadx-gui after class unload
thisArg
=
null
;
sVars
=
Collections
.
emptyList
();
generic
Map
=
null
;
generic
s
=
Collections
.
emptyList
()
;
instructions
=
null
;
blocks
=
null
;
enterBlock
=
null
;
...
...
@@ -174,7 +173,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
return
false
;
}
try
{
generic
Map
=
sp
.
consumeGenericMap
();
generic
s
=
sp
.
consumeGenericMap
();
List
<
ArgType
>
argsTypes
=
sp
.
consumeMethodArgs
();
retType
=
sp
.
consumeType
();
...
...
@@ -261,8 +260,8 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
return
retType
;
}
public
Map
<
ArgType
,
List
<
ArgType
>>
getGenericMap
()
{
return
generic
Map
;
public
List
<
GenericInfo
>
getGenerics
()
{
return
generic
s
;
}
private
static
void
initTryCatches
(
MethodNode
mth
,
Code
mthCode
,
InsnNode
[]
insnByOffset
)
{
...
...
jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
View file @
ed8c6626
...
...
@@ -13,16 +13,19 @@ import jadx.api.ResourceFile;
import
jadx.api.ResourceType
;
import
jadx.api.ResourcesLoader
;
import
jadx.core.clsp.ClspGraph
;
import
jadx.core.clsp.NMethod
;
import
jadx.core.dex.info.ClassInfo
;
import
jadx.core.dex.info.ConstStorage
;
import
jadx.core.dex.info.FieldInfo
;
import
jadx.core.dex.info.InfoStorage
;
import
jadx.core.dex.info.MethodInfo
;
import
jadx.core.dex.instructions.args.ArgType
;
import
jadx.core.dex.visitors.typeinference.TypeUpdate
;
import
jadx.core.utils.CacheStorage
;
import
jadx.core.utils.ErrorsCounter
;
import
jadx.core.utils.StringUtils
;
import
jadx.core.utils.android.AndroidResourcesUtils
;
import
jadx.core.utils.exceptions.DecodeException
;
import
jadx.core.utils.exceptions.JadxRuntimeException
;
import
jadx.core.utils.files.DexFile
;
import
jadx.core.utils.files.InputFile
;
...
...
@@ -191,6 +194,31 @@ public class RootNode {
return
cls
.
dex
().
deepResolveField
(
cls
,
field
);
}
@Nullable
public
ArgType
getMethodGenericReturnType
(
MethodInfo
callMth
)
{
MethodNode
methodNode
=
deepResolveMethod
(
callMth
);
if
(
methodNode
!=
null
)
{
ArgType
returnType
=
methodNode
.
getReturnType
();
if
(
returnType
==
null
)
{
try
{
methodNode
.
load
();
returnType
=
methodNode
.
getReturnType
();
}
catch
(
DecodeException
e
)
{
LOG
.
error
(
"Method load error"
,
e
);
}
}
if
(
returnType
!=
null
&&
(
returnType
.
isGeneric
()
||
returnType
.
isGenericType
()))
{
return
returnType
;
}
return
null
;
}
NMethod
methodDetails
=
clsp
.
getMethodDetails
(
callMth
);
if
(
methodDetails
!=
null
)
{
return
methodDetails
.
getReturnType
();
}
return
null
;
}
public
List
<
DexNode
>
getDexNodes
()
{
return
dexNodes
;
}
...
...
jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java
View file @
ed8c6626
package
jadx
.
core
.
dex
.
nodes
.
parser
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.LinkedHashMap
;
import
java.util.LinkedList
;
import
java.util.List
;
import
java.util.Map
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
...
...
@@ -13,6 +12,7 @@ import jadx.core.Consts;
import
jadx.core.dex.attributes.IAttributeNode
;
import
jadx.core.dex.attributes.annotations.Annotation
;
import
jadx.core.dex.instructions.args.ArgType
;
import
jadx.core.dex.nodes.GenericInfo
;
import
jadx.core.utils.exceptions.JadxRuntimeException
;
public
class
SignatureParser
{
...
...
@@ -219,11 +219,11 @@ public class SignatureParser {
* <p/>
* Example: "<T:Ljava/lang/Exception;:Ljava/lang/Object;>"
*/
public
Map
<
ArgType
,
List
<
ArgType
>
>
consumeGenericMap
()
{
public
List
<
GenericInfo
>
consumeGenericMap
()
{
if
(!
lookAhead
(
'<'
))
{
return
Collections
.
empty
Map
();
return
Collections
.
empty
List
();
}
Map
<
ArgType
,
List
<
ArgType
>>
map
=
new
LinkedHashMap
<>(
2
);
List
<
GenericInfo
>
list
=
new
ArrayList
<>(
);
consume
(
'<'
);
while
(
true
)
{
if
(
lookAhead
(
'>'
)
||
next
()
==
STOP_CHAR
)
{
...
...
@@ -231,15 +231,15 @@ public class SignatureParser {
}
String
id
=
consumeUntil
(
':'
);
if
(
id
==
null
)
{
LOG
.
error
(
"
Can't
parse generic map: {}"
,
sign
);
return
Collections
.
empty
Map
();
LOG
.
error
(
"
Failed to
parse generic map: {}"
,
sign
);
return
Collections
.
empty
List
();
}
tryConsume
(
':'
);
List
<
ArgType
>
types
=
consumeExtendsTypesList
();
map
.
put
(
ArgType
.
genericType
(
id
),
types
);
list
.
add
(
new
GenericInfo
(
ArgType
.
genericType
(
id
),
types
)
);
}
consume
(
'>'
);
return
map
;
return
list
;
}
/**
...
...
jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java
View file @
ed8c6626
...
...
@@ -259,11 +259,11 @@ public class PrepareForCodeGen extends AbstractVisitor {
private
void
addMethodMsg
(
MethodNode
mth
)
{
if
(
commentedCount
>
0
)
{
String
msg
=
"
JADX WARN:
Illegal instructions before constructor call commented (this can break semantics)"
;
String
msg
=
"Illegal instructions before constructor call commented (this can break semantics)"
;
if
(
brokenCode
||
regionDepth
>
1
)
{
mth
.
addWarn
(
msg
);
}
else
{
mth
.
addComment
(
msg
);
mth
.
addComment
(
"JADX WARN: "
+
msg
);
}
}
}
...
...
jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java
View file @
ed8c6626
...
...
@@ -327,7 +327,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
LOG
.
warn
(
"Generic type differs: '{}' and '{}' in {}"
,
gType
,
varType
,
mth
);
return
false
;
}
if
(!
iterableArg
.
isRegister
())
{
if
(!
iterableArg
.
isRegister
()
||
!
iterableType
.
isObject
()
)
{
return
true
;
}
// TODO: add checks
...
...
jadx-core/src/main/java/jadx/core/dex/visitors/regions/variables/ProcessVariables.java
View file @
ed8c6626
...
...
@@ -26,6 +26,8 @@ import jadx.core.dex.nodes.MethodNode;
import
jadx.core.dex.regions.loops.LoopRegion
;
import
jadx.core.dex.visitors.AbstractVisitor
;
import
jadx.core.dex.visitors.regions.DepthRegionTraversal
;
import
jadx.core.dex.visitors.typeinference.TypeCompare
;
import
jadx.core.dex.visitors.typeinference.TypeCompareEnum
;
import
jadx.core.utils.RegionUtils
;
import
jadx.core.utils.Utils
;
import
jadx.core.utils.exceptions.JadxException
;
...
...
@@ -64,19 +66,25 @@ public class ProcessVariables extends AbstractVisitor {
private
void
checkCodeVars
(
MethodNode
mth
,
List
<
CodeVar
>
codeVars
)
{
int
unknownTypesCount
=
0
;
for
(
CodeVar
codeVar
:
codeVars
)
{
codeVar
.
getSsaVars
().
stream
()
.
filter
(
ssaVar
->
ssaVar
.
contains
(
AFlag
.
IMMUTABLE_TYPE
))
.
forEach
(
ssaVar
->
{
ArgType
ssaType
=
ssaVar
.
getAssign
().
getInitType
();
if
(
ssaType
.
isTypeKnown
()
&&
!
ssaType
.
equals
(
codeVar
.
getType
()))
{
mth
.
addWarn
(
"Incorrect type for immutable var: ssa="
+
ssaType
+
", code="
+
codeVar
.
getType
()
+
", for "
+
ssaVar
.
getDetailedVarInfo
(
mth
));
}
});
if
(
codeVar
.
getType
()
==
null
)
{
ArgType
codeVarType
=
codeVar
.
getType
();
if
(
codeVarType
==
null
)
{
codeVar
.
setType
(
ArgType
.
UNKNOWN
);
unknownTypesCount
++;
}
else
{
codeVar
.
getSsaVars
().
stream
()
.
filter
(
ssaVar
->
ssaVar
.
contains
(
AFlag
.
IMMUTABLE_TYPE
))
.
forEach
(
ssaVar
->
{
ArgType
ssaType
=
ssaVar
.
getAssign
().
getInitType
();
if
(
ssaType
.
isTypeKnown
())
{
TypeCompare
comparator
=
mth
.
root
().
getTypeUpdate
().
getComparator
();
TypeCompareEnum
result
=
comparator
.
compareTypes
(
ssaType
,
codeVarType
);
if
(
result
==
TypeCompareEnum
.
CONFLICT
||
result
.
isNarrow
())
{
mth
.
addWarn
(
"Incorrect type for immutable var: ssa="
+
ssaType
+
", code="
+
codeVarType
+
", for "
+
ssaVar
.
getDetailedVarInfo
(
mth
));
}
}
});
}
}
if
(
unknownTypesCount
!=
0
)
{
...
...
jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeBound.java
View file @
ed8c6626
...
...
@@ -5,7 +5,11 @@ import org.jetbrains.annotations.Nullable;
import
jadx.core.dex.instructions.args.ArgType
;
import
jadx.core.dex.instructions.args.RegisterArg
;
/**
* Information to restrict types by applying constraints (or boundaries)
*/
public
interface
ITypeBound
{
BoundEnum
getBound
();
ArgType
getType
();
...
...
jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeBoundDynamic.java
0 → 100644
View file @
ed8c6626
package
jadx
.
core
.
dex
.
visitors
.
typeinference
;
import
jadx.core.dex.instructions.args.ArgType
;
/**
* 'Dynamic' type bound allows to use requested and not yet applied types
* from {@link TypeUpdateInfo} for more precise restrictions
*/
public
interface
ITypeBoundDynamic
extends
ITypeBound
{
/**
* This method will be executed instead of {@link ITypeBound#getType()}
* if {@link TypeUpdateInfo} is available.
*/
ArgType
getType
(
TypeUpdateInfo
updateInfo
);
}
jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundInvokeAssign.java
0 → 100644
View file @
ed8c6626
package
jadx
.
core
.
dex
.
visitors
.
typeinference
;
import
jadx.core.dex.instructions.InvokeNode
;
import
jadx.core.dex.instructions.args.ArgType
;
import
jadx.core.dex.instructions.args.RegisterArg
;
import
jadx.core.dex.nodes.RootNode
;
/**
* Special dynamic bound for invoke with generics.
* Bound type calculated using instance generic type.
* TODO: also can depends on argument types
*/
public
final
class
TypeBoundInvokeAssign
implements
ITypeBoundDynamic
{
private
final
RootNode
root
;
private
final
InvokeNode
invokeNode
;
private
final
ArgType
genericReturnType
;
public
TypeBoundInvokeAssign
(
RootNode
root
,
InvokeNode
invokeNode
,
ArgType
genericReturnType
)
{
this
.
root
=
root
;
this
.
invokeNode
=
invokeNode
;
this
.
genericReturnType
=
genericReturnType
;
}
@Override
public
BoundEnum
getBound
()
{
return
BoundEnum
.
ASSIGN
;
}
@Override
public
ArgType
getType
(
TypeUpdateInfo
updateInfo
)
{
return
getReturnType
(
updateInfo
.
getType
(
invokeNode
.
getArg
(
0
)));
}
@Override
public
ArgType
getType
()
{
return
getReturnType
(
invokeNode
.
getArg
(
0
).
getType
());
}
private
ArgType
getReturnType
(
ArgType
instanceType
)
{
ArgType
resultGeneric
=
TypeUpdate
.
getResultGeneric
(
root
,
instanceType
,
genericReturnType
);
if
(
resultGeneric
!=
null
)
{
return
resultGeneric
;
}
return
invokeNode
.
getCallMth
().
getReturnType
();
}
@Override
public
RegisterArg
getArg
()
{
return
invokeNode
.
getResult
();
}
@Override
public
boolean
equals
(
Object
o
)
{
if
(
this
==
o
)
{
return
true
;
}
if
(
o
==
null
||
getClass
()
!=
o
.
getClass
())
{
return
false
;
}
TypeBoundInvokeAssign
that
=
(
TypeBoundInvokeAssign
)
o
;
return
invokeNode
.
equals
(
that
.
invokeNode
);
}
@Override
public
int
hashCode
()
{
return
invokeNode
.
hashCode
();
}
@Override
public
String
toString
()
{
return
"InvokeAssign{"
+
invokeNode
.
getCallMth
().
getShortId
()
+
", returnType="
+
genericReturnType
+
", currentType="
+
getType
()
+
", instanceArg="
+
invokeNode
.
getArg
(
0
)
+
'}'
;
}
}
jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java
View file @
ed8c6626
...
...
@@ -16,8 +16,11 @@ import jadx.core.clsp.ClspGraph;
import
jadx.core.dex.attributes.AFlag
;
import
jadx.core.dex.attributes.AType
;
import
jadx.core.dex.info.ClassInfo
;
import
jadx.core.dex.info.MethodInfo
;
import
jadx.core.dex.instructions.IndexInsnNode
;
import
jadx.core.dex.instructions.InsnType
;
import
jadx.core.dex.instructions.InvokeNode
;
import
jadx.core.dex.instructions.InvokeType
;
import
jadx.core.dex.instructions.PhiInsn
;
import
jadx.core.dex.instructions.args.ArgType
;
import
jadx.core.dex.instructions.args.CodeVar
;
...
...
@@ -51,11 +54,13 @@ import jadx.core.utils.Utils;
public
final
class
TypeInferenceVisitor
extends
AbstractVisitor
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
TypeInferenceVisitor
.
class
);
private
RootNode
root
;
private
TypeUpdate
typeUpdate
;
@Override
public
void
init
(
RootNode
root
)
{
typeUpdate
=
root
.
getTypeUpdate
();
this
.
root
=
root
;
this
.
typeUpdate
=
root
.
getTypeUpdate
();
}
@Override
...
...
@@ -239,6 +244,10 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
}
break
;
case
INVOKE:
addBound
(
typeInfo
,
makeAssignInvokeBound
((
InvokeNode
)
insn
));
break
;
default
:
ArgType
type
=
insn
.
getResult
().
getInitType
();
addBound
(
typeInfo
,
new
TypeBoundConst
(
BoundEnum
.
ASSIGN
,
type
));
...
...
@@ -246,6 +255,24 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
}
}
private
ITypeBound
makeAssignInvokeBound
(
InvokeNode
invokeNode
)
{
MethodInfo
callMth
=
invokeNode
.
getCallMth
();
ArgType
boundType
=
callMth
.
getReturnType
();
ArgType
genericReturnType
=
root
.
getMethodGenericReturnType
(
callMth
);
if
(
genericReturnType
!=
null
)
{
if
(
genericReturnType
.
containsGenericType
())
{
InvokeType
invokeType
=
invokeNode
.
getInvokeType
();
if
(
invokeNode
.
getArgsCount
()
!=
0
&&
invokeType
!=
InvokeType
.
STATIC
&&
invokeType
!=
InvokeType
.
SUPER
)
{
return
new
TypeBoundInvokeAssign
(
root
,
invokeNode
,
genericReturnType
);
}
}
else
{
boundType
=
genericReturnType
;
}
}
return
new
TypeBoundConst
(
BoundEnum
.
ASSIGN
,
boundType
);
}
@Nullable
private
ITypeBound
makeUseBound
(
RegisterArg
regArg
)
{
InsnNode
insn
=
regArg
.
getParentInsn
();
...
...
jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java
View file @
ed8c6626
...
...
@@ -2,23 +2,29 @@ package jadx.core.dex.visitors.typeinference;
import
java.util.Comparator
;
import
java.util.EnumMap
;
import
java.util.LinkedHashMap
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Objects
;
import
java.util.Set
;
import
org.jetbrains.annotations.NotNull
;
import
org.jetbrains.annotations.Nullable
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
jadx.core.Consts
;
import
jadx.core.clsp.NClass
;
import
jadx.core.dex.attributes.AFlag
;
import
jadx.core.dex.info.MethodInfo
;
import
jadx.core.dex.instructions.InsnType
;
import
jadx.core.dex.instructions.InvokeNode
;
import
jadx.core.dex.instructions.args.ArgType
;
import
jadx.core.dex.instructions.args.InsnArg
;
import
jadx.core.dex.instructions.args.PrimitiveType
;
import
jadx.core.dex.instructions.args.RegisterArg
;
import
jadx.core.dex.instructions.args.SSAVar
;
import
jadx.core.dex.nodes.GenericInfo
;
import
jadx.core.dex.nodes.InsnNode
;
import
jadx.core.dex.nodes.RootNode
;
import
jadx.core.utils.exceptions.JadxOverflowException
;
...
...
@@ -34,10 +40,12 @@ public final class TypeUpdate {
private
static
final
TypeUpdateFlags
FLAGS_EMPTY
=
new
TypeUpdateFlags
();
private
static
final
TypeUpdateFlags
FLAGS_WIDER
=
new
TypeUpdateFlags
().
allowWider
();
private
final
RootNode
root
;
private
final
Map
<
InsnType
,
ITypeListener
>
listenerRegistry
;
private
final
TypeCompare
comparator
;
public
TypeUpdate
(
RootNode
root
)
{
this
.
root
=
root
;
this
.
listenerRegistry
=
initListenerRegistry
();
this
.
comparator
=
new
TypeCompare
(
root
);
}
...
...
@@ -74,7 +82,7 @@ public final class TypeUpdate {
LOG
.
debug
(
"Applying types, init for {} -> {}"
,
ssaVar
,
candidateType
);
updates
.
forEach
(
updateEntry
->
LOG
.
debug
(
" {} -> {}"
,
updateEntry
.
getType
(),
updateEntry
.
getArg
()));
}
update
s
.
forEach
(
TypeUpdateEntry:
:
apply
);
update
Info
.
applyUpdates
(
);
return
CHANGED
;
}
...
...
@@ -112,7 +120,7 @@ public final class TypeUpdate {
private
TypeUpdateResult
updateTypeForSsaVar
(
TypeUpdateInfo
updateInfo
,
SSAVar
ssaVar
,
ArgType
candidateType
)
{
TypeInfo
typeInfo
=
ssaVar
.
getTypeInfo
();
if
(!
inBounds
(
typeInfo
.
getBounds
(),
candidateType
))
{
if
(!
inBounds
(
updateInfo
,
typeInfo
.
getBounds
(),
candidateType
))
{
if
(
Consts
.
DEBUG
)
{
LOG
.
debug
(
"Reject type '{}' for {} by bounds: {}"
,
candidateType
,
ssaVar
,
typeInfo
.
getBounds
());
}
...
...
@@ -176,8 +184,17 @@ public final class TypeUpdate {
}
boolean
inBounds
(
Set
<
ITypeBound
>
bounds
,
ArgType
candidateType
)
{
return
inBounds
(
null
,
bounds
,
candidateType
);
}
private
boolean
inBounds
(
@Nullable
TypeUpdateInfo
updateInfo
,
Set
<
ITypeBound
>
bounds
,
ArgType
candidateType
)
{
for
(
ITypeBound
bound
:
bounds
)
{
ArgType
boundType
=
bound
.
getType
();
ArgType
boundType
;
if
(
updateInfo
!=
null
&&
bound
instanceof
ITypeBoundDynamic
)
{
boundType
=
((
ITypeBoundDynamic
)
bound
).
getType
(
updateInfo
);
}
else
{
boundType
=
bound
.
getType
();
}
if
(
boundType
!=
null
&&
!
checkBound
(
candidateType
,
bound
,
boundType
))
{
return
false
;
}
...
...
@@ -185,10 +202,10 @@ public final class TypeUpdate {
return
true
;
}
private
boolean
inBounds
(
InsnArg
arg
,
ArgType
candidateType
)
{
private
boolean
inBounds
(
TypeUpdateInfo
updateInfo
,
InsnArg
arg
,
ArgType
candidateType
)
{
if
(
arg
.
isRegister
())
{
TypeInfo
typeInfo
=
((
RegisterArg
)
arg
).
getSVar
().
getTypeInfo
();
return
inBounds
(
typeInfo
.
getBounds
(),
candidateType
);
return
inBounds
(
updateInfo
,
typeInfo
.
getBounds
(),
candidateType
);
}
return
arg
.
getType
().
equals
(
candidateType
);
}
...
...
@@ -258,9 +275,83 @@ public final class TypeUpdate {
registry
.
put
(
InsnType
.
NEG
,
this
::
suggestAllSameListener
);
registry
.
put
(
InsnType
.
NOT
,
this
::
suggestAllSameListener
);
registry
.
put
(
InsnType
.
CHECK_CAST
,
this
::
checkCastListener
);
registry
.
put
(
InsnType
.
INVOKE
,
this
::
invokeListener
);
return
registry
;
}
private
TypeUpdateResult
invokeListener
(
TypeUpdateInfo
updateInfo
,
InsnNode
insn
,
InsnArg
arg
,
ArgType
candidateType
)
{
if
(
insn
.
getResult
()
==
null
)
{
return
SAME
;
}
if
(
candidateType
.
isGeneric
()
||
candidateType
.
isGenericType
())
{
InvokeNode
invokeNode
=
(
InvokeNode
)
insn
;
MethodInfo
callMth
=
invokeNode
.
getCallMth
();
if
(
isAssign
(
insn
,
arg
))
{
// TODO: implement backward type propagation (from result to instance)
return
SAME
;
}
else
{
ArgType
returnType
=
root
.
getMethodGenericReturnType
(
callMth
);
if
(
returnType
==
null
)
{
return
SAME
;
}
ArgType
resultGeneric
=
getResultGeneric
(
root
,
candidateType
,
returnType
);
if
(
resultGeneric
==
null
)
{
return
SAME
;
}
return
updateTypeChecked
(
updateInfo
,
insn
.
getResult
(),
resultGeneric
);
}
}
return
SAME
;
}
@Nullable
public
static
ArgType
getResultGeneric
(
RootNode
root
,
ArgType
instanceType
,
ArgType
genericRetType
)
{
if
(
genericRetType
==
null
)
{
return
null
;
}
if
(
instanceType
.
isGeneric
())
{
NClass
clsDetails
=
root
.
getClsp
().
getClsDetails
(
instanceType
);
if
(
clsDetails
==
null
||
clsDetails
.
getGenerics
().
isEmpty
())
{
return
null
;
}
List
<
GenericInfo
>
generics
=
clsDetails
.
getGenerics
();
ArgType
[]
actualTypes
=
instanceType
.
getGenericTypes
();
if
(
generics
.
size
()
!=
actualTypes
.
length
)
{
return
null
;
}
Map
<
ArgType
,
ArgType
>
replaceMap
=
new
LinkedHashMap
<>();
for
(
int
i
=
0
;
i
<
actualTypes
.
length
;
i
++)
{
ArgType
actualType
=
actualTypes
[
i
];
ArgType
genericType
=
generics
.
get
(
i
).
getGenericType
();
replaceMap
.
put
(
genericType
,
actualType
);
}
return
replaceGenericTypes
(
genericRetType
,
replaceMap
);
}
return
null
;
}
private
static
ArgType
replaceGenericTypes
(
ArgType
replaceType
,
Map
<
ArgType
,
ArgType
>
replaceMap
)
{
if
(
replaceType
.
isGenericType
())
{
return
replaceMap
.
get
(
replaceType
);
}
ArgType
[]
genericTypes
=
replaceType
.
getGenericTypes
();
if
(
replaceType
.
isGeneric
()
&&
genericTypes
!=
null
&&
genericTypes
.
length
!=
0
)
{
int
size
=
genericTypes
.
length
;
ArgType
[]
newTypes
=
new
ArgType
[
size
];
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
ArgType
genericType
=
genericTypes
[
i
];
ArgType
type
=
replaceGenericTypes
(
genericType
,
replaceMap
);
if
(
type
==
null
)
{
type
=
genericType
;
}
newTypes
[
i
]
=
type
;
}
return
ArgType
.
generic
(
replaceType
.
getObject
(),
newTypes
);
}
return
null
;
}
private
TypeUpdateResult
sameFirstArgListener
(
TypeUpdateInfo
updateInfo
,
InsnNode
insn
,
InsnArg
arg
,
ArgType
candidateType
)
{
InsnArg
changeArg
=
isAssign
(
insn
,
arg
)
?
insn
.
getArg
(
0
)
:
insn
.
getResult
();
return
updateTypeChecked
(
updateInfo
,
changeArg
,
candidateType
);
...
...
@@ -275,7 +366,7 @@ public final class TypeUpdate {
TypeCompareEnum
compareTypes
=
comparator
.
compareTypes
(
candidateType
,
changeArg
.
getType
());
boolean
correctType
=
compareTypes
==
TypeCompareEnum
.
EQUAL
||
(
assignChanged
?
compareTypes
.
isWider
()
:
compareTypes
.
isNarrow
());
if
(
correctType
&&
inBounds
(
changeArg
,
candidateType
))
{
if
(
correctType
&&
inBounds
(
updateInfo
,
changeArg
,
candidateType
))
{
allowReject
=
true
;
}
else
{
return
REJECT
;
...
...
jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateEntry.java
View file @
ed8c6626
...
...
@@ -12,10 +12,6 @@ public final class TypeUpdateEntry {
this
.
type
=
type
;
}
public
void
apply
()
{
arg
.
setType
(
type
);
}
public
InsnArg
getArg
()
{
return
arg
;
}
...
...
jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java
View file @
ed8c6626
...
...
@@ -18,6 +18,13 @@ public class TypeUpdateInfo {
updates
.
add
(
new
TypeUpdateEntry
(
arg
,
changeType
));
}
public
void
applyUpdates
()
{
for
(
TypeUpdateEntry
updateEntry
:
updates
)
{
InsnArg
arg
=
updateEntry
.
getArg
();
arg
.
setType
(
updateEntry
.
getType
());
}
}
public
boolean
isProcessed
(
InsnArg
arg
)
{
if
(
updates
.
isEmpty
())
{
return
false
;
...
...
@@ -30,6 +37,15 @@ public class TypeUpdateInfo {
return
false
;
}
public
ArgType
getType
(
InsnArg
arg
)
{
for
(
TypeUpdateEntry
update
:
updates
)
{
if
(
update
.
getArg
()
==
arg
)
{
return
update
.
getType
();
}
}
return
arg
.
getType
();
}
public
void
rollbackUpdate
(
InsnArg
arg
)
{
updates
.
removeIf
(
updateEntry
->
updateEntry
.
getArg
()
==
arg
);
}
...
...
jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java
View file @
ed8c6626
...
...
@@ -353,7 +353,7 @@ public abstract class IntegrationTest extends TestUtils {
return
dynamicCompiler
.
invoke
(
cls
,
methodName
,
types
,
args
);
}
p
ublic
File
getJarForClass
(
Class
<?>
cls
)
throws
IOException
{
p
rivate
File
getJarForClass
(
Class
<?>
cls
)
throws
IOException
{
List
<
File
>
files
=
compileClass
(
cls
);
assertThat
(
"File list is empty"
,
files
,
not
(
empty
()));
...
...
jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java
View file @
ed8c6626
package
jadx
.
tests
.
functional
;
import
java.util.
LinkedHashMap
;
import
java.util.
ArrayList
;
import
java.util.List
;
import
java.util.Map
;
import
org.junit.jupiter.api.Test
;
import
jadx.core.dex.instructions.args.ArgType
;
import
jadx.core.dex.nodes.GenericInfo
;
import
jadx.core.dex.nodes.parser.SignatureParser
;
import
static
jadx
.
core
.
dex
.
instructions
.
args
.
ArgType
.
INT
;
...
...
@@ -20,7 +20,6 @@ import static jadx.core.dex.instructions.args.ArgType.wildcard;
import
static
java
.
util
.
Collections
.
emptyList
;
import
static
java
.
util
.
Collections
.
singletonList
;
import
static
org
.
hamcrest
.
MatcherAssert
.
assertThat
;
import
static
org
.
hamcrest
.
Matchers
.
anEmptyMap
;
import
static
org
.
hamcrest
.
Matchers
.
hasSize
;
import
static
org
.
hamcrest
.
Matchers
.
is
;
...
...
@@ -92,14 +91,14 @@ class SignatureParserTest {
@SuppressWarnings
(
"unchecked"
)
private
static
void
checkGenerics
(
String
g
,
Object
...
objs
)
{
Map
<
ArgType
,
List
<
ArgType
>>
map
=
new
SignatureParser
(
g
).
consumeGenericMap
();
Map
<
ArgType
,
List
<
ArgType
>>
expectedMap
=
new
LinkedHashMap
<>();
List
<
GenericInfo
>
genericsList
=
new
SignatureParser
(
g
).
consumeGenericMap
();
List
<
GenericInfo
>
expectedList
=
new
ArrayList
<>();
for
(
int
i
=
0
;
i
<
objs
.
length
;
i
+=
2
)
{
ArgType
generic
=
genericType
((
String
)
objs
[
i
]);
List
<
ArgType
>
list
=
(
List
<
ArgType
>)
objs
[
i
+
1
];
expected
Map
.
put
(
generic
,
list
);
expected
List
.
add
(
new
GenericInfo
(
generic
,
list
)
);
}
assertThat
(
map
,
is
(
expectedMap
));
assertThat
(
genericsList
,
is
(
expectedList
));
}
@Test
...
...
@@ -122,7 +121,7 @@ class SignatureParserTest {
@Test
public
void
testBadGenericMap
()
{
Map
<
ArgType
,
List
<
ArgType
>>
map
=
new
SignatureParser
(
"<A:Ljava/lang/Object;B"
).
consumeGenericMap
();
assertThat
(
map
,
anEmptyMap
(
));
List
<
GenericInfo
>
list
=
new
SignatureParser
(
"<A:Ljava/lang/Object;B"
).
consumeGenericMap
();
assertThat
(
list
,
hasSize
(
0
));
}
}
jadx-core/src/test/java/jadx/tests/integration/loops/TestNestedLoops2.java
View file @
ed8c6626
...
...
@@ -32,6 +32,6 @@ public class TestNestedLoops2 extends IntegrationTest {
String
code
=
cls
.
getCode
().
toString
();
assertThat
(
code
,
containsOne
(
"for (int i = 0; i < list.size(); i++) {"
));
assertThat
(
code
,
containsOne
(
"while (j <
((String) list.get(i)
).length()) {"
));
assertThat
(
code
,
containsOne
(
"while (j <
list.get(i
).length()) {"
));
}
}
jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics2.java
0 → 100644
View file @
ed8c6626
package
jadx
.
tests
.
integration
.
types
;
import
org.junit.jupiter.api.Test
;
import
jadx.core.dex.nodes.ClassNode
;
import
jadx.tests.api.SmaliTest
;
import
static
jadx
.
tests
.
api
.
utils
.
JadxMatchers
.
containsOne
;
import
static
org
.
hamcrest
.
MatcherAssert
.
assertThat
;
public
class
TestGenerics2
extends
SmaliTest
{
// @formatter:off
/*
public void test() {
Map<Integer, String> map = this.field;
useInt(map.size());
Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, String> next = it.next();
useInt(next.getKey().intValue());
next.getValue().trim();
}
}
*/
// @formatter:on
@Test
public
void
test
()
{
ClassNode
cls
=
getClassNodeFromSmali
();
String
code
=
cls
.
getCode
().
toString
();
assertThat
(
code
,
containsOne
(
"Entry<Integer, String> next"
));
assertThat
(
code
,
containsOne
(
"useInt(next.getKey().intValue());"
));
// no Integer cast
}
}
jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics3.java
0 → 100644
View file @
ed8c6626
package
jadx
.
tests
.
integration
.
types
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
org.junit.jupiter.api.Test
;
import
jadx.core.dex.nodes.ClassNode
;
import
jadx.tests.api.IntegrationTest
;
import
static
jadx
.
tests
.
api
.
utils
.
JadxMatchers
.
containsOne
;
import
static
org
.
hamcrest
.
MatcherAssert
.
assertThat
;
public
class
TestGenerics3
extends
IntegrationTest
{
public
static
class
TestCls
{
public
static
void
test
()
{
List
<
String
>
classes
=
getClasses
();
Collections
.
sort
(
classes
);
int
passed
=
0
;
for
(
String
cls
:
classes
)
{
if
(
runTest
(
cls
))
{
passed
++;
}
}
int
failed
=
classes
.
size
()
-
passed
;
System
.
out
.
println
(
"failed: "
+
failed
);
}
private
static
boolean
runTest
(
String
clsName
)
{
return
false
;
}
private
static
List
<
String
>
getClasses
()
{
return
new
ArrayList
<>();
}
}
@Test
public
void
test
()
{
ClassNode
cls
=
getClassNode
(
TestCls
.
class
);
String
code
=
cls
.
getCode
().
toString
();
assertThat
(
code
,
containsOne
(
"List<String> classes"
));
assertThat
(
code
,
containsOne
(
"for (String cls : classes) {"
));
}
@Test
public
void
testNoDebug
()
{
noDebugInfo
();
ClassNode
cls
=
getClassNode
(
TestCls
.
class
);
String
code
=
cls
.
getCode
().
toString
();
assertThat
(
code
,
containsOne
(
"List<String> classes"
));
}
}
jadx-core/src/test/smali/types/TestGenerics2.smali
0 → 100644
View file @
ed8c6626
.class public final Ltypes/TestGenerics2;
.super Ljava/lang/Object;
.source "SourceFile"
# instance fields
.field private field:Ljava/util/Map;
.annotation system Ldalvik/annotation/Signature;
value = {
"Ljava/util/Map<",
"Ljava/lang/Integer;",
"Ljava/lang/String;",
">;"
}
.end annotation
.end field
.method public test()V
.registers 5
iget-object v4, p0, Ltypes/TestGenerics2;->field:Ljava/util/Map;
invoke-interface {v4}, Ljava/util/Map;->size()I
move-result v0
invoke-static {v0}, Ltypes/TestGenerics2;->useInt(I)V
invoke-interface {v4}, Ljava/util/Map;->entrySet()Ljava/util/Set;
move-result-object v4
invoke-interface {v4}, Ljava/util/Set;->iterator()Ljava/util/Iterator;
move-result-object v4
:goto_16
invoke-interface {v4}, Ljava/util/Iterator;->hasNext()Z
move-result v0
if-eqz v0, :ret
invoke-interface {v4}, Ljava/util/Iterator;->next()Ljava/lang/Object;
move-result-object v0
invoke-interface {v0}, Ljava/util/Map$Entry;->getKey()Ljava/lang/Object;
move-result-object v1
check-cast v1, Ljava/lang/Integer;
invoke-virtual {v1}, Ljava/lang/Integer;->intValue()I
move-result v1
invoke-static {v1}, Ltypes/TestGenerics2;->useInt(I)V
invoke-interface {v0}, Ljava/util/Map$Entry;->getValue()Ljava/lang/Object;
move-result-object v0
check-cast v0, Ljava/lang/String;
invoke-interface {v0, p1}, Ljava/lang/String;->trim()Ljava/lang/String;
goto :goto_16
:ret
return-void
.end method
.method public static useInt(I)V
.registers 3
return-void
.end method
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment