fix: add generic types propagation (#695)
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
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)
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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 nClass) {
|
||||
private List<NMethod> getMethodsDetails(ClassNode cls) {
|
||||
List<NMethod> methods = new ArrayList<>();
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (!m.getAccessFlags().isPublic()
|
||||
&& !m.getAccessFlags().isProtected()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<ArgType> args = new ArrayList<>();
|
||||
|
||||
boolean genericArg = false;
|
||||
for (RegisterArg r : m.getArguments(false)) {
|
||||
ArgType argType = r.getType();
|
||||
if (argType.isGeneric() || argType.isGenericType()) {
|
||||
args.add(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));
|
||||
AccessInfo accessFlags = m.getAccessFlags();
|
||||
if (accessFlags.isPublic() || accessFlags.isProtected()) {
|
||||
processMethodDetails(methods, m, accessFlags);
|
||||
}
|
||||
}
|
||||
return methods.toArray(new NMethod[methods.size()]);
|
||||
return methods;
|
||||
}
|
||||
|
||||
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()) {
|
||||
genericArgs[i] = argType;
|
||||
genericArg = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 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 {
|
||||
int argCount = 0;
|
||||
ArgType[] argTypes = method.getArgType();
|
||||
for (ArgType arg : argTypes) {
|
||||
if (arg != null) {
|
||||
argCount++;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
public void load(InputStream input) throws IOException, DecodeException {
|
||||
private 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,18 +353,46 @@ public class ClsSet {
|
||||
for (int j = 0; j < pCount; j++) {
|
||||
parents[j] = classes[in.readInt()];
|
||||
}
|
||||
classes[i].setParents(parents);
|
||||
|
||||
int mCount = in.readByte();
|
||||
NMethod[] methods = new NMethod[mCount];
|
||||
for (int j = 0; j < mCount; j++) {
|
||||
methods[j] = readMethod(in);
|
||||
}
|
||||
classes[i].setMethods(methods);
|
||||
NClass nClass = classes[i];
|
||||
nClass.setParents(parents);
|
||||
nClass.setGenerics(readGenerics(in));
|
||||
nClass.setMethods(readClsMethods(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));
|
||||
}
|
||||
}
|
||||
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 {
|
||||
String shortId = readLongString(in);
|
||||
int argCount = in.readByte();
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.loadFromClstFile();
|
||||
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);
|
||||
|
||||
@@ -49,7 +49,7 @@ public class ConvertToClsSet {
|
||||
root.load(inputFiles);
|
||||
|
||||
ClsSet set = new ClsSet();
|
||||
set.load(root);
|
||||
set.loadFrom(root);
|
||||
set.save(output);
|
||||
LOG.info("Output: {}", output);
|
||||
LOG.info("done");
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
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
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.getGenericMap(), true);
|
||||
addGenericMap(clsCode, cls.getGenerics(), 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 (gmap == null || gmap.isEmpty()) {
|
||||
public boolean addGenericMap(CodeWriter code, List<GenericInfo> generics, boolean classDeclaration) {
|
||||
if (generics == 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();) {
|
||||
|
||||
@@ -99,7 +99,7 @@ public class MethodGen {
|
||||
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
|
||||
}
|
||||
|
||||
if (classGen.addGenericMap(code, mth.getGenericMap(), false)) {
|
||||
if (classGen.addGenericMap(code, mth.getGenerics(), false)) {
|
||||
code.add(' ');
|
||||
}
|
||||
if (ai.isConstructor()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
genericMap = sp.consumeGenericMap();
|
||||
generics = 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 genericMap;
|
||||
public List<GenericInfo> getGenerics() {
|
||||
return generics;
|
||||
}
|
||||
|
||||
public List<MethodNode> getMethods() {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
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 + '}';
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
genericMap = null;
|
||||
generics = Collections.emptyList();
|
||||
instructions = null;
|
||||
blocks = null;
|
||||
enterBlock = null;
|
||||
@@ -174,7 +173,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
genericMap = sp.consumeGenericMap();
|
||||
generics = 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 genericMap;
|
||||
public List<GenericInfo> getGenerics() {
|
||||
return generics;
|
||||
}
|
||||
|
||||
private static void initTryCatches(MethodNode mth, Code mthCode, InsnNode[] insnByOffset) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
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.emptyMap();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
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.emptyMap();
|
||||
LOG.error("Failed to parse generic map: {}", sign);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
tryConsume(':');
|
||||
List<ArgType> types = consumeExtendsTypesList();
|
||||
map.put(ArgType.genericType(id), types);
|
||||
list.add(new GenericInfo(ArgType.genericType(id), types));
|
||||
}
|
||||
consume('>');
|
||||
return map;
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+19
-11
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
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);
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
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)
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
+28
-1
@@ -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();
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
updates.forEach(TypeUpdateEntry::apply);
|
||||
updateInfo.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;
|
||||
|
||||
@@ -12,10 +12,6 @@ public final class TypeUpdateEntry {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public void apply() {
|
||||
arg.setType(type);
|
||||
}
|
||||
|
||||
public InsnArg getArg() {
|
||||
return arg;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -353,7 +353,7 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
return dynamicCompiler.invoke(cls, methodName, types, args);
|
||||
}
|
||||
|
||||
public File getJarForClass(Class<?> cls) throws IOException {
|
||||
private File getJarForClass(Class<?> cls) throws IOException {
|
||||
List<File> files = compileClass(cls);
|
||||
assertThat("File list is empty", files, not(empty()));
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
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];
|
||||
expectedMap.put(generic, list);
|
||||
expectedList.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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()) {"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
.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
|
||||
Reference in New Issue
Block a user