feat: add methods information from standard library, improve generics and varargs restore (#836)
This commit is contained in:
Binary file not shown.
@@ -327,6 +327,14 @@ public class JadxArgs {
|
||||
}
|
||||
}
|
||||
|
||||
public void setRenameFlags(Set<RenameEnum> renameFlags) {
|
||||
this.renameFlags = renameFlags;
|
||||
}
|
||||
|
||||
public Set<RenameEnum> getRenameFlags() {
|
||||
return renameFlags;
|
||||
}
|
||||
|
||||
public OutputFormatEnum getOutputFormat() {
|
||||
return outputFormat;
|
||||
}
|
||||
|
||||
@@ -47,18 +47,12 @@ public final class JavaMethod implements JavaNode {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ArgType> arguments = mth.getArgTypes();
|
||||
if (arguments == null) {
|
||||
arguments = infoArgTypes;
|
||||
}
|
||||
return Utils.collectionMap(arguments,
|
||||
type -> ArgType.tryToResolveClassAlias(mth.dex(), type));
|
||||
}
|
||||
|
||||
public ArgType getReturnType() {
|
||||
ArgType retType = mth.getReturnType();
|
||||
if (retType == null) {
|
||||
retType = mth.getMethodInfo().getReturnType();
|
||||
}
|
||||
return ArgType.tryToResolveClassAlias(mth.dex(), retType);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.visitors.AttachMethodDetails;
|
||||
import jadx.core.dex.visitors.ClassModifier;
|
||||
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||
import jadx.core.dex.visitors.ConstructorVisitor;
|
||||
@@ -25,6 +26,7 @@ import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.InitCodeVariables;
|
||||
import jadx.core.dex.visitors.MarkFinallyVisitor;
|
||||
import jadx.core.dex.visitors.MethodInlineVisitor;
|
||||
import jadx.core.dex.visitors.MethodInvokeVisitor;
|
||||
import jadx.core.dex.visitors.ModVisitor;
|
||||
import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||
import jadx.core.dex.visitors.ProcessAnonymous;
|
||||
@@ -83,6 +85,7 @@ public class Jadx {
|
||||
passes.add(new InitCodeVariables());
|
||||
passes.add(new MarkFinallyVisitor());
|
||||
passes.add(new ConstInlineVisitor());
|
||||
passes.add(new AttachMethodDetails());
|
||||
passes.add(new TypeInferenceVisitor());
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoApplyVisitor());
|
||||
@@ -102,6 +105,7 @@ public class Jadx {
|
||||
passes.add(new CleanRegions());
|
||||
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new MethodInvokeVisitor());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new CheckRegions());
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
@@ -12,21 +13,26 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.GenericInfo;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
@@ -34,6 +40,9 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
|
||||
import static jadx.core.utils.Utils.isEmpty;
|
||||
import static jadx.core.utils.Utils.notEmpty;
|
||||
|
||||
/**
|
||||
* Classes list for import into classpath graph
|
||||
*/
|
||||
@@ -45,123 +54,132 @@ public class ClsSet {
|
||||
private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/');
|
||||
|
||||
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
|
||||
private static final int VERSION = 2;
|
||||
private static final int VERSION = 3;
|
||||
|
||||
private static final String STRING_CHARSET = "US-ASCII";
|
||||
|
||||
private static final NClass[] EMPTY_NCLASS_ARRAY = new NClass[0];
|
||||
private static final ArgType[] EMPTY_ARGTYPE_ARRAY = new ArgType[0];
|
||||
|
||||
private enum TypeEnum {
|
||||
WILDCARD, GENERIC, GENERIC_TYPE, OBJECT, ARRAY, PRIMITIVE
|
||||
private final RootNode root;
|
||||
|
||||
public ClsSet(RootNode root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
private NClass[] classes;
|
||||
private enum TypeEnum {
|
||||
WILDCARD,
|
||||
GENERIC,
|
||||
GENERIC_TYPE_VARIABLE,
|
||||
OUTER_GENERIC,
|
||||
OBJECT,
|
||||
ARRAY,
|
||||
PRIMITIVE
|
||||
}
|
||||
|
||||
private ClspClass[] classes;
|
||||
|
||||
public void loadFromClstFile() throws IOException, DecodeException {
|
||||
long startTime = System.currentTimeMillis();
|
||||
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
|
||||
if (input == null) {
|
||||
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
|
||||
}
|
||||
load(input);
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
long time = System.currentTimeMillis() - startTime;
|
||||
int methodsCount = Arrays.stream(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
|
||||
LOG.debug("Load class set in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadFrom(RootNode root) {
|
||||
List<ClassNode> list = root.getClasses(true);
|
||||
Map<String, NClass> names = new HashMap<>(list.size());
|
||||
Map<String, ClspClass> names = new HashMap<>(list.size());
|
||||
int k = 0;
|
||||
for (ClassNode cls : list) {
|
||||
String clsRawName = cls.getRawName();
|
||||
if (cls.getAccessFlags().isPublic()) {
|
||||
cls.load();
|
||||
NClass nClass = new NClass(clsRawName, k);
|
||||
if (names.put(clsRawName, nClass) != null) {
|
||||
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||
}
|
||||
k++;
|
||||
nClass.setGenerics(cls.getGenerics());
|
||||
nClass.setMethods(getMethodsDetails(cls));
|
||||
} else {
|
||||
names.put(clsRawName, null);
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
String clsRawName = clsType.getObject();
|
||||
cls.load();
|
||||
ClspClass nClass = new ClspClass(clsType, k);
|
||||
if (names.put(clsRawName, nClass) != null) {
|
||||
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||
}
|
||||
k++;
|
||||
nClass.setTypeParameters(cls.getGenericTypeParameters());
|
||||
nClass.setMethods(getMethodsDetails(cls));
|
||||
}
|
||||
classes = new NClass[k];
|
||||
classes = new ClspClass[k];
|
||||
k = 0;
|
||||
for (ClassNode cls : list) {
|
||||
if (cls.getAccessFlags().isPublic()) {
|
||||
NClass nClass = getCls(cls.getRawName(), names);
|
||||
if (nClass == null) {
|
||||
throw new JadxRuntimeException("Missing class: " + cls);
|
||||
}
|
||||
nClass.setParents(makeParentsArray(cls, names));
|
||||
classes[k] = nClass;
|
||||
k++;
|
||||
ClspClass nClass = getCls(cls, names);
|
||||
if (nClass == null) {
|
||||
throw new JadxRuntimeException("Missing class: " + cls);
|
||||
}
|
||||
nClass.setParents(makeParentsArray(cls));
|
||||
classes[k] = nClass;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
private List<NMethod> getMethodsDetails(ClassNode cls) {
|
||||
List<NMethod> methods = new ArrayList<>();
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
AccessInfo accessFlags = m.getAccessFlags();
|
||||
if (accessFlags.isPublic() || accessFlags.isProtected()) {
|
||||
processMethodDetails(methods, m, accessFlags);
|
||||
}
|
||||
private List<ClspMethod> getMethodsDetails(ClassNode cls) {
|
||||
List<MethodNode> methodsList = cls.getMethods();
|
||||
List<ClspMethod> methods = new ArrayList<>(methodsList.size());
|
||||
for (MethodNode mth : methodsList) {
|
||||
processMethodDetails(mth, methods);
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
private void processMethodDetails(List<NMethod> methods, MethodNode mth, AccessInfo accessFlags) {
|
||||
List<ArgType> args = mth.getArgTypes();
|
||||
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++) {
|
||||
ArgType argType = args.get(i);
|
||||
if (argType.isGeneric() || argType.isGenericType()) {
|
||||
genericArgs[i] = argType;
|
||||
genericArg = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ArgType retType = mth.getReturnType();
|
||||
if (!retType.isGeneric() && !retType.isGenericType()) {
|
||||
retType = null;
|
||||
private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) {
|
||||
AccessInfo accessFlags = mth.getAccessFlags();
|
||||
if (accessFlags.isPrivate()) {
|
||||
return;
|
||||
}
|
||||
ArgType genericRetType = mth.getReturnType();
|
||||
boolean varArgs = accessFlags.isVarArgs();
|
||||
if (genericArg || retType != null || varArgs) {
|
||||
methods.add(new NMethod(mth.getMethodInfo().getShortId(), genericArgs, retType, varArgs));
|
||||
List<ArgType> throwList = mth.getThrows();
|
||||
List<GenericTypeParameter> typeParameters = mth.getTypeParameters();
|
||||
// add only methods with additional info
|
||||
if (varArgs
|
||||
|| notEmpty(throwList)
|
||||
|| notEmpty(typeParameters)
|
||||
|| genericRetType.containsGeneric()
|
||||
|| mth.containsGenericArgs()
|
||||
|| mth.isArgsOverloaded()) {
|
||||
ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(),
|
||||
mth.getArgTypes(), genericRetType,
|
||||
typeParameters, varArgs, throwList);
|
||||
methods.add(clspMethod);
|
||||
}
|
||||
}
|
||||
|
||||
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
|
||||
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
|
||||
public static ArgType[] makeParentsArray(ClassNode cls) {
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
NClass c = getCls(superClass.getObject(), names);
|
||||
if (c != null) {
|
||||
parents.add(c);
|
||||
}
|
||||
if (superClass == null) {
|
||||
// cls is java.lang.Object
|
||||
return EMPTY_ARGTYPE_ARRAY;
|
||||
}
|
||||
ArgType[] parents = new ArgType[1 + cls.getInterfaces().size()];
|
||||
parents[0] = superClass;
|
||||
int k = 1;
|
||||
for (ArgType iface : cls.getInterfaces()) {
|
||||
NClass c = getCls(iface.getObject(), names);
|
||||
if (c != null) {
|
||||
parents.add(c);
|
||||
}
|
||||
parents[k] = iface;
|
||||
k++;
|
||||
}
|
||||
int size = parents.size();
|
||||
if (size == 0) {
|
||||
return EMPTY_NCLASS_ARRAY;
|
||||
}
|
||||
return parents.toArray(new NClass[size]);
|
||||
return parents;
|
||||
}
|
||||
|
||||
private static NClass getCls(String fullName, Map<String, NClass> names) {
|
||||
NClass cls = names.get(fullName);
|
||||
private static ClspClass getCls(ClassNode cls, Map<String, ClspClass> names) {
|
||||
return getCls(cls.getRawName(), names);
|
||||
}
|
||||
|
||||
private static ClspClass getCls(ArgType clsType, Map<String, ClspClass> names) {
|
||||
return getCls(clsType.getObject(), names);
|
||||
}
|
||||
|
||||
private static ClspClass getCls(String fullName, Map<String, ClspClass> names) {
|
||||
ClspClass cls = names.get(fullName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Class not found: {}", fullName);
|
||||
}
|
||||
@@ -182,16 +200,25 @@ public class ClsSet {
|
||||
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
|
||||
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
|
||||
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME;
|
||||
out.putNextEntry(new ZipEntry(clst));
|
||||
save(out);
|
||||
boolean clstReplaced = false;
|
||||
ZipEntry entry = in.getNextEntry();
|
||||
while (entry != null) {
|
||||
if (!entry.getName().equals(clst)) {
|
||||
out.putNextEntry(new ZipEntry(entry.getName()));
|
||||
String entryName = entry.getName();
|
||||
ZipEntry copyEntry = new ZipEntry(entryName);
|
||||
copyEntry.setLastModifiedTime(entry.getLastModifiedTime()); // preserve modified time
|
||||
out.putNextEntry(copyEntry);
|
||||
if (entryName.equals(clst)) {
|
||||
save(out);
|
||||
clstReplaced = true;
|
||||
} else {
|
||||
FileUtils.copyStream(in, out);
|
||||
}
|
||||
entry = in.getNextEntry();
|
||||
}
|
||||
if (!clstReplaced) {
|
||||
out.putNextEntry(new ZipEntry(clst));
|
||||
save(out);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
||||
@@ -203,76 +230,90 @@ public class ClsSet {
|
||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||
out.writeByte(VERSION);
|
||||
|
||||
LOG.info("Classes count: {}", classes.length);
|
||||
Map<String, NClass> names = new HashMap<>(classes.length);
|
||||
Map<String, ClspClass> names = new HashMap<>(classes.length);
|
||||
out.writeInt(classes.length);
|
||||
for (NClass cls : classes) {
|
||||
writeString(out, cls.getName());
|
||||
names.put(cls.getName(), cls);
|
||||
for (ClspClass cls : classes) {
|
||||
String clsName = cls.getName();
|
||||
writeString(out, clsName);
|
||||
names.put(clsName, cls);
|
||||
}
|
||||
for (NClass cls : classes) {
|
||||
NClass[] parents = cls.getParents();
|
||||
out.writeByte(parents.length);
|
||||
for (NClass parent : parents) {
|
||||
out.writeInt(parent.getId());
|
||||
}
|
||||
writeGenerics(out, cls, names);
|
||||
List<NMethod> methods = cls.getMethodsList();
|
||||
out.writeByte(methods.size());
|
||||
for (NMethod method : methods) {
|
||||
for (ClspClass cls : classes) {
|
||||
writeArgTypesArray(out, cls.getParents(), names);
|
||||
writeGenericTypeParameters(out, cls.getTypeParameters(), names);
|
||||
List<ClspMethod> methods = cls.getSortedMethodsList();
|
||||
out.writeShort(methods.size());
|
||||
for (ClspMethod method : methods) {
|
||||
writeMethod(out, method, names);
|
||||
}
|
||||
}
|
||||
int methodsCount = Arrays.stream(classes).mapToInt(c -> c.getMethodsMap().size()).sum();
|
||||
LOG.info("Classes: {}, methods: {}, file size: {}B", classes.length, methodsCount, out.size());
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
ArgType[] argTypes = method.getGenericArgs();
|
||||
if (argTypes == null) {
|
||||
private static void writeGenericTypeParameters(DataOutputStream out,
|
||||
List<GenericTypeParameter> typeParameters,
|
||||
Map<String, ClspClass> names) throws IOException {
|
||||
if (isEmpty(typeParameters)) {
|
||||
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);
|
||||
}
|
||||
writeUnsignedByte(out, typeParameters.size());
|
||||
for (GenericTypeParameter typeParameter : typeParameters) {
|
||||
writeArgType(out, typeParameter.getTypeVariable(), names);
|
||||
writeArgTypesList(out, typeParameter.getExtendsList(), names);
|
||||
}
|
||||
}
|
||||
if (method.getReturnType() == null) {
|
||||
out.writeBoolean(false);
|
||||
} else {
|
||||
out.writeBoolean(true);
|
||||
writeArgType(out, method.getReturnType(), names);
|
||||
}
|
||||
out.writeBoolean(method.isVarArgs());
|
||||
}
|
||||
|
||||
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, NClass> names) throws IOException {
|
||||
if (argType.getWildcardType() != null) {
|
||||
private static void writeMethod(DataOutputStream out, ClspMethod method, Map<String, ClspClass> names) throws IOException {
|
||||
MethodInfo methodInfo = method.getMethodInfo();
|
||||
writeString(out, methodInfo.getName());
|
||||
writeArgTypesList(out, methodInfo.getArgumentsTypes(), names);
|
||||
writeArgType(out, methodInfo.getReturnType(), names);
|
||||
|
||||
writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names);
|
||||
writeArgType(out, method.getReturnType(), names);
|
||||
writeGenericTypeParameters(out, method.getTypeParameters(), names);
|
||||
out.writeBoolean(method.isVarArg());
|
||||
writeArgTypesList(out, method.getThrows(), names);
|
||||
}
|
||||
|
||||
private static void writeArgTypesList(DataOutputStream out, List<ArgType> list, Map<String, ClspClass> names) throws IOException {
|
||||
int size = list.size();
|
||||
writeUnsignedByte(out, size);
|
||||
if (size != 0) {
|
||||
for (ArgType type : list) {
|
||||
writeArgType(out, type, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeArgTypesArray(DataOutputStream out, @Nullable ArgType[] arr, Map<String, ClspClass> names) throws IOException {
|
||||
if (arr == null) {
|
||||
out.writeByte(-1);
|
||||
return;
|
||||
}
|
||||
int size = arr.length;
|
||||
out.writeByte(size);
|
||||
if (size != 0) {
|
||||
for (ArgType type : arr) {
|
||||
writeArgType(out, type, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, ClspClass> names) throws IOException {
|
||||
if (argType == null) {
|
||||
out.writeByte(-1);
|
||||
return;
|
||||
}
|
||||
if (argType.isPrimitive()) {
|
||||
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
|
||||
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
|
||||
} else if (argType.getOuterType() != null) {
|
||||
out.writeByte(TypeEnum.OUTER_GENERIC.ordinal());
|
||||
writeArgType(out, argType.getOuterType(), names);
|
||||
writeArgType(out, argType.getInnerType(), names);
|
||||
} else if (argType.getWildcardType() != null) {
|
||||
out.writeByte(TypeEnum.WILDCARD.ordinal());
|
||||
ArgType.WildcardBound bound = argType.getWildcardBound();
|
||||
out.writeByte(bound.getNum());
|
||||
@@ -281,28 +322,18 @@ public class ClsSet {
|
||||
}
|
||||
} else if (argType.isGeneric()) {
|
||||
out.writeByte(TypeEnum.GENERIC.ordinal());
|
||||
out.writeInt(names.get(argType.getObject()).getId());
|
||||
out.writeInt(getCls(argType, names).getId());
|
||||
ArgType[] types = argType.getGenericTypes();
|
||||
if (types == null) {
|
||||
out.writeByte(0);
|
||||
} else {
|
||||
out.writeByte(types.length);
|
||||
for (ArgType type : types) {
|
||||
writeArgType(out, type, names);
|
||||
}
|
||||
}
|
||||
writeArgTypesArray(out, types, names);
|
||||
} else if (argType.isGenericType()) {
|
||||
out.writeByte(TypeEnum.GENERIC_TYPE.ordinal());
|
||||
out.writeByte(TypeEnum.GENERIC_TYPE_VARIABLE.ordinal());
|
||||
writeString(out, argType.getObject());
|
||||
} else if (argType.isObject()) {
|
||||
out.writeByte(TypeEnum.OBJECT.ordinal());
|
||||
out.writeInt(names.get(argType.getObject()).getId());
|
||||
out.writeInt(getCls(argType, names).getId());
|
||||
} else if (argType.isArray()) {
|
||||
out.writeByte(TypeEnum.ARRAY.ordinal());
|
||||
writeArgType(out, argType.getArrayElement(), names);
|
||||
} else if (argType.isPrimitive()) {
|
||||
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
|
||||
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Cannot save type: " + argType);
|
||||
}
|
||||
@@ -330,7 +361,7 @@ public class ClsSet {
|
||||
}
|
||||
|
||||
private void load(InputStream input) throws IOException, DecodeException {
|
||||
try (DataInputStream in = new DataInputStream(input)) {
|
||||
try (DataInputStream in = new DataInputStream(new BufferedInputStream(input))) {
|
||||
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
|
||||
int readHeaderLength = in.read(header);
|
||||
int version = in.readByte();
|
||||
@@ -339,87 +370,119 @@ public class ClsSet {
|
||||
|| version != VERSION) {
|
||||
throw new DecodeException("Wrong jadx class set header");
|
||||
}
|
||||
int count = in.readInt();
|
||||
classes = new NClass[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
int clsCount = in.readInt();
|
||||
classes = new ClspClass[clsCount];
|
||||
for (int i = 0; i < clsCount; i++) {
|
||||
String name = readString(in);
|
||||
classes[i] = new NClass(name, i);
|
||||
classes[i] = new ClspClass(ArgType.object(name), i);
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
int pCount = in.readByte();
|
||||
NClass[] parents = new NClass[pCount];
|
||||
for (int j = 0; j < pCount; j++) {
|
||||
parents[j] = classes[in.readInt()];
|
||||
}
|
||||
NClass nClass = classes[i];
|
||||
nClass.setParents(parents);
|
||||
nClass.setGenerics(readGenerics(in));
|
||||
nClass.setMethods(readClsMethods(in));
|
||||
for (int i = 0; i < clsCount; i++) {
|
||||
ClspClass nClass = classes[i];
|
||||
ClassInfo clsInfo = ClassInfo.fromType(root, nClass.getClsType());
|
||||
nClass.setParents(readArgTypesArray(in));
|
||||
nClass.setTypeParameters(readGenericTypeParameters(in));
|
||||
nClass.setMethods(readClsMethods(in, clsInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<GenericInfo> readGenerics(DataInputStream in) throws IOException {
|
||||
int count = in.readByte();
|
||||
private List<GenericTypeParameter> readGenericTypeParameters(DataInputStream in) throws IOException {
|
||||
int count = readUnsignedByte(in);
|
||||
if (count == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<GenericInfo> list = new ArrayList<>(count);
|
||||
List<GenericTypeParameter> 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));
|
||||
ArgType typeVariable = readArgType(in);
|
||||
List<ArgType> extendsList = readArgTypesList(in);
|
||||
list.add(new GenericTypeParameter(typeVariable, extendsList));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private List<NMethod> readClsMethods(DataInputStream in) throws IOException {
|
||||
int mCount = in.readByte();
|
||||
List<NMethod> methods = new ArrayList<>(mCount);
|
||||
private List<ClspMethod> readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException {
|
||||
int mCount = in.readShort();
|
||||
List<ClspMethod> methods = new ArrayList<>(mCount);
|
||||
for (int j = 0; j < mCount; j++) {
|
||||
methods.add(readMethod(in));
|
||||
methods.add(readMethod(in, clsInfo));
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
private NMethod readMethod(DataInputStream in) throws IOException {
|
||||
String shortId = readLongString(in);
|
||||
int argCount = in.readByte();
|
||||
ArgType[] argTypes = null;
|
||||
for (int i = 0; i < argCount; i++) {
|
||||
int index = in.readByte();
|
||||
ArgType argType = readArgType(in);
|
||||
if (argTypes == null) {
|
||||
argTypes = new ArgType[index + 1];
|
||||
}
|
||||
argTypes[index] = argType;
|
||||
private ClspMethod readMethod(DataInputStream in, ClassInfo clsInfo) throws IOException {
|
||||
String name = readString(in);
|
||||
List<ArgType> argTypes = readArgTypesList(in);
|
||||
ArgType retType = readArgType(in);
|
||||
List<ArgType> genericArgTypes = readArgTypesList(in);
|
||||
if (genericArgTypes.isEmpty() || Objects.equals(genericArgTypes, argTypes)) {
|
||||
genericArgTypes = argTypes;
|
||||
}
|
||||
ArgType retType = in.readBoolean() ? readArgType(in) : null;
|
||||
ArgType genericRetType = readArgType(in);
|
||||
if (Objects.equals(genericRetType, retType)) {
|
||||
genericRetType = retType;
|
||||
}
|
||||
List<GenericTypeParameter> typeParameters = readGenericTypeParameters(in);
|
||||
boolean varArgs = in.readBoolean();
|
||||
return new NMethod(shortId, argTypes, retType, varArgs);
|
||||
List<ArgType> throwList = readArgTypesList(in);
|
||||
MethodInfo methodInfo = MethodInfo.fromDetails(root, clsInfo, name, argTypes, retType);
|
||||
return new ClspMethod(methodInfo,
|
||||
genericArgTypes, genericRetType,
|
||||
typeParameters, varArgs, throwList);
|
||||
}
|
||||
|
||||
private List<ArgType> readArgTypesList(DataInputStream in) throws IOException {
|
||||
int count = in.readByte();
|
||||
if (count == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ArgType> list = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
list.add(readArgType(in));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ArgType[] readArgTypesArray(DataInputStream in) throws IOException {
|
||||
int count = in.readByte();
|
||||
if (count == -1) {
|
||||
return null;
|
||||
}
|
||||
if (count == 0) {
|
||||
return EMPTY_ARGTYPE_ARRAY;
|
||||
}
|
||||
ArgType[] arr = new ArgType[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
arr[i] = readArgType(in);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
private ArgType readArgType(DataInputStream in) throws IOException {
|
||||
int ordinal = in.readByte();
|
||||
if (ordinal == -1) {
|
||||
return null;
|
||||
}
|
||||
if (ordinal >= TypeEnum.values().length) {
|
||||
throw new JadxRuntimeException("Incorrect ordinal for type enum: " + ordinal);
|
||||
}
|
||||
switch (TypeEnum.values()[ordinal]) {
|
||||
case WILDCARD:
|
||||
int bounds = in.readByte();
|
||||
return bounds == 0
|
||||
? ArgType.wildcard()
|
||||
: ArgType.wildcard(readArgType(in), ArgType.WildcardBound.getByNum(bounds));
|
||||
ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte());
|
||||
if (bound == ArgType.WildcardBound.UNBOUND) {
|
||||
return ArgType.WILDCARD;
|
||||
}
|
||||
ArgType objType = readArgType(in);
|
||||
return ArgType.wildcard(objType, bound);
|
||||
|
||||
case OUTER_GENERIC:
|
||||
ArgType outerType = readArgType(in);
|
||||
ArgType innerType = readArgType(in);
|
||||
return ArgType.outerGeneric(outerType, innerType);
|
||||
|
||||
case GENERIC:
|
||||
String obj = classes[in.readInt()].getName();
|
||||
int typeLength = in.readByte();
|
||||
ArgType clsType = classes[in.readInt()].getClsType();
|
||||
int typeLength = readUnsignedByte(in);
|
||||
ArgType[] generics;
|
||||
if (typeLength == 0) {
|
||||
generics = null;
|
||||
@@ -429,13 +492,13 @@ public class ClsSet {
|
||||
generics[i] = readArgType(in);
|
||||
}
|
||||
}
|
||||
return ArgType.generic(obj, generics);
|
||||
return ArgType.generic(clsType, generics);
|
||||
|
||||
case GENERIC_TYPE:
|
||||
case GENERIC_TYPE_VARIABLE:
|
||||
return ArgType.genericType(readString(in));
|
||||
|
||||
case OBJECT:
|
||||
return ArgType.object(classes[in.readInt()].getName());
|
||||
return classes[in.readInt()].getClsType();
|
||||
|
||||
case ARRAY:
|
||||
return ArgType.array(readArgType(in));
|
||||
@@ -451,23 +514,16 @@ public class ClsSet {
|
||||
|
||||
private static void writeString(DataOutputStream out, String name) throws IOException {
|
||||
byte[] bytes = name.getBytes(STRING_CHARSET);
|
||||
out.writeByte(bytes.length);
|
||||
out.write(bytes);
|
||||
}
|
||||
|
||||
private static void writeLongString(DataOutputStream out, String name) throws IOException {
|
||||
byte[] bytes = name.getBytes(STRING_CHARSET);
|
||||
out.writeShort(bytes.length);
|
||||
int len = bytes.length;
|
||||
if (len >= 0xFF) {
|
||||
throw new JadxRuntimeException("String is too long: " + name);
|
||||
}
|
||||
writeUnsignedByte(out, bytes.length);
|
||||
out.write(bytes);
|
||||
}
|
||||
|
||||
private static String readString(DataInputStream in) throws IOException {
|
||||
int len = in.readByte();
|
||||
return readString(in, len);
|
||||
}
|
||||
|
||||
private static String readLongString(DataInputStream in) throws IOException {
|
||||
int len = in.readShort();
|
||||
int len = readUnsignedByte(in);
|
||||
return readString(in, len);
|
||||
}
|
||||
|
||||
@@ -485,12 +541,23 @@ public class ClsSet {
|
||||
return new String(bytes, STRING_CHARSET);
|
||||
}
|
||||
|
||||
private static void writeUnsignedByte(DataOutputStream out, int value) throws IOException {
|
||||
if (value < 0 || value >= 0xFF) {
|
||||
throw new JadxRuntimeException("Unsigned byte value is too big: " + value);
|
||||
}
|
||||
out.writeByte(value);
|
||||
}
|
||||
|
||||
private static int readUnsignedByte(DataInputStream in) throws IOException {
|
||||
return ((int) in.readByte()) & 0xFF;
|
||||
}
|
||||
|
||||
public int getClassesCount() {
|
||||
return classes.length;
|
||||
}
|
||||
|
||||
public void addToMap(Map<String, NClass> nameMap) {
|
||||
for (NClass cls : classes) {
|
||||
public void addToMap(Map<String, ClspClass> nameMap) {
|
||||
for (ClspClass cls : classes) {
|
||||
nameMap.put(cls.getName(), cls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
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.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
|
||||
/**
|
||||
* Class node in classpath graph
|
||||
*/
|
||||
public class ClspClass {
|
||||
|
||||
private final ArgType clsType;
|
||||
private final int id;
|
||||
private ArgType[] parents;
|
||||
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
|
||||
private List<GenericTypeParameter> typeParameters = Collections.emptyList();
|
||||
|
||||
public ClspClass(ArgType clsType, int id) {
|
||||
this.clsType = clsType;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return clsType.getObject();
|
||||
}
|
||||
|
||||
public ArgType getClsType() {
|
||||
return clsType;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public ArgType[] getParents() {
|
||||
return parents;
|
||||
}
|
||||
|
||||
public void setParents(ArgType[] parents) {
|
||||
this.parents = parents;
|
||||
}
|
||||
|
||||
public Map<String, ClspMethod> getMethodsMap() {
|
||||
return methodsMap;
|
||||
}
|
||||
|
||||
public List<ClspMethod> getSortedMethodsList() {
|
||||
List<ClspMethod> list = new ArrayList<>(methodsMap.size());
|
||||
list.addAll(methodsMap.values());
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setMethodsMap(Map<String, ClspMethod> methodsMap) {
|
||||
this.methodsMap = Objects.requireNonNull(methodsMap);
|
||||
}
|
||||
|
||||
public void setMethods(List<ClspMethod> methods) {
|
||||
Map<String, ClspMethod> map = new HashMap<>(methods.size());
|
||||
for (ClspMethod mth : methods) {
|
||||
map.put(mth.getMethodInfo().getShortId(), mth);
|
||||
}
|
||||
setMethodsMap(map);
|
||||
}
|
||||
|
||||
public List<GenericTypeParameter> getTypeParameters() {
|
||||
return typeParameters;
|
||||
}
|
||||
|
||||
public void setTypeParameters(List<GenericTypeParameter> typeParameters) {
|
||||
this.typeParameters = typeParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return clsType.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ClspClass nClass = (ClspClass) o;
|
||||
return clsType.equals(nClass.clsType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return clsType.toString();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ 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.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -26,13 +28,18 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
public class ClspGraph {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
||||
|
||||
private final RootNode root;
|
||||
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
private Map<String, NClass> nameMap;
|
||||
private Map<String, ClspClass> nameMap;
|
||||
|
||||
private final Set<String> missingClasses = new HashSet<>();
|
||||
|
||||
public ClspGraph(RootNode rootNode) {
|
||||
this.root = rootNode;
|
||||
}
|
||||
|
||||
public void load() throws IOException, DecodeException {
|
||||
ClsSet set = new ClsSet();
|
||||
ClsSet set = new ClsSet(root);
|
||||
set.loadFromClstFile();
|
||||
addClasspath(set);
|
||||
}
|
||||
@@ -51,13 +58,13 @@ public class ClspGraph {
|
||||
throw new JadxRuntimeException("Classpath must be loaded first");
|
||||
}
|
||||
int size = classes.size();
|
||||
NClass[] nClasses = new NClass[size];
|
||||
ClspClass[] nClasses = new ClspClass[size];
|
||||
int k = 0;
|
||||
for (ClassNode cls : classes) {
|
||||
nClasses[k++] = addClass(cls);
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
|
||||
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,24 +72,44 @@ public class ClspGraph {
|
||||
return nameMap.containsKey(fullName);
|
||||
}
|
||||
|
||||
public NClass getClsDetails(ArgType type) {
|
||||
public ClspClass getClsDetails(ArgType type) {
|
||||
return nameMap.get(type.getObject());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public NMethod getMethodDetails(MethodInfo methodInfo) {
|
||||
NClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
|
||||
public IMethodDetails getMethodDetails(MethodInfo methodInfo) {
|
||||
ClspClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
ClspMethod clspMethod = getMethodFromClass(cls, methodInfo);
|
||||
if (clspMethod != null) {
|
||||
return clspMethod;
|
||||
}
|
||||
// deep search
|
||||
for (ArgType parent : cls.getParents()) {
|
||||
ClspClass clspParent = getClspClass(parent);
|
||||
if (clspParent != null) {
|
||||
ClspMethod methodFromParent = getMethodFromClass(clspParent, methodInfo);
|
||||
if (methodFromParent != null) {
|
||||
return methodFromParent;
|
||||
}
|
||||
}
|
||||
}
|
||||
// all other methods in known ClspClass are 'simple'
|
||||
return new SimpleMethodDetails(methodInfo);
|
||||
}
|
||||
|
||||
private ClspMethod getMethodFromClass(ClspClass cls, MethodInfo methodInfo) {
|
||||
return cls.getMethodsMap().get(methodInfo.getShortId());
|
||||
}
|
||||
|
||||
private NClass addClass(ClassNode cls) {
|
||||
String rawName = cls.getRawName();
|
||||
NClass nClass = new NClass(rawName, -1);
|
||||
nameMap.put(rawName, nClass);
|
||||
return nClass;
|
||||
private ClspClass addClass(ClassNode cls) {
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
String rawName = clsType.getObject();
|
||||
ClspClass clspClass = new ClspClass(clsType, -1);
|
||||
nameMap.put(rawName, clspClass);
|
||||
return clspClass;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,7 +134,7 @@ public class ClspGraph {
|
||||
if (clsName.equals(implClsName)) {
|
||||
return clsName;
|
||||
}
|
||||
NClass cls = nameMap.get(implClsName);
|
||||
ClspClass cls = nameMap.get(implClsName);
|
||||
if (cls == null) {
|
||||
missingClasses.add(clsName);
|
||||
return null;
|
||||
@@ -119,15 +146,18 @@ public class ClspGraph {
|
||||
return searchCommonParent(anc, cls);
|
||||
}
|
||||
|
||||
private String searchCommonParent(Set<String> anc, NClass cls) {
|
||||
for (NClass p : cls.getParents()) {
|
||||
String name = p.getName();
|
||||
private String searchCommonParent(Set<String> anc, ClspClass cls) {
|
||||
for (ArgType p : cls.getParents()) {
|
||||
String name = p.getObject();
|
||||
if (anc.contains(name)) {
|
||||
return name;
|
||||
}
|
||||
String r = searchCommonParent(anc, p);
|
||||
if (r != null) {
|
||||
return r;
|
||||
ClspClass nCls = getClspClass(p);
|
||||
if (nCls != null) {
|
||||
String r = searchCommonParent(anc, nCls);
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -138,7 +168,7 @@ public class ClspGraph {
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
NClass cls = nameMap.get(clsName);
|
||||
ClspClass cls = nameMap.get(clsName);
|
||||
if (cls == null) {
|
||||
missingClasses.add(clsName);
|
||||
return Collections.emptySet();
|
||||
@@ -152,15 +182,32 @@ public class ClspGraph {
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addAncestorsNames(NClass cls, Set<String> result) {
|
||||
private void addAncestorsNames(ClspClass cls, Set<String> result) {
|
||||
boolean isNew = result.add(cls.getName());
|
||||
if (isNew) {
|
||||
for (NClass p : cls.getParents()) {
|
||||
addAncestorsNames(p, result);
|
||||
for (ArgType parentType : cls.getParents()) {
|
||||
if (parentType == null) {
|
||||
continue;
|
||||
}
|
||||
ClspClass parentCls = getClspClass(parentType);
|
||||
if (parentCls != null) {
|
||||
addAncestorsNames(parentCls, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ClspClass getClspClass(ArgType clsType) {
|
||||
ClspClass clspClass = nameMap.get(clsType.getObject());
|
||||
if (clspClass == null) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("External class not found: {}", clsType.getObject());
|
||||
}
|
||||
}
|
||||
return clspClass;
|
||||
}
|
||||
|
||||
public void printMissingClasses() {
|
||||
int count = missingClasses.size();
|
||||
if (count == 0) {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
* Method node in classpath graph.
|
||||
*/
|
||||
public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
|
||||
|
||||
private final MethodInfo methodInfo;
|
||||
private final List<ArgType> argTypes;
|
||||
private final ArgType returnType;
|
||||
private final List<GenericTypeParameter> typeParameters;
|
||||
private final List<ArgType> throwList;
|
||||
private final boolean varArg;
|
||||
|
||||
public ClspMethod(MethodInfo methodInfo,
|
||||
List<ArgType> argTypes, ArgType returnType,
|
||||
List<GenericTypeParameter> typeParameters,
|
||||
boolean varArgs, List<ArgType> throwList) {
|
||||
this.methodInfo = methodInfo;
|
||||
this.argTypes = argTypes;
|
||||
this.returnType = returnType;
|
||||
this.typeParameters = typeParameters;
|
||||
this.throwList = throwList;
|
||||
this.varArg = varArgs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInfo getMethodInfo() {
|
||||
return methodInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getArgTypes() {
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
public boolean containsGenericArgs() {
|
||||
return !Objects.equals(argTypes, methodInfo.getArgumentsTypes());
|
||||
}
|
||||
|
||||
public int getArgsCount() {
|
||||
return argTypes.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GenericTypeParameter> getTypeParameters() {
|
||||
return typeParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getThrows() {
|
||||
return throwList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVarArg() {
|
||||
return varArg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof ClspMethod)) {
|
||||
return false;
|
||||
}
|
||||
ClspMethod other = (ClspMethod) o;
|
||||
return methodInfo.equals(other.methodInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return methodInfo.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ClspMethod other) {
|
||||
return this.methodInfo.compareTo(other.methodInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("ClspMth{");
|
||||
if (Utils.notEmpty(getTypeParameters())) {
|
||||
sb.append('<');
|
||||
sb.append(Utils.listToString(getTypeParameters()));
|
||||
sb.append("> ");
|
||||
}
|
||||
sb.append(getMethodInfo().getFullName());
|
||||
sb.append('(');
|
||||
sb.append(Utils.listToString(getArgTypes()));
|
||||
sb.append("):");
|
||||
sb.append(getReturnType());
|
||||
if (isVarArg()) {
|
||||
sb.append(" VARARG");
|
||||
}
|
||||
List<ArgType> throwsList = getThrows();
|
||||
if (Utils.notEmpty(throwsList)) {
|
||||
sb.append(" throws ").append(Utils.listToString(throwsList));
|
||||
}
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -45,13 +46,15 @@ public class ConvertToClsSet {
|
||||
LOG.info("Loaded: {}", inputFile.getFile());
|
||||
}
|
||||
|
||||
RootNode root = new RootNode(new JadxArgs());
|
||||
JadxArgs jadxArgs = new JadxArgs();
|
||||
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
|
||||
RootNode root = new RootNode(jadxArgs);
|
||||
root.load(inputFiles);
|
||||
|
||||
ClsSet set = new ClsSet();
|
||||
ClsSet set = new ClsSet(root);
|
||||
set.loadFrom(root);
|
||||
set.save(output);
|
||||
LOG.info("Output: {}", output);
|
||||
LOG.info("Output: {}, file size: {}B", output, output.toFile().length());
|
||||
LOG.info("done");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
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 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;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public NClass[] getParents() {
|
||||
return parents;
|
||||
}
|
||||
|
||||
public void setParents(NClass[] parents) {
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
NClass nClass = (NClass) o;
|
||||
return name.equals(nClass.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
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 implements Comparable<NMethod> {
|
||||
|
||||
private final String shortId;
|
||||
|
||||
/**
|
||||
* 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, @Nullable ArgType[] genericArgs, @Nullable ArgType retType, boolean varArgs) {
|
||||
this.shortId = shortId;
|
||||
this.genericArgs = genericArgs;
|
||||
this.retType = retType;
|
||||
this.varArgs = varArgs;
|
||||
}
|
||||
|
||||
public String getShortId() {
|
||||
return shortId;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
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=" + Arrays.toString(genericArgs)
|
||||
+ ", retType=" + retType
|
||||
+ ", varArgs=" + varArgs
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
|
||||
public class SimpleMethodDetails implements IMethodDetails {
|
||||
|
||||
private final MethodInfo methodInfo;
|
||||
|
||||
public SimpleMethodDetails(MethodInfo methodInfo) {
|
||||
this.methodInfo = methodInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInfo getMethodInfo() {
|
||||
return methodInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType getReturnType() {
|
||||
return methodInfo.getReturnType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getArgTypes() {
|
||||
return methodInfo.getArgumentsTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GenericTypeParameter> getTypeParameters() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getThrows() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVarArg() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleMethodDetails{" + methodInfo + '}';
|
||||
}
|
||||
}
|
||||
@@ -113,13 +113,11 @@ public class AnnotationGen {
|
||||
return paramName;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void addThrows(MethodNode mth, CodeWriter code) {
|
||||
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
|
||||
if (an != null) {
|
||||
Object exs = an.getDefaultValue();
|
||||
List<ArgType> throwList = mth.getThrows();
|
||||
if (!throwList.isEmpty()) {
|
||||
code.add(" throws ");
|
||||
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext();) {
|
||||
for (Iterator<ArgType> it = throwList.iterator(); it.hasNext();) {
|
||||
ArgType ex = it.next();
|
||||
classGen.useType(code, ex);
|
||||
if (it.hasNext()) {
|
||||
|
||||
@@ -29,7 +29,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.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
@@ -149,7 +149,7 @@ public class ClassGen {
|
||||
clsCode.attachDefinition(cls);
|
||||
clsCode.add(cls.getClassInfo().getAliasShortName());
|
||||
|
||||
addGenericMap(clsCode, cls.getGenerics(), true);
|
||||
addGenericTypeParameters(clsCode, cls.getGenericTypeParameters(), true);
|
||||
clsCode.add(' ');
|
||||
|
||||
ArgType sup = cls.getSuperClass();
|
||||
@@ -180,17 +180,17 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addGenericMap(CodeWriter code, List<GenericInfo> generics, boolean classDeclaration) {
|
||||
public boolean addGenericTypeParameters(CodeWriter code, List<GenericTypeParameter> generics, boolean classDeclaration) {
|
||||
if (generics == null || generics.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
code.add('<');
|
||||
int i = 0;
|
||||
for (GenericInfo genericInfo : generics) {
|
||||
for (GenericTypeParameter genericInfo : generics) {
|
||||
if (i != 0) {
|
||||
code.add(", ");
|
||||
}
|
||||
ArgType type = genericInfo.getGenericType();
|
||||
ArgType type = genericInfo.getTypeVariable();
|
||||
if (type.isGenericType()) {
|
||||
code.add(type.getObject());
|
||||
} else {
|
||||
|
||||
@@ -23,7 +23,7 @@ import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.ArithOp;
|
||||
import jadx.core.dex.instructions.CallMthInterface;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.ConstClassNode;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.FillArrayNode;
|
||||
@@ -53,7 +53,6 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.TypeUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -621,7 +620,7 @@ public class InsnGen {
|
||||
code.add("new ");
|
||||
useClass(code, insn.getClassType());
|
||||
ArgType argType = insn.getResult().getSVar().getCodeVar().getType();
|
||||
boolean genericCls = cls == null || !cls.getGenerics().isEmpty();
|
||||
boolean genericCls = cls == null || !cls.getGenericTypeParameters().isEmpty();
|
||||
if (argType != null
|
||||
&& argType.getGenericTypes() != null
|
||||
&& genericCls) {
|
||||
@@ -761,30 +760,31 @@ public class InsnGen {
|
||||
return useCls.getParentClass().getClassInfo();
|
||||
}
|
||||
|
||||
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
|
||||
@Nullable MethodNode callMth) throws CodegenException {
|
||||
void generateMethodArguments(CodeWriter code, BaseInvokeNode insn, int startArgNum,
|
||||
@Nullable MethodNode mthNode) throws CodegenException {
|
||||
int k = startArgNum;
|
||||
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
if (mthNode != null && mthNode.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
k++;
|
||||
}
|
||||
int argsCount = insn.getArgsCount();
|
||||
code.add('(');
|
||||
boolean firstArg = true;
|
||||
if (k < argsCount) {
|
||||
boolean overloaded = callMth != null && callMth.isArgsOverload();
|
||||
for (int i = k; i < argsCount; i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
if (arg.contains(AFlag.SKIP_ARG)) {
|
||||
continue;
|
||||
}
|
||||
if (SkipMethodArgsAttr.isSkip(callMth, i - startArgNum)) {
|
||||
int argOrigPos = i - startArgNum;
|
||||
if (SkipMethodArgsAttr.isSkip(mthNode, argOrigPos)) {
|
||||
continue;
|
||||
}
|
||||
if (!firstArg) {
|
||||
code.add(", ");
|
||||
} else {
|
||||
firstArg = false;
|
||||
}
|
||||
boolean cast = addArgCast(code, insn, callMth, arg, i - startArgNum, overloaded);
|
||||
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
|
||||
if (i == argsCount - 1 && processVarArg(code, insn, arg)) {
|
||||
continue;
|
||||
}
|
||||
addArg(code, arg, false);
|
||||
@@ -794,91 +794,29 @@ public class InsnGen {
|
||||
code.add(')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional cast for method argument.
|
||||
*/
|
||||
private boolean addArgCast(CodeWriter code, InsnNode insn, @Nullable MethodNode callMth,
|
||||
InsnArg arg, int origPos, boolean overloaded) {
|
||||
ArgType castType = null;
|
||||
if (callMth != null) {
|
||||
List<ArgType> argTypes = callMth.getArgTypes();
|
||||
ArgType origType = argTypes.get(origPos);
|
||||
if (origType.isGenericType() && !callMth.getParentClass().equals(mth.getParentClass())) {
|
||||
// cancel cast
|
||||
return false;
|
||||
}
|
||||
if (insn instanceof CallMthInterface && origType.containsGenericType()) {
|
||||
ArgType clsType;
|
||||
CallMthInterface mthCall = (CallMthInterface) insn;
|
||||
RegisterArg instanceArg = mthCall.getInstanceArg();
|
||||
if (instanceArg != null) {
|
||||
clsType = instanceArg.getType();
|
||||
} else {
|
||||
clsType = mthCall.getCallMth().getDeclClass().getType();
|
||||
}
|
||||
ArgType replacedType = TypeUtils.replaceClassGenerics(root, clsType, origType);
|
||||
if (replacedType != null) {
|
||||
castType = replacedType;
|
||||
}
|
||||
if (castType == null) {
|
||||
ArgType invReplType = TypeUtils.replaceMethodGenerics(root, insn, origType);
|
||||
if (invReplType != null) {
|
||||
castType = invReplType;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (castType == null) {
|
||||
castType = origType;
|
||||
}
|
||||
} else {
|
||||
castType = arg.getType();
|
||||
}
|
||||
// TODO: check castType for left type variables
|
||||
|
||||
if (isCastNeeded(arg, castType, overloaded)) {
|
||||
code.add('(');
|
||||
useType(code, castType);
|
||||
code.add(") ");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isCastNeeded(InsnArg arg, ArgType origType, boolean overloaded) {
|
||||
ArgType argType = arg.getType();
|
||||
if (arg.isLiteral() && ((LiteralArg) arg).getLiteral() == 0
|
||||
&& (argType.isObject() || argType.isArray())) {
|
||||
return true;
|
||||
}
|
||||
if (argType.equals(origType)) {
|
||||
return false;
|
||||
}
|
||||
return overloaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand varArgs from filled array.
|
||||
*/
|
||||
private boolean processVarArg(CodeWriter code, MethodNode callMth, InsnArg lastArg) throws CodegenException {
|
||||
if (callMth == null || !callMth.getAccessFlags().isVarArgs()) {
|
||||
private boolean processVarArg(CodeWriter code, BaseInvokeNode invokeInsn, InsnArg lastArg) throws CodegenException {
|
||||
if (!invokeInsn.contains(AFlag.VARARG_CALL)) {
|
||||
return false;
|
||||
}
|
||||
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
|
||||
return false;
|
||||
}
|
||||
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
|
||||
if (insn.getType() == InsnType.FILLED_NEW_ARRAY) {
|
||||
int count = insn.getArgsCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
InsnArg elemArg = insn.getArg(i);
|
||||
addArg(code, elemArg, false);
|
||||
if (i < count - 1) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
if (insn.getType() != InsnType.FILLED_NEW_ARRAY) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
int count = insn.getArgsCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
InsnArg elemArg = insn.getArg(i);
|
||||
addArg(code, elemArg, false);
|
||||
if (i < count - 1) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||
@@ -936,7 +874,7 @@ public class InsnGen {
|
||||
if (parentInsn.contains(AFlag.WRAPPED)) {
|
||||
return false;
|
||||
}
|
||||
return !callMthNode.getReturnType().equals(ArgType.VOID);
|
||||
return !callMthNode.isVoidReturn();
|
||||
}
|
||||
|
||||
private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
||||
|
||||
@@ -98,8 +98,12 @@ public class MethodGen {
|
||||
if (Consts.DEBUG) {
|
||||
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
|
||||
}
|
||||
if (clsAccFlags.isInterface() && !mth.isNoCode()) {
|
||||
// add 'default' for method with code in interface
|
||||
code.add("default ");
|
||||
}
|
||||
|
||||
if (classGen.addGenericMap(code, mth.getGenerics(), false)) {
|
||||
if (classGen.addGenericTypeParameters(code, mth.getTypeParameters(), false)) {
|
||||
code.add(' ');
|
||||
}
|
||||
if (ai.isConstructor()) {
|
||||
|
||||
@@ -60,6 +60,7 @@ public enum AFlag {
|
||||
FALL_THROUGH,
|
||||
|
||||
EXPLICIT_GENERICS,
|
||||
VARARG_CALL,
|
||||
|
||||
/**
|
||||
* Use constants with explicit type: cast '(byte) 1' or type letter '7L'
|
||||
|
||||
@@ -24,6 +24,7 @@ import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
@@ -79,6 +80,7 @@ public class AType<T extends IAttribute> {
|
||||
// instruction
|
||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
|
||||
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
|
||||
public static final AType<IMethodDetails> METHOD_DETAILS = new AType<>();
|
||||
|
||||
// register
|
||||
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.Set;
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
/**
|
||||
* Storage for different attribute types:
|
||||
@@ -19,6 +20,13 @@ import jadx.core.utils.Utils;
|
||||
*/
|
||||
public class AttributeStorage {
|
||||
|
||||
static {
|
||||
int flagsCount = AFlag.values().length;
|
||||
if (flagsCount >= 64) {
|
||||
throw new JadxRuntimeException("Try to reduce flags count to 64 for use one long in EnumSet, now " + flagsCount);
|
||||
}
|
||||
}
|
||||
|
||||
private final Set<AFlag> flags;
|
||||
private Map<AType<?>, IAttribute> attributes;
|
||||
|
||||
@@ -127,7 +135,7 @@ public class AttributeStorage {
|
||||
list.add(a.toString());
|
||||
}
|
||||
for (IAttribute a : attributes.values()) {
|
||||
list.add(a.toString());
|
||||
list.add(a.toAttrString());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -2,4 +2,8 @@ package jadx.core.dex.attributes;
|
||||
|
||||
public interface IAttribute {
|
||||
AType<? extends IAttribute> getType();
|
||||
|
||||
default String toAttrString() {
|
||||
return this.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
||||
|
||||
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
|
||||
if (clsIndex == DexNode.NO_INDEX) {
|
||||
return null;
|
||||
throw new JadxRuntimeException("NO_INDEX for class");
|
||||
}
|
||||
return fromType(dex.root(), dex.getType(clsIndex));
|
||||
}
|
||||
|
||||
@@ -5,12 +5,15 @@ import java.util.Map;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class InfoStorage {
|
||||
|
||||
private final Map<ArgType, ClassInfo> classes = new HashMap<>();
|
||||
private final Map<Integer, MethodInfo> methods = new HashMap<>();
|
||||
private final Map<FieldInfo, FieldInfo> fields = new HashMap<>();
|
||||
private final Map<Integer, MethodInfo> methods = new HashMap<>();
|
||||
// use only one MethodInfo instance
|
||||
private final Map<MethodInfo, MethodInfo> uniqueMethods = new HashMap<>();
|
||||
|
||||
public ClassInfo getCls(ArgType type) {
|
||||
return classes.get(type);
|
||||
@@ -31,10 +34,25 @@ public class InfoStorage {
|
||||
return methods.get(generateMethodLookupId(dex, mtdId));
|
||||
}
|
||||
|
||||
public MethodInfo putMethod(DexNode dex, int mthId, MethodInfo mth) {
|
||||
public MethodInfo putMethod(DexNode dex, int mthId, MethodInfo methodInfo) {
|
||||
synchronized (methods) {
|
||||
MethodInfo prev = methods.put(generateMethodLookupId(dex, mthId), mth);
|
||||
return prev == null ? mth : prev;
|
||||
MethodInfo uniqueMethodInfo = putMethod(methodInfo);
|
||||
MethodInfo prev = methods.put(generateMethodLookupId(dex, mthId), uniqueMethodInfo);
|
||||
if (prev != null) {
|
||||
throw new JadxRuntimeException("Method info already added: " + methodInfo);
|
||||
}
|
||||
return uniqueMethodInfo;
|
||||
}
|
||||
}
|
||||
|
||||
public MethodInfo putMethod(MethodInfo newMth) {
|
||||
synchronized (uniqueMethods) {
|
||||
MethodInfo prev = uniqueMethods.get(newMth);
|
||||
if (prev != null) {
|
||||
return prev;
|
||||
}
|
||||
uniqueMethods.put(newMth, newMth);
|
||||
return newMth;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.android.dex.MethodId;
|
||||
import com.android.dex.ProtoId;
|
||||
@@ -8,52 +11,48 @@ import com.android.dex.ProtoId;
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public final class MethodInfo {
|
||||
public final class MethodInfo implements Comparable<MethodInfo> {
|
||||
|
||||
private final String name;
|
||||
private final ArgType retType;
|
||||
private final List<ArgType> args;
|
||||
private final List<ArgType> argTypes;
|
||||
private final ClassInfo declClass;
|
||||
private final String shortId;
|
||||
private String alias;
|
||||
private boolean aliasFromPreset;
|
||||
|
||||
private MethodInfo(DexNode dex, int mthIndex) {
|
||||
MethodId mthId = dex.getMethodId(mthIndex);
|
||||
name = dex.getString(mthId.getNameIndex());
|
||||
alias = name;
|
||||
aliasFromPreset = false;
|
||||
declClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex());
|
||||
|
||||
ProtoId proto = dex.getProtoId(mthId.getProtoIndex());
|
||||
retType = dex.getType(proto.getReturnTypeIndex());
|
||||
args = dex.readParamList(proto.getParametersOffset());
|
||||
shortId = makeSignature(true);
|
||||
}
|
||||
|
||||
private MethodInfo(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
|
||||
this.name = name;
|
||||
this.alias = name;
|
||||
this.aliasFromPreset = false;
|
||||
this.declClass = declClass;
|
||||
this.args = args;
|
||||
this.argTypes = args;
|
||||
this.retType = retType;
|
||||
this.shortId = makeSignature(true);
|
||||
}
|
||||
|
||||
public static MethodInfo externalMth(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
|
||||
return new MethodInfo(declClass, name, args, retType);
|
||||
this.shortId = makeShortId(name, argTypes, retType);
|
||||
}
|
||||
|
||||
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
|
||||
MethodInfo mth = dex.root().getInfoStorage().getMethod(dex, mthIndex);
|
||||
if (mth != null) {
|
||||
return mth;
|
||||
MethodInfo storageMth = dex.root().getInfoStorage().getMethod(dex, mthIndex);
|
||||
if (storageMth != null) {
|
||||
return storageMth;
|
||||
}
|
||||
mth = new MethodInfo(dex, mthIndex);
|
||||
return dex.root().getInfoStorage().putMethod(dex, mthIndex, mth);
|
||||
MethodId mthId = dex.getMethodId(mthIndex);
|
||||
String mthName = dex.getString(mthId.getNameIndex());
|
||||
ClassInfo parentClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex());
|
||||
|
||||
ProtoId proto = dex.getProtoId(mthId.getProtoIndex());
|
||||
ArgType returnType = dex.getType(proto.getReturnTypeIndex());
|
||||
List<ArgType> args = dex.readParamList(proto.getParametersOffset());
|
||||
MethodInfo newMth = new MethodInfo(parentClass, mthName, args, returnType);
|
||||
return dex.root().getInfoStorage().putMethod(dex, mthIndex, newMth);
|
||||
}
|
||||
|
||||
public static MethodInfo fromDetails(RootNode rootNode, ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
|
||||
MethodInfo newMth = new MethodInfo(declClass, name, args, retType);
|
||||
return rootNode.getInfoStorage().putMethod(newMth);
|
||||
}
|
||||
|
||||
public String makeSignature(boolean includeRetType) {
|
||||
@@ -61,17 +60,29 @@ public final class MethodInfo {
|
||||
}
|
||||
|
||||
public String makeSignature(boolean useAlias, boolean includeRetType) {
|
||||
StringBuilder signature = new StringBuilder();
|
||||
signature.append(useAlias ? alias : name);
|
||||
signature.append('(');
|
||||
for (ArgType arg : args) {
|
||||
signature.append(TypeGen.signature(arg));
|
||||
return makeShortId(useAlias ? alias : name,
|
||||
argTypes,
|
||||
includeRetType ? retType : null);
|
||||
}
|
||||
|
||||
public static String makeShortId(String name, List<ArgType> argTypes, @Nullable ArgType retType) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(name);
|
||||
sb.append('(');
|
||||
for (ArgType arg : argTypes) {
|
||||
sb.append(TypeGen.signature(arg));
|
||||
}
|
||||
signature.append(')');
|
||||
if (includeRetType) {
|
||||
signature.append(TypeGen.signature(retType));
|
||||
sb.append(')');
|
||||
if (retType != null) {
|
||||
sb.append(TypeGen.signature(retType));
|
||||
}
|
||||
return signature.toString();
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public boolean isOverloadedBy(MethodInfo otherMthInfo) {
|
||||
return argTypes.size() == otherMthInfo.argTypes.size()
|
||||
&& name.equals(otherMthInfo.name)
|
||||
&& !Objects.equals(this.shortId, otherMthInfo.shortId);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -106,11 +117,11 @@ public final class MethodInfo {
|
||||
}
|
||||
|
||||
public List<ArgType> getArgumentsTypes() {
|
||||
return args;
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
public int getArgsCount() {
|
||||
return args.size();
|
||||
return argTypes.size();
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
@@ -143,10 +154,7 @@ public final class MethodInfo {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = declClass.hashCode();
|
||||
result = 31 * result + retType.hashCode();
|
||||
result = 31 * result + shortId.hashCode();
|
||||
return result;
|
||||
return shortId.hashCode() + 31 * declClass.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -159,13 +167,21 @@ public final class MethodInfo {
|
||||
}
|
||||
MethodInfo other = (MethodInfo) obj;
|
||||
return shortId.equals(other.shortId)
|
||||
&& retType.equals(other.retType)
|
||||
&& declClass.equals(other.declClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MethodInfo other) {
|
||||
int clsCmp = declClass.compareTo(other.declClass);
|
||||
if (clsCmp != 0) {
|
||||
return clsCmp;
|
||||
}
|
||||
return shortId.compareTo(other.shortId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return declClass.getFullName() + '.' + name
|
||||
+ '(' + Utils.listToString(args) + "):" + retType;
|
||||
+ '(' + Utils.listToString(argTypes) + "):" + retType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
public abstract class BaseInvokeNode extends InsnNode {
|
||||
public BaseInvokeNode(InsnType type, int argsCount) {
|
||||
super(type, argsCount);
|
||||
}
|
||||
|
||||
public abstract MethodInfo getCallMth();
|
||||
|
||||
@Nullable
|
||||
public abstract InsnArg getInstanceArg();
|
||||
|
||||
public abstract boolean isStaticCall();
|
||||
|
||||
/**
|
||||
* Return offset to match method args from {@link #getCallMth()}
|
||||
*/
|
||||
public abstract int getFirstArgOffset();
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
|
||||
public interface CallMthInterface {
|
||||
|
||||
MethodInfo getCallMth();
|
||||
|
||||
@Nullable
|
||||
RegisterArg getInstanceArg();
|
||||
|
||||
/**
|
||||
* Return offset to match method args from {@link #getCallMth()}
|
||||
*/
|
||||
int getFirstArgOffset();
|
||||
}
|
||||
@@ -7,11 +7,10 @@ import com.android.dx.io.instructions.DecodedInstruction;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
|
||||
public class InvokeNode extends InsnNode implements CallMthInterface {
|
||||
public final class InvokeNode extends BaseInvokeNode {
|
||||
|
||||
private final InvokeType type;
|
||||
private final MethodInfo mth;
|
||||
@@ -55,17 +54,18 @@ public class InvokeNode extends InsnNode implements CallMthInterface {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public RegisterArg getInstanceArg() {
|
||||
public InsnArg getInstanceArg() {
|
||||
if (type != InvokeType.STATIC && getArgsCount() > 0) {
|
||||
InsnArg firstArg = getArg(0);
|
||||
if (firstArg.isRegister()) {
|
||||
return ((RegisterArg) firstArg);
|
||||
}
|
||||
return getArg(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStaticCall() {
|
||||
return type == InvokeType.STATIC;
|
||||
}
|
||||
|
||||
public int getFirstArgOffset() {
|
||||
return type == InvokeType.STATIC ? 0 : 1;
|
||||
}
|
||||
@@ -89,6 +89,6 @@ public class InvokeNode extends InsnNode implements CallMthInterface {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ' ' + mth + " type: " + type;
|
||||
return super.toString() + " type: " + type + " call: " + mth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public abstract class ArgType {
|
||||
@@ -24,12 +23,13 @@ public abstract class ArgType {
|
||||
public static final ArgType LONG = primitive(PrimitiveType.LONG);
|
||||
public static final ArgType VOID = primitive(PrimitiveType.VOID);
|
||||
|
||||
public static final ArgType OBJECT = object(Consts.CLASS_OBJECT);
|
||||
public static final ArgType CLASS = object(Consts.CLASS_CLASS);
|
||||
public static final ArgType STRING = object(Consts.CLASS_STRING);
|
||||
public static final ArgType ENUM = object(Consts.CLASS_ENUM);
|
||||
public static final ArgType THROWABLE = object(Consts.CLASS_THROWABLE);
|
||||
public static final ArgType OBJECT = objectNoCache(Consts.CLASS_OBJECT);
|
||||
public static final ArgType CLASS = objectNoCache(Consts.CLASS_CLASS);
|
||||
public static final ArgType STRING = objectNoCache(Consts.CLASS_STRING);
|
||||
public static final ArgType ENUM = objectNoCache(Consts.CLASS_ENUM);
|
||||
public static final ArgType THROWABLE = objectNoCache(Consts.CLASS_THROWABLE);
|
||||
public static final ArgType OBJECT_ARRAY = array(OBJECT);
|
||||
public static final ArgType WILDCARD = wildcard();
|
||||
|
||||
public static final ArgType UNKNOWN = unknown(PrimitiveType.values());
|
||||
public static final ArgType UNKNOWN_OBJECT = unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY);
|
||||
@@ -66,10 +66,25 @@ public abstract class ArgType {
|
||||
return new PrimitiveArg(stype);
|
||||
}
|
||||
|
||||
public static ArgType object(String obj) {
|
||||
private static ArgType objectNoCache(String obj) {
|
||||
return new ObjectType(obj);
|
||||
}
|
||||
|
||||
public static ArgType object(String obj) {
|
||||
// TODO: add caching
|
||||
String cleanObjectName = Utils.cleanObjectName(obj);
|
||||
switch (cleanObjectName) {
|
||||
case Consts.CLASS_OBJECT:
|
||||
return OBJECT;
|
||||
case Consts.CLASS_STRING:
|
||||
return STRING;
|
||||
case Consts.CLASS_CLASS:
|
||||
return CLASS;
|
||||
default:
|
||||
return new ObjectType(cleanObjectName);
|
||||
}
|
||||
}
|
||||
|
||||
public static ArgType genericType(String type) {
|
||||
return new GenericType(type);
|
||||
}
|
||||
@@ -82,12 +97,16 @@ public abstract class ArgType {
|
||||
return new WildcardType(obj, bound);
|
||||
}
|
||||
|
||||
public static ArgType parseGenericSignature(String sign) {
|
||||
return new SignatureParser(sign).consumeType();
|
||||
public static ArgType generic(ArgType obj, ArgType... generics) {
|
||||
if (!obj.isObject()) {
|
||||
throw new IllegalArgumentException("Expected Object as ArgType, got: " + obj);
|
||||
}
|
||||
return new GenericObject(obj.getObject(), generics);
|
||||
}
|
||||
|
||||
public static ArgType generic(String obj, ArgType... generics) {
|
||||
return new GenericObject(obj, generics);
|
||||
String cleanObjectName = Utils.cleanObjectName(obj);
|
||||
return new GenericObject(cleanObjectName, generics);
|
||||
}
|
||||
|
||||
public static ArgType outerGeneric(ArgType genericOuterType, ArgType innerType) {
|
||||
@@ -160,7 +179,7 @@ public abstract class ArgType {
|
||||
protected final String objName;
|
||||
|
||||
public ObjectType(String obj) {
|
||||
this.objName = Utils.cleanObjectName(obj);
|
||||
this.objName = obj;
|
||||
this.hash = objName.hashCode();
|
||||
}
|
||||
|
||||
@@ -555,12 +574,12 @@ public abstract class ArgType {
|
||||
|
||||
public abstract PrimitiveType[] getPossibleTypes();
|
||||
|
||||
public static boolean isCastNeeded(DexNode dex, ArgType from, ArgType to) {
|
||||
public static boolean isCastNeeded(RootNode root, ArgType from, ArgType to) {
|
||||
if (from.equals(to)) {
|
||||
return false;
|
||||
}
|
||||
if (from.isObject() && to.isObject()
|
||||
&& dex.root().getClsp().isImplements(from.getObject(), to.getObject())) {
|
||||
&& root.getClsp().isImplements(from.getObject(), to.getObject())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -679,25 +698,44 @@ public abstract class ArgType {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public boolean containsGenericType() {
|
||||
public boolean containsGeneric() {
|
||||
if (isGeneric() || isGenericType()) {
|
||||
return true;
|
||||
}
|
||||
if (isArray()) {
|
||||
ArgType arrayElement = getArrayElement();
|
||||
if (arrayElement != null) {
|
||||
return arrayElement.containsGeneric();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsTypeVariable() {
|
||||
if (isGenericType()) {
|
||||
return true;
|
||||
}
|
||||
ArgType wildcardType = getWildcardType();
|
||||
if (wildcardType != null) {
|
||||
return wildcardType.containsGenericType();
|
||||
return wildcardType.containsTypeVariable();
|
||||
}
|
||||
if (isGeneric()) {
|
||||
ArgType[] genericTypes = getGenericTypes();
|
||||
if (genericTypes != null) {
|
||||
for (ArgType genericType : genericTypes) {
|
||||
if (genericType.containsGenericType()) {
|
||||
if (genericType.containsTypeVariable()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (isArray()) {
|
||||
ArgType arrayElement = getArrayElement();
|
||||
if (arrayElement != null) {
|
||||
return arrayElement.containsTypeVariable();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.CallMthInterface;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public final class ConstructorInsn extends InsnNode implements CallMthInterface {
|
||||
public final class ConstructorInsn extends BaseInvokeNode {
|
||||
|
||||
private final MethodInfo callMth;
|
||||
private final CallType callType;
|
||||
@@ -59,6 +59,7 @@ public final class ConstructorInsn extends InsnNode implements CallMthInterface
|
||||
this.callType = callType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInfo getCallMth() {
|
||||
return callMth;
|
||||
}
|
||||
@@ -93,6 +94,11 @@ public final class ConstructorInsn extends InsnNode implements CallMthInterface
|
||||
return callType == CallType.SELF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStaticCall() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirstArgOffset() {
|
||||
return 0;
|
||||
|
||||
@@ -54,7 +54,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
private AccessInfo accessFlags;
|
||||
private ArgType superClass;
|
||||
private List<ArgType> interfaces;
|
||||
private List<GenericInfo> generics = Collections.emptyList();
|
||||
private List<GenericTypeParameter> generics = Collections.emptyList();
|
||||
|
||||
private final List<MethodNode> methods;
|
||||
private final List<FieldNode> fields;
|
||||
@@ -80,6 +80,10 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
try {
|
||||
if (cls.getSupertypeIndex() == DexNode.NO_INDEX) {
|
||||
this.superClass = null;
|
||||
// only java.lang.Object don't have super class
|
||||
if (!clsInfo.getType().getObject().equals(Consts.CLASS_OBJECT)) {
|
||||
throw new JadxRuntimeException("No super class in " + clsInfo.getType());
|
||||
}
|
||||
} else {
|
||||
this.superClass = dex.getType(cls.getSupertypeIndex());
|
||||
}
|
||||
@@ -197,7 +201,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
}
|
||||
try {
|
||||
// parse class generic map
|
||||
generics = sp.consumeGenericMap();
|
||||
generics = sp.consumeGenericTypeParameters();
|
||||
// parse super class signature
|
||||
superClass = sp.consumeType();
|
||||
// parse interfaces signatures
|
||||
@@ -335,7 +339,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
public List<GenericInfo> getGenerics() {
|
||||
public List<GenericTypeParameter> getGenericTypeParameters() {
|
||||
return generics;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
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 + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import static jadx.core.utils.Utils.notEmpty;
|
||||
|
||||
public class GenericTypeParameter {
|
||||
private final ArgType typeVariable;
|
||||
private final List<ArgType> extendsList;
|
||||
|
||||
public GenericTypeParameter(ArgType typeVariable, List<ArgType> extendsList) {
|
||||
this.typeVariable = typeVariable;
|
||||
this.extendsList = extendsList;
|
||||
}
|
||||
|
||||
public ArgType getTypeVariable() {
|
||||
return typeVariable;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
GenericTypeParameter other = (GenericTypeParameter) o;
|
||||
return typeVariable.equals(other.typeVariable)
|
||||
&& extendsList.equals(other.extendsList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * typeVariable.hashCode() + extendsList.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(typeVariable);
|
||||
if (notEmpty(extendsList)) {
|
||||
sb.append(" extends ");
|
||||
sb.append(Utils.listToString(extendsList, " & "));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public interface IMethodDetails extends IAttribute {
|
||||
|
||||
MethodInfo getMethodInfo();
|
||||
|
||||
ArgType getReturnType();
|
||||
|
||||
List<ArgType> getArgTypes();
|
||||
|
||||
List<GenericTypeParameter> getTypeParameters();
|
||||
|
||||
List<ArgType> getThrows();
|
||||
|
||||
boolean isVarArg();
|
||||
|
||||
@Override
|
||||
default AType<IMethodDetails> getType() {
|
||||
return AType.METHOD_DETAILS;
|
||||
}
|
||||
|
||||
@Override
|
||||
default String toAttrString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("MD:");
|
||||
if (Utils.notEmpty(getTypeParameters())) {
|
||||
sb.append('<');
|
||||
sb.append(Utils.listToString(getTypeParameters()));
|
||||
sb.append(">:");
|
||||
}
|
||||
sb.append('(');
|
||||
sb.append(Utils.listToString(getArgTypes()));
|
||||
sb.append("):");
|
||||
sb.append(getReturnType());
|
||||
if (isVarArg()) {
|
||||
sb.append(" VARARG");
|
||||
}
|
||||
List<ArgType> throwsList = getThrows();
|
||||
if (Utils.notEmpty(throwsList)) {
|
||||
sb.append(" throws ").append(Utils.listToString(throwsList));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -404,7 +404,7 @@ public class InsnNode extends LineAttrNode {
|
||||
|
||||
protected void appendArgs(StringBuilder sb) {
|
||||
String argsStr = Utils.listToString(arguments);
|
||||
if (argsStr.length() < 60) {
|
||||
if (argsStr.length() < 120) {
|
||||
sb.append(argsStr);
|
||||
} else {
|
||||
// wrap args
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -16,8 +17,10 @@ import com.android.dex.Code;
|
||||
import com.android.dex.Code.CatchHandler;
|
||||
import com.android.dex.Code.Try;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
@@ -46,7 +49,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.utils.Utils.lockList;
|
||||
|
||||
public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadable, ICodeNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
|
||||
|
||||
private final MethodInfo mthInfo;
|
||||
@@ -66,7 +69,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
// additional info available after load, keep on unload
|
||||
private ArgType retType;
|
||||
private List<ArgType> argTypes;
|
||||
private List<GenericInfo> generics;
|
||||
private List<GenericTypeParameter> typeParameters;
|
||||
|
||||
// decompilation data, reset on unload
|
||||
private RegisterArg thisArg;
|
||||
@@ -96,7 +99,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
if (noCode) {
|
||||
return;
|
||||
}
|
||||
// don't unload retType, argTypes, generics
|
||||
// don't unload retType, argTypes, typeParameters
|
||||
thisArg = null;
|
||||
argsList = null;
|
||||
sVars = Collections.emptyList();
|
||||
@@ -190,7 +193,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
this.generics = sp.consumeGenericMap();
|
||||
this.typeParameters = sp.consumeGenericTypeParameters();
|
||||
List<ArgType> argsTypes = sp.consumeMethodArgs();
|
||||
this.retType = sp.consumeType();
|
||||
|
||||
@@ -264,17 +267,32 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public List<ArgType> getArgTypes() {
|
||||
if (argTypes == null) {
|
||||
throw new JadxRuntimeException("Method types not initialized: " + this);
|
||||
throw new JadxRuntimeException("Method generic types not initialized: " + this);
|
||||
}
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
public boolean containsGenericArgs() {
|
||||
return !Objects.equals(mthInfo.getArgumentsTypes(), getArgTypes());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ArgType getReturnType() {
|
||||
return retType;
|
||||
}
|
||||
|
||||
public boolean isVoidReturn() {
|
||||
return mthInfo.getReturnType().equals(ArgType.VOID);
|
||||
}
|
||||
|
||||
public List<RegisterArg> getArgRegs() {
|
||||
if (argsList == null) {
|
||||
throw new JadxRuntimeException("Method args not loaded: " + this
|
||||
throw new JadxRuntimeException("Method arg registers not loaded: " + this
|
||||
+ ", class status: " + parentClass.getTopParentClass().getState());
|
||||
}
|
||||
return argsList;
|
||||
@@ -300,12 +318,9 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
this.add(AFlag.SKIP_FIRST_ARG);
|
||||
}
|
||||
|
||||
public ArgType getReturnType() {
|
||||
return retType;
|
||||
}
|
||||
|
||||
public List<GenericInfo> getGenerics() {
|
||||
return generics;
|
||||
@Override
|
||||
public List<GenericTypeParameter> getTypeParameters() {
|
||||
return typeParameters;
|
||||
}
|
||||
|
||||
private static void initTryCatches(MethodNode mth, Code mthCode, InsnNode[] insnByOffset) {
|
||||
@@ -575,25 +590,31 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return exceptionHandlers.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<ArgType> getThrows() {
|
||||
Annotation an = getAnnotation(Consts.DALVIK_THROWS);
|
||||
if (an == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return (List<ArgType>) an.getDefaultValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if exists method with same name and arguments count
|
||||
*/
|
||||
public boolean isArgsOverload() {
|
||||
int argsCount = mthInfo.getArgumentsTypes().size();
|
||||
if (argsCount == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String name = getName();
|
||||
public boolean isArgsOverloaded() {
|
||||
MethodInfo thisMthInfo = this.mthInfo;
|
||||
// quick check in current class
|
||||
for (MethodNode method : parentClass.getMethods()) {
|
||||
MethodInfo otherMthInfo = method.mthInfo;
|
||||
if (this != method
|
||||
&& otherMthInfo.getArgumentsTypes().size() == argsCount
|
||||
&& otherMthInfo.getName().equals(name)) {
|
||||
if (method == this) {
|
||||
continue;
|
||||
}
|
||||
if (method.getMethodInfo().isOverloadedBy(thisMthInfo)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return root().getMethodUtils().isMethodArgsOverloaded(parentClass.getClassInfo().getType(), thisMthInfo);
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
@@ -721,6 +742,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
ErrorsCounter.methodError(this, errStr, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInfo getMethodInfo() {
|
||||
return mthInfo;
|
||||
}
|
||||
@@ -743,6 +765,11 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVarArg() {
|
||||
return accFlags.isVarArgs();
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return loaded;
|
||||
}
|
||||
@@ -767,7 +794,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
@Override
|
||||
public String toString() {
|
||||
return parentClass + "." + mthInfo.getName()
|
||||
+ '(' + Utils.listToString(mthInfo.getArgumentsTypes()) + "):"
|
||||
+ '(' + Utils.listToString(argTypes) + "):"
|
||||
+ retType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@ import jadx.api.ResourceType;
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.clsp.ClspGraph;
|
||||
import jadx.core.clsp.NClass;
|
||||
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.nodes.utils.MethodUtils;
|
||||
import jadx.core.dex.nodes.utils.TypeUtils;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.typeinference.TypeUpdate;
|
||||
import jadx.core.utils.CacheStorage;
|
||||
@@ -48,6 +48,8 @@ public class RootNode {
|
||||
private final InfoStorage infoStorage = new InfoStorage();
|
||||
private final CacheStorage cacheStorage = new CacheStorage();
|
||||
private final TypeUpdate typeUpdate;
|
||||
private final MethodUtils methodUtils;
|
||||
private final TypeUtils typeUtils;
|
||||
|
||||
private final ICodeCache codeCache;
|
||||
|
||||
@@ -65,6 +67,10 @@ public class RootNode {
|
||||
this.constValues = new ConstStorage(args);
|
||||
this.typeUpdate = new TypeUpdate(this);
|
||||
this.codeCache = args.getCodeCache();
|
||||
this.methodUtils = new MethodUtils(this);
|
||||
this.typeUtils = new TypeUtils(this);
|
||||
|
||||
this.dexNodes = Collections.emptyList();
|
||||
}
|
||||
|
||||
public void load(List<InputFile> inputFiles) {
|
||||
@@ -119,7 +125,7 @@ public class RootNode {
|
||||
public void initClassPath() {
|
||||
try {
|
||||
if (this.clsp == null) {
|
||||
ClspGraph newClsp = new ClspGraph();
|
||||
ClspGraph newClsp = new ClspGraph(this);
|
||||
newClsp.load();
|
||||
|
||||
List<ClassNode> classes = new ArrayList<>();
|
||||
@@ -168,6 +174,20 @@ public class RootNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ClassNode resolveClass(ArgType clsType) {
|
||||
if (clsType.isGeneric()) {
|
||||
clsType = ArgType.object(clsType.getObject());
|
||||
}
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
ClassNode cls = dexNode.resolveClass(clsType);
|
||||
if (cls != null) {
|
||||
return cls;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ClassNode searchClassByName(String fullName) {
|
||||
ClassInfo clsInfo = ClassInfo.fromName(this, fullName);
|
||||
@@ -206,6 +226,10 @@ public class RootNode {
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
MethodNode methodNode = cls.searchMethod(mth);
|
||||
if (methodNode != null) {
|
||||
return methodNode;
|
||||
}
|
||||
return cls.dex().deepResolveMethod(cls, mth.makeSignature(false));
|
||||
}
|
||||
|
||||
@@ -232,60 +256,6 @@ public class RootNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType getMethodGenericReturnType(MethodInfo callMth) {
|
||||
MethodNode methodNode = deepResolveMethod(callMth);
|
||||
if (methodNode != null) {
|
||||
ArgType returnType = methodNode.getReturnType();
|
||||
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<ArgType> getMethodArgTypes(MethodInfo callMth) {
|
||||
MethodNode methodNode = deepResolveMethod(callMth);
|
||||
if (methodNode != null) {
|
||||
return methodNode.getArgTypes();
|
||||
}
|
||||
NMethod methodDetails = clsp.getMethodDetails(callMth);
|
||||
if (methodDetails != null && methodDetails.getGenericArgs() != null) {
|
||||
List<ArgType> argTypes = callMth.getArgumentsTypes();
|
||||
int argsCount = argTypes.size();
|
||||
List<ArgType> list = new ArrayList<>(argsCount);
|
||||
for (int i = 0; i < argsCount; i++) {
|
||||
ArgType genericArgType = methodDetails.getGenericArg(i);
|
||||
if (genericArgType != null) {
|
||||
list.add(genericArgType);
|
||||
} else {
|
||||
list.add(argTypes.get(i));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<GenericInfo> getClassGenerics(ArgType type) {
|
||||
ClassNode classNode = resolveClass(ClassInfo.fromType(this, type));
|
||||
if (classNode != null) {
|
||||
return classNode.getGenerics();
|
||||
}
|
||||
NClass clsDetails = getClsp().getClsDetails(type);
|
||||
if (clsDetails == null || clsDetails.getGenerics().isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<GenericInfo> generics = clsDetails.getGenerics();
|
||||
return generics == null ? Collections.emptyList() : generics;
|
||||
}
|
||||
|
||||
public List<DexNode> getDexNodes() {
|
||||
return dexNodes;
|
||||
}
|
||||
@@ -335,4 +305,11 @@ public class RootNode {
|
||||
return codeCache;
|
||||
}
|
||||
|
||||
public MethodUtils getMethodUtils() {
|
||||
return methodUtils;
|
||||
}
|
||||
|
||||
public TypeUtils getTypeUtils() {
|
||||
return typeUtils;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -12,7 +13,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.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class SignatureParser {
|
||||
@@ -65,32 +66,40 @@ public class SignatureParser {
|
||||
* @return string from 'mark' to current position (not including current character)
|
||||
*/
|
||||
private String slice() {
|
||||
if (mark >= pos) {
|
||||
int start = mark == -1 ? 0 : mark;
|
||||
if (start >= pos) {
|
||||
return "";
|
||||
}
|
||||
return sign.substring(mark, pos);
|
||||
return sign.substring(start, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inclusive slice (includes current character)
|
||||
*/
|
||||
private String inclusiveSlice() {
|
||||
if (mark >= pos) {
|
||||
int start = mark;
|
||||
if (start == -1) {
|
||||
start = 0;
|
||||
}
|
||||
int last = pos + 1;
|
||||
if (start >= last) {
|
||||
return "";
|
||||
}
|
||||
return sign.substring(mark, pos + 1);
|
||||
return sign.substring(start, last);
|
||||
}
|
||||
|
||||
private boolean forwardTo(char lastChar) {
|
||||
private boolean skipUntil(char untilChar) {
|
||||
int startPos = pos;
|
||||
char ch;
|
||||
while ((ch = next()) != STOP_CHAR) {
|
||||
if (ch == lastChar) {
|
||||
while (true) {
|
||||
if (lookAhead(untilChar)) {
|
||||
return true;
|
||||
}
|
||||
char ch = next();
|
||||
if (ch == STOP_CHAR) {
|
||||
pos = startPos;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
pos = startPos;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void consume(char exp) {
|
||||
@@ -109,14 +118,14 @@ public class SignatureParser {
|
||||
return false;
|
||||
}
|
||||
|
||||
private String consumeUntil(char lastChar) {
|
||||
@Nullable
|
||||
public String consumeUntil(char lastChar) {
|
||||
mark();
|
||||
return forwardTo(lastChar) ? slice() : null;
|
||||
return skipUntil(lastChar) ? inclusiveSlice() : null;
|
||||
}
|
||||
|
||||
public ArgType consumeType() {
|
||||
char ch = next();
|
||||
mark();
|
||||
switch (ch) {
|
||||
case 'L':
|
||||
ArgType obj = consumeObjectType(false);
|
||||
@@ -127,10 +136,13 @@ public class SignatureParser {
|
||||
case 'T':
|
||||
next();
|
||||
mark();
|
||||
if (forwardTo(';')) {
|
||||
return ArgType.genericType(slice());
|
||||
String typeVarName = consumeUntil(';');
|
||||
if (typeVarName != null) {
|
||||
consume(';');
|
||||
return ArgType.genericType(typeVarName);
|
||||
}
|
||||
break;
|
||||
|
||||
case '[':
|
||||
return ArgType.array(consumeType());
|
||||
|
||||
@@ -145,7 +157,7 @@ public class SignatureParser {
|
||||
}
|
||||
break;
|
||||
}
|
||||
throw new JadxRuntimeException("Can't parse type: " + debugString());
|
||||
throw new JadxRuntimeException("Can't parse type: " + debugString() + ", unexpected: " + ch);
|
||||
}
|
||||
|
||||
private ArgType consumeObjectType(boolean incompleteType) {
|
||||
@@ -220,11 +232,11 @@ public class SignatureParser {
|
||||
* <p/>
|
||||
* Example: "<T:Ljava/lang/Exception;:Ljava/lang/Object;>"
|
||||
*/
|
||||
public List<GenericInfo> consumeGenericMap() {
|
||||
public List<GenericTypeParameter> consumeGenericTypeParameters() {
|
||||
if (!lookAhead('<')) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<GenericInfo> list = new ArrayList<>();
|
||||
List<GenericTypeParameter> list = new ArrayList<>();
|
||||
consume('<');
|
||||
while (true) {
|
||||
if (lookAhead('>') || next() == STOP_CHAR) {
|
||||
@@ -232,12 +244,13 @@ public class SignatureParser {
|
||||
}
|
||||
String id = consumeUntil(':');
|
||||
if (id == null) {
|
||||
LOG.error("Failed to parse generic map: {}", sign);
|
||||
LOG.error("Failed to parse generic types map: {}", sign);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
consume(':');
|
||||
tryConsume(':');
|
||||
List<ArgType> types = consumeExtendsTypesList();
|
||||
list.add(new GenericInfo(ArgType.genericType(id), types));
|
||||
list.add(new GenericTypeParameter(ArgType.genericType(id), types));
|
||||
}
|
||||
consume('>');
|
||||
return list;
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package jadx.core.dex.nodes.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.clsp.ClspClass;
|
||||
import jadx.core.clsp.ClspMethod;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public class MethodUtils {
|
||||
private final RootNode root;
|
||||
|
||||
public MethodUtils(RootNode rootNode) {
|
||||
this.root = rootNode;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public IMethodDetails getMethodDetails(BaseInvokeNode invokeNode) {
|
||||
IMethodDetails methodDetails = invokeNode.get(AType.METHOD_DETAILS);
|
||||
if (methodDetails != null) {
|
||||
return methodDetails;
|
||||
}
|
||||
return getMethodDetails(invokeNode.getCallMth());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public IMethodDetails getMethodDetails(MethodInfo callMth) {
|
||||
MethodNode mthNode = root.deepResolveMethod(callMth);
|
||||
if (mthNode != null) {
|
||||
return mthNode;
|
||||
}
|
||||
return root.getClsp().getMethodDetails(callMth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search methods with same name and args count in class hierarchy starting from {@code startCls}
|
||||
* Beware {@code startCls} can be different from {@code mthInfo.getDeclClass()}
|
||||
*/
|
||||
public boolean isMethodArgsOverloaded(ArgType startCls, MethodInfo mthInfo) {
|
||||
return processMethodArgsOverloaded(startCls, mthInfo, null);
|
||||
}
|
||||
|
||||
public List<IMethodDetails> collectOverloadedMethods(ArgType startCls, MethodInfo mthInfo) {
|
||||
List<IMethodDetails> list = new ArrayList<>();
|
||||
processMethodArgsOverloaded(startCls, mthInfo, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType getMethodGenericReturnType(BaseInvokeNode invokeNode) {
|
||||
IMethodDetails methodDetails = getMethodDetails(invokeNode);
|
||||
if (methodDetails != null) {
|
||||
ArgType returnType = methodDetails.getReturnType();
|
||||
if (returnType != null && returnType.containsGeneric()) {
|
||||
return returnType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean processMethodArgsOverloaded(ArgType startCls, MethodInfo mthInfo, @Nullable List<IMethodDetails> collectedMths) {
|
||||
if (startCls == null) {
|
||||
return false;
|
||||
}
|
||||
boolean isMthConstructor = mthInfo.isConstructor() || mthInfo.isClassInit();
|
||||
ClassNode classNode = root.resolveClass(startCls);
|
||||
if (classNode != null) {
|
||||
for (MethodNode mth : classNode.getMethods()) {
|
||||
if (mthInfo.isOverloadedBy(mth.getMethodInfo())) {
|
||||
if (collectedMths == null) {
|
||||
return true;
|
||||
}
|
||||
collectedMths.add(mth);
|
||||
}
|
||||
}
|
||||
if (!isMthConstructor) {
|
||||
if (processMethodArgsOverloaded(classNode.getSuperClass(), mthInfo, collectedMths)) {
|
||||
if (collectedMths == null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (ArgType parentInterface : classNode.getInterfaces()) {
|
||||
if (processMethodArgsOverloaded(parentInterface, mthInfo, collectedMths)) {
|
||||
if (collectedMths == null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ClspClass clsDetails = root.getClsp().getClsDetails(startCls);
|
||||
if (clsDetails == null) {
|
||||
// class info not available
|
||||
return false;
|
||||
}
|
||||
for (ClspMethod clspMth : clsDetails.getMethodsMap().values()) {
|
||||
if (mthInfo.isOverloadedBy(clspMth.getMethodInfo())) {
|
||||
if (collectedMths == null) {
|
||||
return true;
|
||||
}
|
||||
collectedMths.add(clspMth);
|
||||
}
|
||||
}
|
||||
if (!isMthConstructor) {
|
||||
for (ArgType parent : clsDetails.getParents()) {
|
||||
if (processMethodArgsOverloaded(parent, mthInfo, collectedMths)) {
|
||||
if (collectedMths == null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package jadx.core.dex.nodes.utils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.clsp.ClspClass;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public class TypeUtils {
|
||||
private final RootNode root;
|
||||
|
||||
public TypeUtils(RootNode rootNode) {
|
||||
this.root = rootNode;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<GenericTypeParameter> getClassGenerics(ArgType type) {
|
||||
ClassNode classNode = root.resolveClass(type);
|
||||
if (classNode != null) {
|
||||
return classNode.getGenericTypeParameters();
|
||||
}
|
||||
ClspClass clsDetails = root.getClsp().getClsDetails(type);
|
||||
if (clsDetails == null || clsDetails.getTypeParameters().isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<GenericTypeParameter> generics = clsDetails.getTypeParameters();
|
||||
return generics == null ? Collections.emptyList() : generics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace generic types in {@code typeWithGeneric} using instance types
|
||||
* <br>
|
||||
* Example:
|
||||
* <ul>
|
||||
* <li>{@code instanceType: Set<String>}
|
||||
* <li>{@code typeWithGeneric: Iterator<E>}
|
||||
* <li>{@code return: Iterator<String>}
|
||||
* </ul>
|
||||
*/
|
||||
@Nullable
|
||||
public ArgType replaceClassGenerics(ArgType instanceType, ArgType typeWithGeneric) {
|
||||
if (typeWithGeneric != null) {
|
||||
Map<ArgType, ArgType> replaceMap = getTypeVariablesMapping(instanceType);
|
||||
if (!replaceMap.isEmpty()) {
|
||||
return replaceTypeVariablesUsingMap(typeWithGeneric, replaceMap);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<ArgType, ArgType> getTypeVariablesMapping(ArgType clsType) {
|
||||
if (!clsType.isGeneric()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
List<GenericTypeParameter> typeParameters = root.getTypeUtils().getClassGenerics(clsType);
|
||||
if (typeParameters.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
ArgType[] actualTypes = clsType.getGenericTypes();
|
||||
if (actualTypes == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
int genericParamsCount = actualTypes.length;
|
||||
if (genericParamsCount != typeParameters.size()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<ArgType, ArgType> replaceMap = new HashMap<>(genericParamsCount);
|
||||
for (int i = 0; i < genericParamsCount; i++) {
|
||||
ArgType actualType = actualTypes[i];
|
||||
ArgType genericType = typeParameters.get(i).getTypeVariable();
|
||||
replaceMap.put(genericType, actualType);
|
||||
}
|
||||
return replaceMap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType replaceMethodGenerics(BaseInvokeNode invokeInsn, IMethodDetails details, ArgType typeWithGeneric) {
|
||||
if (typeWithGeneric == null) {
|
||||
return null;
|
||||
}
|
||||
List<ArgType> methodArgTypes = details.getArgTypes();
|
||||
if (methodArgTypes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
int firstArgOffset = invokeInsn.getFirstArgOffset();
|
||||
int argsCount = methodArgTypes.size();
|
||||
for (int i = 0; i < argsCount; i++) {
|
||||
ArgType methodArgType = methodArgTypes.get(i);
|
||||
InsnArg insnArg = invokeInsn.getArg(i + firstArgOffset);
|
||||
ArgType insnType = insnArg.getType();
|
||||
if (methodArgType.equals(typeWithGeneric)) {
|
||||
return insnType;
|
||||
}
|
||||
}
|
||||
// TODO build complete map for type variables
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType replaceTypeVariablesUsingMap(ArgType replaceType, Map<ArgType, ArgType> replaceMap) {
|
||||
if (replaceType.isGenericType()) {
|
||||
return replaceMap.get(replaceType);
|
||||
}
|
||||
|
||||
ArgType wildcardType = replaceType.getWildcardType();
|
||||
if (wildcardType != null && wildcardType.containsTypeVariable()) {
|
||||
ArgType newWildcardType = replaceTypeVariablesUsingMap(wildcardType, replaceMap);
|
||||
if (newWildcardType == null) {
|
||||
return null;
|
||||
}
|
||||
return ArgType.wildcard(newWildcardType, replaceType.getWildcardBound());
|
||||
}
|
||||
|
||||
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 = replaceTypeVariablesUsingMap(genericType, replaceMap);
|
||||
if (type == null) {
|
||||
type = genericType;
|
||||
}
|
||||
newTypes[i] = type;
|
||||
}
|
||||
return ArgType.generic(replaceType.getObject(), newTypes);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.nodes.utils.MethodUtils;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "Attach Method Details",
|
||||
desc = "Attach method details for invoke instructions",
|
||||
runBefore = {
|
||||
CodeShrinkVisitor.class
|
||||
}
|
||||
)
|
||||
public class AttachMethodDetails extends AbstractVisitor {
|
||||
|
||||
private MethodUtils methodUtils;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
methodUtils = root.getMethodUtils();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
for (BlockNode blockNode : mth.getBasicBlocks()) {
|
||||
for (InsnNode insn : blockNode.getInstructions()) {
|
||||
if (insn instanceof BaseInvokeNode) {
|
||||
attachMethodDetails((BaseInvokeNode) insn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void attachMethodDetails(BaseInvokeNode insn) {
|
||||
IMethodDetails methodDetails = methodUtils.getMethodDetails(insn.getCallMth());
|
||||
if (methodDetails != null) {
|
||||
insn.addAttr(methodDetails);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.CallMthInterface;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
@@ -240,8 +240,8 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static boolean needExplicitCast(InsnNode insn, LiteralArg arg) {
|
||||
if (insn instanceof CallMthInterface) {
|
||||
CallMthInterface callInsn = (CallMthInterface) insn;
|
||||
if (insn instanceof BaseInvokeNode) {
|
||||
BaseInvokeNode callInsn = (BaseInvokeNode) insn;
|
||||
MethodInfo callMth = callInsn.getCallMth();
|
||||
int offset = callInsn.getFirstArgOffset();
|
||||
int argIndex = insn.getArgIndex(arg);
|
||||
|
||||
@@ -54,7 +54,7 @@ public class DeboxingVisitor extends AbstractVisitor {
|
||||
private static MethodInfo valueOfMth(RootNode root, ArgType argType, String clsName) {
|
||||
ArgType boxType = ArgType.object(clsName);
|
||||
ClassInfo boxCls = ClassInfo.fromType(root, boxType);
|
||||
return MethodInfo.externalMth(boxCls, "valueOf", Collections.singletonList(argType), boxType);
|
||||
return MethodInfo.fromDetails(root, boxCls, "valueOf", Collections.singletonList(argType), boxType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.dex.visitors.typeinference.TypeCompare;
|
||||
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "MethodInvokeVisitor",
|
||||
desc = "Process additional info for method invocation (overload, vararg)",
|
||||
runAfter = {
|
||||
CodeShrinkVisitor.class,
|
||||
ModVisitor.class
|
||||
},
|
||||
runBefore = {
|
||||
SimplifyVisitor.class // run before cast remove and StringBuilder replace
|
||||
}
|
||||
)
|
||||
public class MethodInvokeVisitor extends AbstractVisitor {
|
||||
private RootNode root;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
if (block.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (insn.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
processInsn(mth, insn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processInsn(MethodNode mth, InsnNode insn) {
|
||||
if (insn instanceof BaseInvokeNode) {
|
||||
processInvoke(mth, ((BaseInvokeNode) insn));
|
||||
}
|
||||
for (InsnArg insnArg : insn.getArguments()) {
|
||||
if (insnArg instanceof InsnWrapArg) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) insnArg).getWrapInsn();
|
||||
processInsn(mth, wrapInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processInvoke(MethodNode parentMth, BaseInvokeNode invokeInsn) {
|
||||
MethodInfo callMth = invokeInsn.getCallMth();
|
||||
if (callMth.getArgsCount() == 0) {
|
||||
return;
|
||||
}
|
||||
IMethodDetails mthDetails = root.getMethodUtils().getMethodDetails(invokeInsn);
|
||||
if (mthDetails == null) {
|
||||
if (Consts.DEBUG) {
|
||||
parentMth.addComment("JADX DEBUG: Method info not found: " + callMth);
|
||||
}
|
||||
processUnknown(invokeInsn);
|
||||
} else {
|
||||
// parentMth.addComment("JADX DEBUG: got method details: " + mthDetails);
|
||||
if (mthDetails.isVarArg()) {
|
||||
ArgType last = Utils.last(mthDetails.getArgTypes());
|
||||
if (last != null && last.isArray()) {
|
||||
invokeInsn.add(AFlag.VARARG_CALL);
|
||||
}
|
||||
}
|
||||
processOverloaded(parentMth, invokeInsn, mthDetails);
|
||||
}
|
||||
}
|
||||
|
||||
private void processOverloaded(MethodNode parentMth, BaseInvokeNode invokeInsn, IMethodDetails mthDetails) {
|
||||
MethodInfo callMth = invokeInsn.getCallMth();
|
||||
ArgType callCls = getCallClassFromInvoke(parentMth, invokeInsn, callMth);
|
||||
List<IMethodDetails> overloadMethods = root.getMethodUtils().collectOverloadedMethods(callCls, callMth);
|
||||
if (overloadMethods.isEmpty()) {
|
||||
// not overloaded
|
||||
return;
|
||||
}
|
||||
|
||||
overloadMethods.add(mthDetails);
|
||||
resolveTypeVariablesInMethodArgs(invokeInsn, mthDetails, overloadMethods);
|
||||
|
||||
int argsOffset = invokeInsn.getFirstArgOffset();
|
||||
List<ArgType> compilerVarTypes = collectCompilerVarTypes(invokeInsn, argsOffset);
|
||||
List<ArgType> castTypes = searchCastTypes(parentMth, mthDetails, overloadMethods, compilerVarTypes);
|
||||
applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, castTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method details not found => add cast for 'null' args
|
||||
*/
|
||||
private void processUnknown(BaseInvokeNode invokeInsn) {
|
||||
int argsOffset = invokeInsn.getFirstArgOffset();
|
||||
List<ArgType> compilerVarTypes = collectCompilerVarTypes(invokeInsn, argsOffset);
|
||||
List<ArgType> castTypes = new ArrayList<>(compilerVarTypes);
|
||||
if (replaceUnknownTypes(castTypes, invokeInsn.getCallMth().getArgumentsTypes())) {
|
||||
applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, castTypes);
|
||||
}
|
||||
}
|
||||
|
||||
private ArgType getCallClassFromInvoke(MethodNode parentMth, BaseInvokeNode invokeInsn, MethodInfo callMth) {
|
||||
if (invokeInsn instanceof ConstructorInsn) {
|
||||
ConstructorInsn constrInsn = (ConstructorInsn) invokeInsn;
|
||||
if (constrInsn.isSuper()) {
|
||||
return parentMth.getParentClass().getSuperClass();
|
||||
}
|
||||
}
|
||||
InsnArg instanceArg = invokeInsn.getInstanceArg();
|
||||
if (instanceArg != null) {
|
||||
return instanceArg.getType();
|
||||
}
|
||||
// static call
|
||||
return callMth.getDeclClass().getType();
|
||||
}
|
||||
|
||||
private void resolveTypeVariablesInMethodArgs(BaseInvokeNode invokeInsn, IMethodDetails mthDetails,
|
||||
List<IMethodDetails> overloadedMethods) {
|
||||
MethodInfo callMth = invokeInsn.getCallMth();
|
||||
ArgType declClsType = callMth.getDeclClass().getType();
|
||||
ArgType callClsType;
|
||||
InsnArg instanceArg = invokeInsn.getInstanceArg();
|
||||
if (instanceArg != null) {
|
||||
callClsType = instanceArg.getType();
|
||||
} else {
|
||||
callClsType = declClsType;
|
||||
}
|
||||
|
||||
Map<ArgType, ArgType> typeVarsMapping = root.getTypeUtils().getTypeVariablesMapping(callClsType);
|
||||
resolveTypeVars(mthDetails, typeVarsMapping);
|
||||
for (IMethodDetails m : overloadedMethods) {
|
||||
resolveTypeVars(m, typeVarsMapping);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyArgsCast(BaseInvokeNode invokeInsn, int argsOffset, List<ArgType> compilerVarTypes, List<ArgType> castTypes) {
|
||||
int argsCount = invokeInsn.getArgsCount();
|
||||
for (int i = argsOffset; i < argsCount; i++) {
|
||||
InsnArg arg = invokeInsn.getArg(i);
|
||||
int origPos = i - argsOffset;
|
||||
ArgType compilerType = compilerVarTypes.get(origPos);
|
||||
ArgType castType = castTypes.get(origPos);
|
||||
if (castType != null) {
|
||||
if (!castType.equals(compilerType)) {
|
||||
if (arg.isLiteral() && compilerType.isPrimitive() && castType.isPrimitive()) {
|
||||
arg.setType(castType);
|
||||
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
|
||||
} else {
|
||||
InsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1);
|
||||
castInsn.addArg(arg);
|
||||
castInsn.add(AFlag.EXPLICIT_CAST);
|
||||
InsnArg wrapCast = InsnArg.wrapArg(castInsn);
|
||||
wrapCast.setType(castType);
|
||||
invokeInsn.setArg(i, wrapCast);
|
||||
}
|
||||
} else {
|
||||
// protect already existed cast
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
if (wrapInsn.getType() == InsnType.CHECK_CAST) {
|
||||
wrapInsn.add(AFlag.EXPLICIT_CAST);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveTypeVars(IMethodDetails mthDetails, Map<ArgType, ArgType> typeVarsMapping) {
|
||||
List<ArgType> argTypes = mthDetails.getArgTypes();
|
||||
int argsCount = argTypes.size();
|
||||
for (int argNum = 0; argNum < argsCount; argNum++) {
|
||||
ArgType argType = argTypes.get(argNum);
|
||||
if (argType == null) {
|
||||
throw new JadxRuntimeException("Null arg type in " + mthDetails + " at: " + argNum + " in: " + argTypes);
|
||||
}
|
||||
if (argType.containsTypeVariable()) {
|
||||
ArgType resolvedType = root.getTypeUtils().replaceTypeVariablesUsingMap(argType, typeVarsMapping);
|
||||
if (resolvedType == null || resolvedType.containsTypeVariable()) {
|
||||
// type variables erased from method info by compiler
|
||||
resolvedType = mthDetails.getMethodInfo().getArgumentsTypes().get(argNum);
|
||||
}
|
||||
argTypes.set(argNum, resolvedType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<ArgType> searchCastTypes(MethodNode parentMth, IMethodDetails mthDetails, List<IMethodDetails> overloadedMethods,
|
||||
List<ArgType> compilerVarTypes) {
|
||||
// try compile types
|
||||
if (isOverloadResolved(mthDetails, overloadedMethods, compilerVarTypes)) {
|
||||
return compilerVarTypes;
|
||||
}
|
||||
int argsCount = compilerVarTypes.size();
|
||||
List<ArgType> castTypes = new ArrayList<>(compilerVarTypes);
|
||||
|
||||
// replace unknown types
|
||||
boolean changed = replaceUnknownTypes(castTypes, mthDetails.getArgTypes());
|
||||
if (changed && isOverloadResolved(mthDetails, overloadedMethods, castTypes)) {
|
||||
return castTypes;
|
||||
}
|
||||
|
||||
// replace generic types
|
||||
changed = false;
|
||||
for (int i = 0; i < argsCount; i++) {
|
||||
ArgType castType = castTypes.get(i);
|
||||
ArgType mthType = mthDetails.getArgTypes().get(i);
|
||||
if (!castType.isGeneric() && mthType.isGeneric()) {
|
||||
castTypes.set(i, mthType);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed && isOverloadResolved(mthDetails, overloadedMethods, castTypes)) {
|
||||
return castTypes;
|
||||
}
|
||||
|
||||
// if just one arg => cast will resolve
|
||||
if (argsCount == 1) {
|
||||
return mthDetails.getArgTypes();
|
||||
}
|
||||
|
||||
// TODO: try to minimize casts count
|
||||
|
||||
parentMth.addComment("JADX DEBUG: Failed to find minimal casts for resolve overloaded methods, cast all args instead"
|
||||
+ "\n method: " + mthDetails
|
||||
+ "\n arg types: " + compilerVarTypes
|
||||
+ "\n candidates:\n " + Utils.listToString(overloadedMethods, "\n "));
|
||||
// not resolved -> cast all args
|
||||
return mthDetails.getArgTypes();
|
||||
}
|
||||
|
||||
private boolean replaceUnknownTypes(List<ArgType> castTypes, List<ArgType> mthArgTypes) {
|
||||
int argsCount = castTypes.size();
|
||||
boolean changed = false;
|
||||
for (int i = 0; i < argsCount; i++) {
|
||||
ArgType castType = castTypes.get(i);
|
||||
if (!castType.isTypeKnown()) {
|
||||
ArgType mthType = mthArgTypes.get(i);
|
||||
castTypes.set(i, mthType);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
private boolean isOverloadResolved(IMethodDetails expectedMthDetails, List<IMethodDetails> overloadedMethods, List<ArgType> castTypes) {
|
||||
if (overloadedMethods.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// TODO: search closest method, instead filtering
|
||||
List<IMethodDetails> strictMethods = filterApplicableMethods(overloadedMethods, castTypes, MethodInvokeVisitor::isStrictTypes);
|
||||
if (strictMethods.size() == 1) {
|
||||
return strictMethods.get(0).equals(expectedMthDetails);
|
||||
}
|
||||
List<IMethodDetails> resolvedMethods = filterApplicableMethods(overloadedMethods, castTypes, MethodInvokeVisitor::isTypeApplicable);
|
||||
if (resolvedMethods.size() == 1) {
|
||||
return resolvedMethods.get(0).equals(expectedMthDetails);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isStrictTypes(TypeCompareEnum result) {
|
||||
return result.isEqual();
|
||||
}
|
||||
|
||||
private static boolean isTypeApplicable(TypeCompareEnum result) {
|
||||
return result.isNarrowOrEqual() || result == TypeCompareEnum.WIDER_BY_GENERIC;
|
||||
}
|
||||
|
||||
private List<IMethodDetails> filterApplicableMethods(List<IMethodDetails> methods, List<ArgType> types,
|
||||
Function<TypeCompareEnum, Boolean> acceptFunction) {
|
||||
List<IMethodDetails> list = new ArrayList<>(methods.size());
|
||||
for (IMethodDetails m : methods) {
|
||||
if (isMethodAcceptable(m, types, acceptFunction)) {
|
||||
list.add(m);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private boolean isMethodAcceptable(IMethodDetails methodDetails, List<ArgType> types,
|
||||
Function<TypeCompareEnum, Boolean> acceptFunction) {
|
||||
List<ArgType> mthTypes = methodDetails.getArgTypes();
|
||||
int argCount = mthTypes.size();
|
||||
if (argCount != types.size()) {
|
||||
return false;
|
||||
}
|
||||
TypeCompare typeCompare = root.getTypeUpdate().getTypeCompare();
|
||||
for (int i = 0; i < argCount; i++) {
|
||||
ArgType mthType = mthTypes.get(i);
|
||||
ArgType argType = types.get(i);
|
||||
TypeCompareEnum result = typeCompare.compareTypes(argType, mthType);
|
||||
if (!acceptFunction.apply(result)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<ArgType> collectCompilerVarTypes(BaseInvokeNode insn, int argOffset) {
|
||||
int argsCount = insn.getArgsCount();
|
||||
List<ArgType> result = new ArrayList<>(argsCount);
|
||||
for (int i = argOffset; i < argsCount; i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
result.add(getCompilerVarType(arg));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return type as seen by compiler
|
||||
*/
|
||||
private ArgType getCompilerVarType(InsnArg arg) {
|
||||
if (arg instanceof LiteralArg) {
|
||||
LiteralArg literalArg = (LiteralArg) arg;
|
||||
ArgType type = literalArg.getType();
|
||||
if (literalArg.getLiteral() == 0) {
|
||||
if (type.isObject() || type.isArray()) {
|
||||
// null
|
||||
return ArgType.UNKNOWN_OBJECT;
|
||||
}
|
||||
}
|
||||
if (type.isPrimitive() && !arg.contains(AFlag.EXPLICIT_PRIMITIVE_TYPE)) {
|
||||
return ArgType.INT;
|
||||
}
|
||||
return arg.getType();
|
||||
}
|
||||
if (arg instanceof RegisterArg) {
|
||||
return arg.getType();
|
||||
}
|
||||
if (arg instanceof InsnWrapArg) {
|
||||
InsnWrapArg wrapArg = (InsnWrapArg) arg;
|
||||
InsnNode wrapInsn = wrapArg.getWrapInsn();
|
||||
if (wrapInsn.getResult() != null) {
|
||||
return wrapInsn.getResult().getType();
|
||||
}
|
||||
return arg.getType();
|
||||
}
|
||||
throw new JadxRuntimeException("Unknown var type for: " + arg);
|
||||
}
|
||||
}
|
||||
@@ -255,7 +255,7 @@ public class ModVisitor extends AbstractVisitor {
|
||||
private static void removeRedundantCast(MethodNode mth, BlockNode block, int i, IndexInsnNode insn) {
|
||||
InsnArg castArg = insn.getArg(0);
|
||||
ArgType castType = (ArgType) insn.getIndex();
|
||||
if (!ArgType.isCastNeeded(mth.dex(), castArg.getType(), castType)
|
||||
if (!ArgType.isCastNeeded(mth.root(), castArg.getType(), castType)
|
||||
|| isCastDuplicate(insn)) {
|
||||
InsnNode insnNode = new InsnNode(InsnType.MOVE, 1);
|
||||
insnNode.setResult(insn.getResult());
|
||||
|
||||
@@ -49,7 +49,8 @@ public class SimplifyVisitor extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
stringGetBytesMth = MethodInfo.externalMth(
|
||||
stringGetBytesMth = MethodInfo.fromDetails(
|
||||
root,
|
||||
ClassInfo.fromType(root, ArgType.STRING),
|
||||
"getBytes",
|
||||
Collections.emptyList(),
|
||||
@@ -224,7 +225,7 @@ public class SimplifyVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
ArgType castToType = (ArgType) castInsn.getIndex();
|
||||
if (!ArgType.isCastNeeded(mth.dex(), argType, castToType)
|
||||
if (!ArgType.isCastNeeded(mth.root(), argType, castToType)
|
||||
|| isCastDuplicate(castInsn)) {
|
||||
InsnNode insnNode = new InsnNode(InsnType.MOVE, 1);
|
||||
insnNode.setOffset(castInsn.getOffset());
|
||||
|
||||
@@ -16,7 +16,6 @@ import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -422,7 +421,7 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static boolean mergeConstReturn(MethodNode mth) {
|
||||
if (mth.getReturnType() == ArgType.VOID) {
|
||||
if (mth.isVoidReturn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+2
-10
@@ -90,7 +90,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
||||
RegDebugInfoAttr debugInfo = debugInfoSet.iterator().next();
|
||||
applyDebugInfo(mth, ssaVar, debugInfo.getRegType(), debugInfo.getName());
|
||||
} else {
|
||||
LOG.warn("Multiple debug info for {}: {}", ssaVar, debugInfoSet);
|
||||
mth.addComment("JADX INFO: Multiple debug info for " + ssaVar + ": " + debugInfoSet);
|
||||
for (RegDebugInfoAttr debugInfo : debugInfoSet) {
|
||||
applyDebugInfo(mth, ssaVar, debugInfo.getRegType(), debugInfo.getName());
|
||||
}
|
||||
@@ -150,14 +150,6 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
||||
if (NameMapper.isValidAndPrintable(varName)) {
|
||||
ssaVar.setName(varName);
|
||||
}
|
||||
detachDebugInfo(ssaVar.getAssign());
|
||||
ssaVar.getUseList().forEach(DebugInfoApplyVisitor::detachDebugInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private static void detachDebugInfo(RegisterArg reg) {
|
||||
if (reg != null) {
|
||||
reg.remove(AType.REG_DEBUG_INFO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +164,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
||||
* Fix debug info for splitter 'return' instructions
|
||||
*/
|
||||
private static void fixLinesForReturn(MethodNode mth) {
|
||||
if (mth.getReturnType().equals(ArgType.VOID)) {
|
||||
if (mth.isVoidReturn()) {
|
||||
return;
|
||||
}
|
||||
InsnNode origReturn = null;
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
|
||||
public final class LocalVar {
|
||||
@@ -31,7 +32,7 @@ public final class LocalVar {
|
||||
this.name = name;
|
||||
if (sign != null) {
|
||||
try {
|
||||
ArgType gType = ArgType.parseGenericSignature(sign);
|
||||
ArgType gType = new SignatureParser(sign).consumeType();
|
||||
if (checkSignature(type, gType)) {
|
||||
type = gType;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package jadx.core.dex.visitors.regions;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
@@ -96,7 +95,7 @@ public class IfRegionVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static void moveReturnToThenBlock(MethodNode mth, IfRegion ifRegion) {
|
||||
if (!mth.getReturnType().equals(ArgType.VOID)
|
||||
if (!mth.isVoidReturn()
|
||||
&& hasSimpleReturnBlock(ifRegion.getElseRegion())
|
||||
/* && insnsCount(ifRegion.getThenRegion()) < 2 */) {
|
||||
invertIfRegion(ifRegion);
|
||||
@@ -139,7 +138,7 @@ public class IfRegionVisitor extends AbstractVisitor {
|
||||
// code style check:
|
||||
// will remove 'return;' from 'then' and 'else' with one instruction
|
||||
// see #jadx.tests.integration.conditions.TestConditions9
|
||||
if (mth.getReturnType() == ArgType.VOID
|
||||
if (mth.isVoidReturn()
|
||||
&& insnsCount(ifRegion.getThenRegion()) == 2
|
||||
&& insnsCount(ifRegion.getElseRegion()) == 2) {
|
||||
return false;
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IBranchRegion;
|
||||
@@ -26,7 +25,7 @@ public class ReturnVisitor extends AbstractVisitor {
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
// remove useless returns in void methods
|
||||
if (mth.getReturnType().equals(ArgType.VOID)) {
|
||||
if (mth.isVoidReturn()) {
|
||||
DepthRegionTraversal.traverse(mth, new ReturnRemoverVisitor());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.util.Map;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -123,7 +122,7 @@ public class TernaryMod implements IRegionIterativeVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mth.getReturnType().equals(ArgType.VOID)
|
||||
if (!mth.isVoidReturn()
|
||||
&& thenInsn.getType() == InsnType.RETURN
|
||||
&& elseInsn.getType() == InsnType.RETURN) {
|
||||
InsnArg thenArg = thenInsn.getArg(0);
|
||||
|
||||
+1
-2
@@ -4,7 +4,6 @@ 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;
|
||||
import jadx.core.utils.TypeUtils;
|
||||
|
||||
/**
|
||||
* Special dynamic bound for invoke with generics.
|
||||
@@ -38,7 +37,7 @@ public final class TypeBoundInvokeAssign implements ITypeBoundDynamic {
|
||||
}
|
||||
|
||||
private ArgType getReturnType(ArgType instanceType) {
|
||||
ArgType resultGeneric = TypeUtils.replaceClassGenerics(root, instanceType, genericReturnType);
|
||||
ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(instanceType, genericReturnType);
|
||||
if (resultGeneric != null) {
|
||||
return resultGeneric;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,13 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.ArgType.WildcardBound;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.CONFLICT;
|
||||
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.EQUAL;
|
||||
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.NARROW;
|
||||
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.NARROW_BY_GENERIC;
|
||||
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.UNKNOWN;
|
||||
@@ -143,18 +145,42 @@ public class TypeCompare {
|
||||
if (firstGenericType && secondGenericType && !objectsEquals) {
|
||||
return CONFLICT;
|
||||
}
|
||||
boolean firstGeneric = first.isGeneric();
|
||||
boolean secondGeneric = second.isGeneric();
|
||||
|
||||
if (firstGenericType || secondGenericType) {
|
||||
ArgType firstWildcardType = first.getWildcardType();
|
||||
ArgType secondWildcardType = second.getWildcardType();
|
||||
if (firstWildcardType != null || secondWildcardType != null) {
|
||||
if (firstWildcardType != null && secondGenericType && first.getWildcardBound() == WildcardBound.UNBOUND) {
|
||||
return CONFLICT;
|
||||
}
|
||||
if (firstGenericType && secondWildcardType != null && second.getWildcardBound() == WildcardBound.UNBOUND) {
|
||||
return CONFLICT;
|
||||
}
|
||||
}
|
||||
if (firstGenericType) {
|
||||
return compareGenericTypeWithObject(first, second);
|
||||
} else {
|
||||
return compareGenericTypeWithObject(second, first).invert();
|
||||
}
|
||||
}
|
||||
boolean firstGeneric = first.isGeneric();
|
||||
boolean secondGeneric = second.isGeneric();
|
||||
if (firstGeneric != secondGeneric && objectsEquals) {
|
||||
// don't check generics for now
|
||||
return firstGeneric ? NARROW_BY_GENERIC : WIDER_BY_GENERIC;
|
||||
if (objectsEquals) {
|
||||
if (firstGeneric != secondGeneric) {
|
||||
return firstGeneric ? NARROW_BY_GENERIC : WIDER_BY_GENERIC;
|
||||
}
|
||||
// both generics on same object, compare generics arrays
|
||||
ArgType[] firstGenericTypes = first.getGenericTypes();
|
||||
ArgType[] secondGenericTypes = second.getGenericTypes();
|
||||
int len = firstGenericTypes.length;
|
||||
if (len == secondGenericTypes.length) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
TypeCompareEnum res = compareTypes(firstGenericTypes[i], secondGenericTypes[i]);
|
||||
if (res != EQUAL) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean firstIsObjCls = first.equals(ArgType.OBJECT);
|
||||
if (firstIsObjCls || second.equals(ArgType.OBJECT)) {
|
||||
|
||||
@@ -42,4 +42,12 @@ public enum TypeCompareEnum {
|
||||
public boolean isNarrow() {
|
||||
return this == NARROW || this == NARROW_BY_GENERIC;
|
||||
}
|
||||
|
||||
public boolean isNarrowOrEqual() {
|
||||
return isEqual() || isNarrow();
|
||||
}
|
||||
|
||||
public boolean isGeneric() {
|
||||
return this == WIDER_BY_GENERIC || this == NARROW_BY_GENERIC;
|
||||
}
|
||||
}
|
||||
|
||||
+20
-6
@@ -16,7 +16,7 @@ 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.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
@@ -30,11 +30,13 @@ 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.BlockNode;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.AttachMethodDetails;
|
||||
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||
import jadx.core.dex.visitors.InitCodeVariables;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
@@ -48,7 +50,8 @@ import jadx.core.utils.Utils;
|
||||
desc = "Calculate best types for SSA variables",
|
||||
runAfter = {
|
||||
SSATransform.class,
|
||||
ConstInlineVisitor.class
|
||||
ConstInlineVisitor.class,
|
||||
AttachMethodDetails.class
|
||||
}
|
||||
)
|
||||
public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
@@ -68,6 +71,9 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
LOG.info("Start type inference in method: {}", mth);
|
||||
}
|
||||
boolean resolved = runTypePropagation(mth);
|
||||
if (!resolved) {
|
||||
boolean moveAdded = false;
|
||||
@@ -271,11 +277,10 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private ITypeBound makeAssignInvokeBound(InvokeNode invokeNode) {
|
||||
MethodInfo callMth = invokeNode.getCallMth();
|
||||
ArgType boundType = callMth.getReturnType();
|
||||
ArgType genericReturnType = root.getMethodGenericReturnType(callMth);
|
||||
ArgType boundType = invokeNode.getCallMth().getReturnType();
|
||||
ArgType genericReturnType = root.getMethodUtils().getMethodGenericReturnType(invokeNode);
|
||||
if (genericReturnType != null) {
|
||||
if (genericReturnType.containsGenericType()) {
|
||||
if (genericReturnType.containsTypeVariable()) {
|
||||
InvokeType invokeType = invokeNode.getInvokeType();
|
||||
if (invokeNode.getArgsCount() != 0
|
||||
&& invokeType != InvokeType.STATIC && invokeType != InvokeType.SUPER) {
|
||||
@@ -294,6 +299,15 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
if (insn == null) {
|
||||
return null;
|
||||
}
|
||||
if (insn instanceof BaseInvokeNode) {
|
||||
IMethodDetails methodDetails = root.getMethodUtils().getMethodDetails((BaseInvokeNode) insn);
|
||||
if (methodDetails != null) {
|
||||
if (methodDetails.getArgTypes().stream().anyMatch(ArgType::containsTypeVariable)) {
|
||||
// don't add const bound for generic type variables
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new TypeBoundConst(BoundEnum.USE, regArg.getInitType(), regArg);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Consts;
|
||||
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;
|
||||
@@ -22,7 +21,6 @@ import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.TypeUtils;
|
||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -79,8 +77,9 @@ public final class TypeUpdate {
|
||||
return SAME;
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
LOG.debug("Applying types, init for {} -> {}", ssaVar, candidateType);
|
||||
updates.forEach(updateEntry -> LOG.debug(" {} -> {}", updateEntry.getType(), updateEntry.getArg()));
|
||||
LOG.debug("Applying types for {} -> {}", ssaVar, candidateType);
|
||||
updates.forEach(updateEntry -> LOG.debug(" {} -> {}, insn: {}",
|
||||
updateEntry.getType(), updateEntry.getArg(), updateEntry.getArg().getParentInsn()));
|
||||
}
|
||||
updateInfo.applyUpdates();
|
||||
return CHANGED;
|
||||
@@ -282,18 +281,17 @@ public final class TypeUpdate {
|
||||
if (insn.getResult() == null) {
|
||||
return SAME;
|
||||
}
|
||||
if (candidateType.isGeneric() || candidateType.isGenericType()) {
|
||||
if (candidateType.containsTypeVariable()) {
|
||||
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);
|
||||
ArgType returnType = root.getMethodUtils().getMethodGenericReturnType(invokeNode);
|
||||
if (returnType == null) {
|
||||
return SAME;
|
||||
}
|
||||
ArgType resultGeneric = TypeUtils.replaceClassGenerics(root, candidateType, returnType);
|
||||
ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(candidateType, returnType);
|
||||
if (resultGeneric == null) {
|
||||
return SAME;
|
||||
}
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.CallMthInterface;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.GenericInfo;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class TypeUtils {
|
||||
/**
|
||||
* Replace generic types in {@code typeWithGeneric} using instance types
|
||||
* <br>
|
||||
* Example:
|
||||
* <ul>
|
||||
* <li>{@code instanceType: Set<String>}
|
||||
* <li>{@code typeWithGeneric: Iterator<E>}
|
||||
* <li>{@code return: Iterator<String>}
|
||||
* </ul>
|
||||
*/
|
||||
@Nullable
|
||||
public static ArgType replaceClassGenerics(RootNode root, ArgType instanceType, ArgType typeWithGeneric) {
|
||||
if (typeWithGeneric == null) {
|
||||
return null;
|
||||
}
|
||||
if (instanceType.isGeneric()) {
|
||||
List<GenericInfo> generics = root.getClassGenerics(instanceType);
|
||||
if (generics.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
ArgType[] actualTypes = instanceType.getGenericTypes();
|
||||
if (actualTypes == null) {
|
||||
return null;
|
||||
}
|
||||
int genericParamsCount = actualTypes.length;
|
||||
if (genericParamsCount != generics.size()) {
|
||||
return null;
|
||||
}
|
||||
Map<ArgType, ArgType> replaceMap = new HashMap<>(genericParamsCount);
|
||||
for (int i = 0; i < genericParamsCount; i++) {
|
||||
ArgType actualType = actualTypes[i];
|
||||
ArgType genericType = generics.get(i).getGenericType();
|
||||
replaceMap.put(genericType, actualType);
|
||||
}
|
||||
return replaceGenericUsingTypeMap(typeWithGeneric, replaceMap);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ArgType replaceMethodGenerics(RootNode root, InsnNode invokeInsn, ArgType typeWithGeneric) {
|
||||
if (typeWithGeneric == null) {
|
||||
return null;
|
||||
}
|
||||
if (!(invokeInsn instanceof CallMthInterface)) {
|
||||
throw new JadxRuntimeException("Expected CallMthInterface, got: " + invokeInsn.getClass());
|
||||
}
|
||||
CallMthInterface callInsn = (CallMthInterface) invokeInsn;
|
||||
MethodInfo mthInfo = callInsn.getCallMth();
|
||||
List<ArgType> methodArgTypes = root.getMethodArgTypes(mthInfo);
|
||||
if (methodArgTypes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
int firstArgOffset = callInsn.getFirstArgOffset();
|
||||
int argsCount = methodArgTypes.size();
|
||||
for (int i = 0; i < argsCount; i++) {
|
||||
ArgType methodArgType = methodArgTypes.get(i);
|
||||
InsnArg insnArg = invokeInsn.getArg(i + firstArgOffset);
|
||||
ArgType insnType = insnArg.getType();
|
||||
if (methodArgType.equals(typeWithGeneric)) {
|
||||
return insnType;
|
||||
}
|
||||
}
|
||||
// TODO build complete map for type variables
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ArgType replaceGenericUsingTypeMap(ArgType replaceType, Map<ArgType, ArgType> replaceMap) {
|
||||
if (replaceType.isGenericType()) {
|
||||
return replaceMap.get(replaceType);
|
||||
}
|
||||
|
||||
ArgType wildcardType = replaceType.getWildcardType();
|
||||
if (wildcardType != null && wildcardType.containsGenericType()) {
|
||||
ArgType newWildcardType = replaceGenericUsingTypeMap(wildcardType, replaceMap);
|
||||
if (newWildcardType == null) {
|
||||
return null;
|
||||
}
|
||||
return ArgType.wildcard(newWildcardType, replaceType.getWildcardBound());
|
||||
}
|
||||
|
||||
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 = replaceGenericUsingTypeMap(genericType, replaceMap);
|
||||
if (type == null) {
|
||||
type = genericType;
|
||||
}
|
||||
newTypes[i] = type;
|
||||
}
|
||||
return ArgType.generic(replaceType.getObject(), newTypes);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private TypeUtils() {
|
||||
}
|
||||
}
|
||||
@@ -247,6 +247,13 @@ public class Utils {
|
||||
return list.get(list.size() - 1);
|
||||
}
|
||||
|
||||
public static <T> T getOrElse(@Nullable T obj, T defaultObj) {
|
||||
if (obj == null) {
|
||||
return defaultObj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
public static <T> boolean isEmpty(Collection<T> col) {
|
||||
return col == null || col.isEmpty();
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ class ArgTypeTest {
|
||||
@Test
|
||||
void testContainsGenericType() {
|
||||
ArgType wildcard = ArgType.wildcard(ArgType.genericType("T"), ArgType.WildcardBound.SUPER);
|
||||
assertTrue(wildcard.containsGenericType());
|
||||
assertTrue(wildcard.containsTypeVariable());
|
||||
|
||||
ArgType type = ArgType.generic("java.lang.List", wildcard);
|
||||
assertTrue(type.containsGenericType());
|
||||
assertTrue(type.containsTypeVariable());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.Collections;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
import jadx.NotYetImplementedExtension;
|
||||
@@ -14,19 +16,24 @@ import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
import static jadx.core.dex.instructions.args.ArgType.BOOLEAN;
|
||||
import static jadx.core.dex.instructions.args.ArgType.CHAR;
|
||||
import static jadx.core.dex.instructions.args.ArgType.CLASS;
|
||||
import static jadx.core.dex.instructions.args.ArgType.INT;
|
||||
import static jadx.core.dex.instructions.args.ArgType.NARROW;
|
||||
import static jadx.core.dex.instructions.args.ArgType.NARROW_INTEGRAL;
|
||||
import static jadx.core.dex.instructions.args.ArgType.OBJECT;
|
||||
import static jadx.core.dex.instructions.args.ArgType.STRING;
|
||||
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN;
|
||||
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_ARRAY;
|
||||
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_OBJECT;
|
||||
import static jadx.core.dex.instructions.args.ArgType.array;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static jadx.core.dex.instructions.args.ArgType.generic;
|
||||
import static jadx.core.dex.instructions.args.ArgType.wildcard;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(NotYetImplementedExtension.class)
|
||||
public class TypeCompareTest {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TypeCompareTest.class);
|
||||
|
||||
private TypeCompare compare;
|
||||
|
||||
@BeforeEach
|
||||
@@ -65,8 +72,21 @@ public class TypeCompareTest {
|
||||
|
||||
firstIsNarrow(array(OBJECT), OBJECT);
|
||||
firstIsNarrow(array(OBJECT), array(UNKNOWN_OBJECT));
|
||||
firstIsNarrow(array(STRING), array(UNKNOWN_OBJECT));
|
||||
firstIsNarrow(array(STRING), array(OBJECT));
|
||||
|
||||
firstIsNarrow(UNKNOWN_ARRAY, OBJECT);
|
||||
|
||||
check(array(OBJECT), array(INT), TypeCompareEnum.CONFLICT);
|
||||
|
||||
ArgType integerType = ArgType.object("java.lang.Integer");
|
||||
check(array(OBJECT), array(integerType), TypeCompareEnum.WIDER);
|
||||
check(array(INT), array(integerType), TypeCompareEnum.CONFLICT);
|
||||
check(array(INT), array(INT), TypeCompareEnum.EQUAL);
|
||||
|
||||
ArgType wildClass = generic(CLASS, wildcard());
|
||||
check(array(wildClass), array(CLASS), TypeCompareEnum.NARROW_BY_GENERIC);
|
||||
check(array(CLASS), array(wildClass), TypeCompareEnum.WIDER_BY_GENERIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -78,10 +98,15 @@ public class TypeCompareTest {
|
||||
ArgType valueType = ArgType.genericType("V");
|
||||
ArgType mapGeneric = ArgType.generic(mapCls.getObject(), keyType, valueType);
|
||||
|
||||
check(mapGeneric, mapCls, TypeCompareEnum.NARROW_BY_GENERIC);
|
||||
check(mapCls, mapGeneric, TypeCompareEnum.WIDER_BY_GENERIC);
|
||||
|
||||
check(mapCls, setCls, TypeCompareEnum.CONFLICT);
|
||||
|
||||
ArgType setGeneric = ArgType.generic(setCls.getObject(), valueType);
|
||||
ArgType setWildcard = ArgType.generic(setCls.getObject(), ArgType.wildcard());
|
||||
|
||||
check(setWildcard, setGeneric, TypeCompareEnum.CONFLICT);
|
||||
check(setWildcard, setCls, TypeCompareEnum.NARROW_BY_GENERIC);
|
||||
// TODO implement compare for wildcard with bounds
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -99,10 +124,7 @@ public class TypeCompareTest {
|
||||
tType.setExtendTypes(Collections.singletonList(ArgType.STRING));
|
||||
|
||||
check(tType, ArgType.STRING, TypeCompareEnum.NARROW_BY_GENERIC);
|
||||
check(ArgType.STRING, tType, TypeCompareEnum.WIDER_BY_GENERIC);
|
||||
|
||||
check(tType, ArgType.OBJECT, TypeCompareEnum.NARROW_BY_GENERIC);
|
||||
check(ArgType.OBJECT, tType, TypeCompareEnum.WIDER_BY_GENERIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -111,18 +133,21 @@ public class TypeCompareTest {
|
||||
ArgType vType = ArgType.genericType("V");
|
||||
// TODO: use extend types from generic declaration for more strict checks
|
||||
check(vType, ArgType.STRING, TypeCompareEnum.CONFLICT);
|
||||
check(ArgType.STRING, vType, TypeCompareEnum.CONFLICT);
|
||||
}
|
||||
|
||||
private void firstIsNarrow(ArgType first, ArgType second) {
|
||||
check(first, second, TypeCompareEnum.NARROW);
|
||||
// reverse
|
||||
check(second, first, TypeCompareEnum.WIDER);
|
||||
}
|
||||
|
||||
private void check(ArgType first, ArgType second, TypeCompareEnum expectedResult) {
|
||||
TypeCompareEnum result = compare.compareTypes(first, second);
|
||||
assertThat("Compare '" + first + "' vs '" + second + '\'',
|
||||
result, is(expectedResult));
|
||||
LOG.debug("Compare: '{}' and '{}', expect: '{}'", first, second, expectedResult);
|
||||
|
||||
assertThat(compare.compareTypes(first, second))
|
||||
.as("Compare '%s' and '%s'", first, second)
|
||||
.isEqualTo(expectedResult);
|
||||
|
||||
assertThat(compare.compareTypes(second, first))
|
||||
.as("Compare '%s' and '%s'", second, first)
|
||||
.isEqualTo(expectedResult.invert());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
@@ -10,7 +9,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.GenericInfo;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
@@ -25,17 +24,16 @@ class TypeUtilsTest {
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
root = new RootNode(new JadxArgs());
|
||||
root.load(Collections.emptyList());
|
||||
root.initClassPath();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceGenericsWithWildcards() {
|
||||
// check classpath graph
|
||||
List<GenericInfo> classGenerics = root.getClassGenerics(ArgType.object("java.util.ArrayList"));
|
||||
List<GenericTypeParameter> classGenerics = root.getTypeUtils().getClassGenerics(ArgType.object("java.util.ArrayList"));
|
||||
assertThat(classGenerics, hasSize(1));
|
||||
GenericInfo genericInfo = classGenerics.get(0);
|
||||
assertThat(genericInfo.getGenericType(), is(ArgType.genericType("E")));
|
||||
GenericTypeParameter genericInfo = classGenerics.get(0);
|
||||
assertThat(genericInfo.getTypeVariable(), is(ArgType.genericType("E")));
|
||||
assertThat(genericInfo.getExtendsList(), hasSize(0));
|
||||
|
||||
// prepare input
|
||||
@@ -46,7 +44,7 @@ class TypeUtilsTest {
|
||||
LOG.debug("generic: {}", generic);
|
||||
|
||||
// replace
|
||||
ArgType result = TypeUtils.replaceClassGenerics(root, instanceType, generic);
|
||||
ArgType result = root.getTypeUtils().replaceClassGenerics(instanceType, generic);
|
||||
LOG.debug("result: {}", result);
|
||||
|
||||
ArgType expected = ArgType.generic("java.util.List", ArgType.wildcard(ArgType.OBJECT, ArgType.WildcardBound.SUPER));
|
||||
|
||||
@@ -1,39 +1,34 @@
|
||||
package jadx.tests.functional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.clsp.ClspGraph;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
|
||||
import static jadx.core.dex.instructions.args.ArgType.STRING;
|
||||
import static jadx.core.dex.instructions.args.ArgType.object;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class JadxClasspathTest {
|
||||
|
||||
private static final String JAVA_LANG_EXCEPTION = "java.lang.Exception";
|
||||
private static final String JAVA_LANG_THROWABLE = "java.lang.Throwable";
|
||||
|
||||
private DexNode dex;
|
||||
private RootNode root;
|
||||
private ClspGraph clsp;
|
||||
|
||||
@BeforeEach
|
||||
public void initClsp() throws IOException, DecodeException {
|
||||
clsp = new ClspGraph();
|
||||
clsp.load();
|
||||
dex = mock(DexNode.class);
|
||||
RootNode rootNode = mock(RootNode.class);
|
||||
when(rootNode.getClsp()).thenReturn(clsp);
|
||||
when(dex.root()).thenReturn(rootNode);
|
||||
public void initClsp() {
|
||||
this.root = new RootNode(new JadxArgs());
|
||||
this.root.load(Collections.emptyList());
|
||||
this.root.initClassPath();
|
||||
this.clsp = root.getClsp();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -44,9 +39,9 @@ public class JadxClasspathTest {
|
||||
assertTrue(clsp.isImplements(JAVA_LANG_EXCEPTION, JAVA_LANG_THROWABLE));
|
||||
assertFalse(clsp.isImplements(JAVA_LANG_THROWABLE, JAVA_LANG_EXCEPTION));
|
||||
|
||||
assertFalse(ArgType.isCastNeeded(dex, objExc, objThr));
|
||||
assertTrue(ArgType.isCastNeeded(dex, objThr, objExc));
|
||||
assertFalse(ArgType.isCastNeeded(root, objExc, objThr));
|
||||
assertTrue(ArgType.isCastNeeded(root, objThr, objExc));
|
||||
|
||||
assertTrue(ArgType.isCastNeeded(dex, ArgType.OBJECT, STRING));
|
||||
assertTrue(ArgType.isCastNeeded(root, ArgType.OBJECT, STRING));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.ArgType.WildcardBound;
|
||||
import jadx.core.dex.nodes.GenericInfo;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
|
||||
import static jadx.core.dex.instructions.args.ArgType.INT;
|
||||
@@ -92,12 +92,12 @@ class SignatureParserTest {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void checkGenerics(String g, Object... objs) {
|
||||
List<GenericInfo> genericsList = new SignatureParser(g).consumeGenericMap();
|
||||
List<GenericInfo> expectedList = new ArrayList<>();
|
||||
List<GenericTypeParameter> genericsList = new SignatureParser(g).consumeGenericTypeParameters();
|
||||
List<GenericTypeParameter> 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];
|
||||
expectedList.add(new GenericInfo(generic, list));
|
||||
expectedList.add(new GenericTypeParameter(generic, list));
|
||||
}
|
||||
assertThat(genericsList, is(expectedList));
|
||||
}
|
||||
@@ -122,7 +122,7 @@ class SignatureParserTest {
|
||||
|
||||
@Test
|
||||
public void testBadGenericMap() {
|
||||
List<GenericInfo> list = new SignatureParser("<A:Ljava/lang/Object;B").consumeGenericMap();
|
||||
List<GenericTypeParameter> list = new SignatureParser("<A:Ljava/lang/Object;B").consumeGenericTypeParameters();
|
||||
assertThat(list, hasSize(0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class TestElseIf extends IntegrationTest {
|
||||
assertThat(code, containsOne("r = 1;"));
|
||||
assertThat(code, containsOne("r = -1;"));
|
||||
// no ternary operator
|
||||
assertThat(code, not(containsString("?")));
|
||||
assertThat(code, not(containsString(":")));
|
||||
assertThat(code, not(containsString(" ? ")));
|
||||
assertThat(code, not(containsString(" : ")));
|
||||
}
|
||||
}
|
||||
|
||||
+13
-4
@@ -29,24 +29,33 @@ public class TestCastInOverloadedInvoke extends IntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
public void test3() {
|
||||
call((String) null);
|
||||
call((List<String>) null);
|
||||
call((ArrayList<String>) null);
|
||||
}
|
||||
|
||||
public void call(String str) {
|
||||
c += 1;
|
||||
}
|
||||
|
||||
public void call(List<String> list) {
|
||||
c += 2;
|
||||
c += 10;
|
||||
}
|
||||
|
||||
public void call(ArrayList<String> list) {
|
||||
c += 4;
|
||||
c += 100;
|
||||
}
|
||||
|
||||
public void check() {
|
||||
test();
|
||||
assertThat(c, is(2 + 4));
|
||||
assertThat(c, is(10 + 100));
|
||||
c = 0;
|
||||
test2("str");
|
||||
assertThat(c, is(1));
|
||||
c = 0;
|
||||
test3();
|
||||
assertThat(c, is(111));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +72,7 @@ public class TestCastInOverloadedInvoke extends IntegrationTest {
|
||||
|
||||
@NotYetImplemented
|
||||
@Test
|
||||
public void test2() {
|
||||
public void testNYI() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package jadx.tests.integration.invoke;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test cast for 'unknown' but overloaded method
|
||||
*/
|
||||
public class TestCastInOverloadedInvoke3 extends IntegrationTest {
|
||||
|
||||
public static class OuterCls {
|
||||
static int c = 0;
|
||||
|
||||
public static void call(String str) {
|
||||
c = 1;
|
||||
}
|
||||
|
||||
public static void call(List<String> list) {
|
||||
c = 10;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestCls {
|
||||
public void test() {
|
||||
OuterCls.call((String) null);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
disableCompilation();
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("OuterCls.call((String) null);");
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package jadx.tests.integration.invoke;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
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;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class TestHierarchyOverloadedInvoke extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
static int c = 0;
|
||||
B b = new B();
|
||||
|
||||
public interface I {
|
||||
default void call(String str) {
|
||||
c += 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static class A implements I {
|
||||
public void call(List<String> list) {
|
||||
c += 10;
|
||||
}
|
||||
}
|
||||
|
||||
public static class B extends A {
|
||||
public void call(ArrayList<String> list) {
|
||||
c += 100;
|
||||
}
|
||||
}
|
||||
|
||||
public void test() {
|
||||
b.call(new ArrayList<>());
|
||||
b.call((List<String>) new ArrayList<String>());
|
||||
}
|
||||
|
||||
public void test2(Object obj) {
|
||||
if (obj instanceof String) {
|
||||
b.call((String) obj);
|
||||
}
|
||||
}
|
||||
|
||||
public void test3() {
|
||||
b.call((String) null);
|
||||
b.call((List<String>) null);
|
||||
b.call((ArrayList<String>) null);
|
||||
}
|
||||
|
||||
public void test4() {
|
||||
((I) b).call(null);
|
||||
((A) b).call((String) null);
|
||||
((A) b).call((List<String>) null);
|
||||
}
|
||||
|
||||
public void check() {
|
||||
test();
|
||||
assertThat(c, is(10 + 100));
|
||||
|
||||
c = 0;
|
||||
test2("str");
|
||||
assertThat(c, is(1));
|
||||
|
||||
c = 0;
|
||||
test3();
|
||||
assertThat(c, is(111));
|
||||
|
||||
c = 0;
|
||||
test4();
|
||||
assertThat(c, is(12));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsOne("b.call((ArrayList<String>) new ArrayList());"));
|
||||
assertThat(code, containsOne("b.call((List<String>) new ArrayList());"));
|
||||
|
||||
assertThat(code, containsOne("b.call((String) obj);"));
|
||||
}
|
||||
|
||||
@NotYetImplemented
|
||||
@Test
|
||||
public void test2() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsOne("b.call(new ArrayList<>());"));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -12,7 +12,7 @@ public class TestSuperInvokeWithGenerics extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
|
||||
public class A<T extends Exception, V> {
|
||||
public static class A<T extends Exception, V> {
|
||||
public A(T t) {
|
||||
System.out.println("t" + t);
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public class TestSuperInvokeWithGenerics extends IntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
public class B extends A<Exception, String> {
|
||||
public static class B extends A<Exception, String> {
|
||||
public B(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class TestDeboxing2 extends IntegrationTest {
|
||||
assertThat(code, containsOne("l = 0L;"));
|
||||
|
||||
// checks for 'check' method
|
||||
assertThat(code, containsOne("test((Long) null)")); // TODO: cast not needed
|
||||
assertThat(code, containsOne("test(null)"));
|
||||
assertThat(code, containsOne("test(0L)"));
|
||||
assertThat(code, countString(2, "is(0L)"));
|
||||
assertThat(code, containsOne("test(7L)"));
|
||||
|
||||
@@ -45,7 +45,7 @@ public class TestLoopInTry2 extends IntegrationTest {
|
||||
|
||||
assertThat(code, containsOne("try {"));
|
||||
assertThat(code, containsOne("while (in.hasMore()) {"));
|
||||
assertThat(code, containsOne("decoded[in.cursor()] = DecodedInstruction.decode(in);"));
|
||||
assertThat(code, containsOne("decoded[in.cursor()] = DecodedInstruction.decode("));
|
||||
assertThat(code, containsOne("} catch (EOFException e) {"));
|
||||
assertThat(code, containsOne("throw new DecodeException"));
|
||||
}
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ public class TestStringBuilderElimination2 extends IntegrationTest {
|
||||
public void test1() {
|
||||
ClassNode cls = getClassNode(TestStringBuilderElimination2.TestCls1.class);
|
||||
String code = cls.getCode().toString();
|
||||
assertThat(code, containsString("return \"[init]\" + \"a1\" + 'c' + 2 + 0 + 1.0f + 2.0d + true;"));
|
||||
assertThat(code, containsString("return \"[init]\" + \"a1\" + 'c' + 2 + 0L + 1.0f + 2.0d + true;"));
|
||||
}
|
||||
|
||||
public static class TestCls2 {
|
||||
@@ -49,7 +49,7 @@ public class TestStringBuilderElimination2 extends IntegrationTest {
|
||||
public void test2() {
|
||||
ClassNode cls = getClassNode(TestStringBuilderElimination2.TestCls2.class);
|
||||
String code = cls.getCode().toString();
|
||||
assertThat(code, containsString("return \"[init]\" + \"a1\" + 'c' + 1 + 2 + 1.0f + 2.0d + true;"));
|
||||
assertThat(code, containsString("return \"[init]\" + \"a1\" + 'c' + 1 + 2L + 1.0f + 2.0d + true;"));
|
||||
}
|
||||
|
||||
public static class TestClsStringUtilsReverse {
|
||||
|
||||
@@ -6,7 +6,8 @@ import java.io.OutputStream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.core.clsp.NClass;
|
||||
import jadx.core.clsp.ClspClass;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
@@ -16,21 +17,21 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||
public class TestTryCatchFinally2 extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
private NClass[] classes;
|
||||
private ClspClass[] classes;
|
||||
|
||||
public void test(OutputStream output) throws IOException {
|
||||
DataOutputStream out = new DataOutputStream(output);
|
||||
try {
|
||||
out.writeByte(1);
|
||||
out.writeInt(classes.length);
|
||||
for (NClass cls : classes) {
|
||||
for (ClspClass cls : classes) {
|
||||
writeString(out, cls.getName());
|
||||
}
|
||||
for (NClass cls : classes) {
|
||||
NClass[] parents = cls.getParents();
|
||||
for (ClspClass cls : classes) {
|
||||
ArgType[] parents = cls.getParents();
|
||||
out.writeByte(parents.length);
|
||||
for (NClass parent : parents) {
|
||||
out.writeInt(parent.getId());
|
||||
for (ArgType parent : parents) {
|
||||
out.writeInt(parent.getObject().hashCode());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -50,9 +51,9 @@ public class TestTryCatchFinally2 extends IntegrationTest {
|
||||
assertThat(code, containsOne("} finally {"));
|
||||
assertThat(code, containsOne("out.close();"));
|
||||
|
||||
assertThat(code, containsOne("for (NClass parent : parents) {"));
|
||||
assertThat(code, containsOne("for (ArgType parent : parents) {"));
|
||||
|
||||
assertThat(code, containsOne("for (NClass cls : this.classes) {"));
|
||||
assertThat(code, containsOne("for (NClass cls2 : this.classes) {"));
|
||||
assertThat(code, containsOne("for (ClspClass cls : this.classes) {"));
|
||||
assertThat(code, containsOne("for (ClspClass cls2 : this.classes) {"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,5 +32,6 @@ public class TestGenerics2 extends SmaliTest {
|
||||
|
||||
assertThat(code, containsOne("Entry<Integer, String> next"));
|
||||
assertThat(code, containsOne("useInt(next.getKey().intValue());")); // no Integer cast
|
||||
assertThat(code, containsOne("next.getValue().trim();")); // no String cast
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public class TestGenerics4 extends IntegrationTest {
|
||||
|
||||
@NotYetImplemented
|
||||
@Test
|
||||
public void testNYI() {
|
||||
public void testOmitCast() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.lang.ref.WeakReference;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
@@ -31,13 +30,12 @@ public class TestTypeResolver12 extends IntegrationTest {
|
||||
.containsOne("T obj = this.ref.get();");
|
||||
}
|
||||
|
||||
@NotYetImplemented("Generic type inference")
|
||||
@Test
|
||||
public void testNoDebug() {
|
||||
noDebugInfo();
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.doesNotContain("Object obj")
|
||||
.containsOne("T obj = this.ref.get();");
|
||||
.containsOne("T t = this.ref.get();");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package jadx.tests.integration.types;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestTypeResolver13 extends IntegrationTest {
|
||||
|
||||
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
|
||||
public static class TestCls {
|
||||
private static final Set<?> CONST = new HashSet<>();
|
||||
private Map<Set<?>, List<?>> map = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> test(Set<T> type) {
|
||||
List<?> obj = this.map.get(type == null ? CONST : type);
|
||||
if (obj != null) {
|
||||
return (List<T>) obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NotYetImplemented("additional cast for generic types")
|
||||
@Test
|
||||
public void test() {
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("public <T> List<T> test(Set<T> type) {")
|
||||
.containsOne("return (List<T>) obj;");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user