diff --git a/jadx-core/clsp-data/android-29-clst.jar b/jadx-core/clsp-data/android-29-clst.jar index 5a4e2de98..cc5c4432d 100644 Binary files a/jadx-core/clsp-data/android-29-clst.jar and b/jadx-core/clsp-data/android-29-clst.jar differ diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index 79f566ed4..93e52d30a 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -327,6 +327,14 @@ public class JadxArgs { } } + public void setRenameFlags(Set renameFlags) { + this.renameFlags = renameFlags; + } + + public Set getRenameFlags() { + return renameFlags; + } + public OutputFormatEnum getOutputFormat() { return outputFormat; } diff --git a/jadx-core/src/main/java/jadx/api/JavaMethod.java b/jadx-core/src/main/java/jadx/api/JavaMethod.java index 53a51e94b..82b5ac492 100644 --- a/jadx-core/src/main/java/jadx/api/JavaMethod.java +++ b/jadx-core/src/main/java/jadx/api/JavaMethod.java @@ -47,18 +47,12 @@ public final class JavaMethod implements JavaNode { return Collections.emptyList(); } List 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); } diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 02fd8fe24..bf0d8a095 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -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()); diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java b/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java index 426b8a8a7..565a11b9b 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java @@ -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 list = root.getClasses(true); - Map names = new HashMap<>(list.size()); + Map 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 getMethodsDetails(ClassNode cls) { - List methods = new ArrayList<>(); - for (MethodNode m : cls.getMethods()) { - AccessInfo accessFlags = m.getAccessFlags(); - if (accessFlags.isPublic() || accessFlags.isProtected()) { - processMethodDetails(methods, m, accessFlags); - } + private List getMethodsDetails(ClassNode cls) { + List methodsList = cls.getMethods(); + List methods = new ArrayList<>(methodsList.size()); + for (MethodNode mth : methodsList) { + processMethodDetails(mth, methods); } return methods; } - private void processMethodDetails(List methods, MethodNode mth, AccessInfo accessFlags) { - List 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 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 throwList = mth.getThrows(); + List 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 names) { - List 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 names) { - NClass cls = names.get(fullName); + private static ClspClass getCls(ClassNode cls, Map names) { + return getCls(cls.getRawName(), names); + } + + private static ClspClass getCls(ArgType clsType, Map names) { + return getCls(clsType.getObject(), names); + } + + private static ClspClass getCls(String fullName, Map 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 names = new HashMap<>(classes.length); + Map 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 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 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 names) throws IOException { - List genericsList = cls.getGenerics(); - out.writeByte(genericsList.size()); - for (GenericInfo genericInfo : genericsList) { - writeArgType(out, genericInfo.getGenericType(), names); - List extendsList = genericInfo.getExtendsList(); - out.writeByte(extendsList.size()); - for (ArgType type : extendsList) { - writeArgType(out, type, names); - } - - } - } - - private static void writeMethod(DataOutputStream out, NMethod method, Map names) throws IOException { - writeLongString(out, method.getShortId()); - - ArgType[] argTypes = method.getGenericArgs(); - if (argTypes == null) { + private static void writeGenericTypeParameters(DataOutputStream out, + List typeParameters, + Map 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 names) throws IOException { - if (argType.getWildcardType() != null) { + private static void writeMethod(DataOutputStream out, ClspMethod method, Map 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 list, Map 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 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 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 readGenerics(DataInputStream in) throws IOException { - int count = in.readByte(); + private List readGenericTypeParameters(DataInputStream in) throws IOException { + int count = readUnsignedByte(in); if (count == 0) { return Collections.emptyList(); } - List list = new ArrayList<>(count); + List list = new ArrayList<>(count); for (int i = 0; i < count; i++) { - ArgType genericType = readArgType(in); - List 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 extendsList = readArgTypesList(in); + list.add(new GenericTypeParameter(typeVariable, extendsList)); } return list; } - private List readClsMethods(DataInputStream in) throws IOException { - int mCount = in.readByte(); - List methods = new ArrayList<>(mCount); + private List readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException { + int mCount = in.readShort(); + List 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 argTypes = readArgTypesList(in); + ArgType retType = readArgType(in); + List 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 typeParameters = readGenericTypeParameters(in); boolean varArgs = in.readBoolean(); - return new NMethod(shortId, argTypes, retType, varArgs); + List throwList = readArgTypesList(in); + MethodInfo methodInfo = MethodInfo.fromDetails(root, clsInfo, name, argTypes, retType); + return new ClspMethod(methodInfo, + genericArgTypes, genericRetType, + typeParameters, varArgs, throwList); + } + + private List readArgTypesList(DataInputStream in) throws IOException { + int count = in.readByte(); + if (count == 0) { + return Collections.emptyList(); + } + List 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 nameMap) { - for (NClass cls : classes) { + public void addToMap(Map nameMap) { + for (ClspClass cls : classes) { nameMap.put(cls.getName(), cls); } } diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClspClass.java b/jadx-core/src/main/java/jadx/core/clsp/ClspClass.java new file mode 100644 index 000000000..97a01ccb3 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/clsp/ClspClass.java @@ -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 methodsMap = Collections.emptyMap(); + private List 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 getMethodsMap() { + return methodsMap; + } + + public List getSortedMethodsList() { + List list = new ArrayList<>(methodsMap.size()); + list.addAll(methodsMap.values()); + Collections.sort(list); + return list; + } + + public void setMethodsMap(Map methodsMap) { + this.methodsMap = Objects.requireNonNull(methodsMap); + } + + public void setMethods(List methods) { + Map map = new HashMap<>(methods.size()); + for (ClspMethod mth : methods) { + map.put(mth.getMethodInfo().getShortId(), mth); + } + setMethodsMap(map); + } + + public List getTypeParameters() { + return typeParameters; + } + + public void setTypeParameters(List 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(); + } +} diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java b/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java index abdfc9475..c8a7e9eaa 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java @@ -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> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>()); - private Map nameMap; + private Map nameMap; private final Set 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 anc, NClass cls) { - for (NClass p : cls.getParents()) { - String name = p.getName(); + private String searchCommonParent(Set 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 result) { + private void addAncestorsNames(ClspClass cls, Set 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) { diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClspMethod.java b/jadx-core/src/main/java/jadx/core/clsp/ClspMethod.java new file mode 100644 index 000000000..250adeeb6 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/clsp/ClspMethod.java @@ -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 { + + private final MethodInfo methodInfo; + private final List argTypes; + private final ArgType returnType; + private final List typeParameters; + private final List throwList; + private final boolean varArg; + + public ClspMethod(MethodInfo methodInfo, + List argTypes, ArgType returnType, + List typeParameters, + boolean varArgs, List 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 getArgTypes() { + return argTypes; + } + + public boolean containsGenericArgs() { + return !Objects.equals(argTypes, methodInfo.getArgumentsTypes()); + } + + public int getArgsCount() { + return argTypes.size(); + } + + @Override + public List getTypeParameters() { + return typeParameters; + } + + @Override + public List 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 throwsList = getThrows(); + if (Utils.notEmpty(throwsList)) { + sb.append(" throws ").append(Utils.listToString(throwsList)); + } + sb.append('}'); + return sb.toString(); + } +} diff --git a/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java b/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java index adf4addb2..95ae11ac2 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java @@ -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"); } diff --git a/jadx-core/src/main/java/jadx/core/clsp/NClass.java b/jadx-core/src/main/java/jadx/core/clsp/NClass.java deleted file mode 100644 index 08dfac6e8..000000000 --- a/jadx-core/src/main/java/jadx/core/clsp/NClass.java +++ /dev/null @@ -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 methodsMap = Collections.emptyMap(); - private List 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 getMethodsMap() { - return methodsMap; - } - - public List getMethodsList() { - List list = new ArrayList<>(methodsMap.size()); - list.addAll(methodsMap.values()); - Collections.sort(list); - return list; - } - - public void setMethodsMap(Map methodsMap) { - this.methodsMap = Objects.requireNonNull(methodsMap); - } - - public void setMethods(List methods) { - Map map = new HashMap<>(methods.size()); - for (NMethod mth : methods) { - map.put(mth.getShortId(), mth); - } - setMethodsMap(map); - } - - public List getGenerics() { - return generics; - } - - public void setGenerics(List 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; - } -} diff --git a/jadx-core/src/main/java/jadx/core/clsp/NMethod.java b/jadx-core/src/main/java/jadx/core/clsp/NMethod.java deleted file mode 100644 index 70cf7c084..000000000 --- a/jadx-core/src/main/java/jadx/core/clsp/NMethod.java +++ /dev/null @@ -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 { - - 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 - + '}'; - } -} diff --git a/jadx-core/src/main/java/jadx/core/clsp/SimpleMethodDetails.java b/jadx-core/src/main/java/jadx/core/clsp/SimpleMethodDetails.java new file mode 100644 index 000000000..d1ada832c --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/clsp/SimpleMethodDetails.java @@ -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 getArgTypes() { + return methodInfo.getArgumentsTypes(); + } + + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } + + @Override + public List getThrows() { + return Collections.emptyList(); + } + + @Override + public boolean isVarArg() { + return false; + } + + @Override + public String toString() { + return "SimpleMethodDetails{" + methodInfo + '}'; + } +} diff --git a/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java b/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java index 7bcf11ab5..01f4f7889 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java @@ -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 throwList = mth.getThrows(); + if (!throwList.isEmpty()) { code.add(" throws "); - for (Iterator it = ((List) exs).iterator(); it.hasNext();) { + for (Iterator it = throwList.iterator(); it.hasNext();) { ArgType ex = it.next(); classGen.useType(code, ex); if (it.hasNext()) { diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index 4a83d5c35..5331a8e65 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -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 generics, boolean classDeclaration) { + public boolean addGenericTypeParameters(CodeWriter code, List 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 { diff --git a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java index 8de03f0b3..f6890d505 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -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 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 state) throws CodegenException { diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index b9ee51edb..09d4ed00f 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -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()) { diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java index 69348444e..c4f862442 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java @@ -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' diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java index 2cdab5b73..00d018af4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java @@ -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 { // instruction public static final AType LOOP_LABEL = new AType<>(); public static final AType> JUMP = new AType<>(); + public static final AType METHOD_DETAILS = new AType<>(); // register public static final AType REG_DEBUG_INFO = new AType<>(); diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java index 789bac206..2d342b9d1 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java @@ -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 flags; private Map, 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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/IAttribute.java b/jadx-core/src/main/java/jadx/core/dex/attributes/IAttribute.java index cac361c26..9eb94d8e5 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/IAttribute.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/IAttribute.java @@ -2,4 +2,8 @@ package jadx.core.dex.attributes; public interface IAttribute { AType getType(); + + default String toAttrString() { + return this.toString(); + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index 7e166566f..e58e3b33c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -39,7 +39,7 @@ public final class ClassInfo implements Comparable { 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)); } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java index 80e4e8f36..4996d18f8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java @@ -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 classes = new HashMap<>(); - private final Map methods = new HashMap<>(); private final Map fields = new HashMap<>(); + private final Map methods = new HashMap<>(); + // use only one MethodInfo instance + private final Map 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; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java index a37c45dbc..f8058cb0f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java @@ -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 { private final String name; private final ArgType retType; - private final List args; + private final List 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 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 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 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 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 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 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; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/BaseInvokeNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/BaseInvokeNode.java new file mode 100644 index 000000000..02234e741 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/BaseInvokeNode.java @@ -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(); +} diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/CallMthInterface.java b/jadx-core/src/main/java/jadx/core/dex/instructions/CallMthInterface.java deleted file mode 100644 index 823ca0e22..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/CallMthInterface.java +++ /dev/null @@ -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(); -} diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java index f0099e76f..9b850c919 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java @@ -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; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java index 783e147d5..fe6fff83d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java index 7a590cbc1..7a900d340 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java @@ -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; diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 9ba4da5a6..8d3867083 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -54,7 +54,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { private AccessInfo accessFlags; private ArgType superClass; private List interfaces; - private List generics = Collections.emptyList(); + private List generics = Collections.emptyList(); private final List methods; private final List 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 getGenerics() { + public List getGenericTypeParameters() { return generics; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/GenericInfo.java b/jadx-core/src/main/java/jadx/core/dex/nodes/GenericInfo.java deleted file mode 100644 index e70e5649b..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/GenericInfo.java +++ /dev/null @@ -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 extendsList; - - public GenericInfo(ArgType genericType, List extendsList) { - this.genericType = genericType; - this.extendsList = extendsList; - } - - public ArgType getGenericType() { - return genericType; - } - - public List 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 + '}'; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/GenericTypeParameter.java b/jadx-core/src/main/java/jadx/core/dex/nodes/GenericTypeParameter.java new file mode 100644 index 000000000..61f974755 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/GenericTypeParameter.java @@ -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 extendsList; + + public GenericTypeParameter(ArgType typeVariable, List extendsList) { + this.typeVariable = typeVariable; + this.extendsList = extendsList; + } + + public ArgType getTypeVariable() { + return typeVariable; + } + + public List 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(); + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/IMethodDetails.java b/jadx-core/src/main/java/jadx/core/dex/nodes/IMethodDetails.java new file mode 100644 index 000000000..eaf04388f --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/IMethodDetails.java @@ -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 getArgTypes(); + + List getTypeParameters(); + + List getThrows(); + + boolean isVarArg(); + + @Override + default AType 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 throwsList = getThrows(); + if (Utils.notEmpty(throwsList)) { + sb.append(" throws ").append(Utils.listToString(throwsList)); + } + return sb.toString(); + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java index 703c824c5..86fee5ce1 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java @@ -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 diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index bfda66775..fd2aee0fd 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -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 argTypes; - private List generics; + private List 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 argsTypes = sp.consumeMethodArgs(); this.retType = sp.consumeType(); @@ -264,17 +267,32 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode { } } + @Override @NotNull public List 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 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 getGenerics() { - return generics; + @Override + public List 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 getThrows() { + Annotation an = getAnnotation(Consts.DALVIK_THROWS); + if (an == null) { + return Collections.emptyList(); + } + return (List) 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; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index 781a047d5..32d20823d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -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 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 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 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 argTypes = callMth.getArgumentsTypes(); - int argsCount = argTypes.size(); - List 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 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 generics = clsDetails.getGenerics(); - return generics == null ? Collections.emptyList() : generics; - } - public List getDexNodes() { return dexNodes; } @@ -335,4 +305,11 @@ public class RootNode { return codeCache; } + public MethodUtils getMethodUtils() { + return methodUtils; + } + + public TypeUtils getTypeUtils() { + return typeUtils; + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java index ebdb3fbaf..4b70dfd7c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java @@ -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 { *

* Example: "" */ - public List consumeGenericMap() { + public List consumeGenericTypeParameters() { if (!lookAhead('<')) { return Collections.emptyList(); } - List list = new ArrayList<>(); + List 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 types = consumeExtendsTypesList(); - list.add(new GenericInfo(ArgType.genericType(id), types)); + list.add(new GenericTypeParameter(ArgType.genericType(id), types)); } consume('>'); return list; diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/utils/MethodUtils.java b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/MethodUtils.java new file mode 100644 index 000000000..67f24e708 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/MethodUtils.java @@ -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 collectOverloadedMethods(ArgType startCls, MethodInfo mthInfo) { + List 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 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; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java new file mode 100644 index 000000000..0bb936df0 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java @@ -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 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 generics = clsDetails.getTypeParameters(); + return generics == null ? Collections.emptyList() : generics; + } + + /** + * Replace generic types in {@code typeWithGeneric} using instance types + *
+ * Example: + *

    + *
  • {@code instanceType: Set} + *
  • {@code typeWithGeneric: Iterator} + *
  • {@code return: Iterator} + *
+ */ + @Nullable + public ArgType replaceClassGenerics(ArgType instanceType, ArgType typeWithGeneric) { + if (typeWithGeneric != null) { + Map replaceMap = getTypeVariablesMapping(instanceType); + if (!replaceMap.isEmpty()) { + return replaceTypeVariablesUsingMap(typeWithGeneric, replaceMap); + } + } + return null; + } + + public Map getTypeVariablesMapping(ArgType clsType) { + if (!clsType.isGeneric()) { + return Collections.emptyMap(); + } + + List 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 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 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 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; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java new file mode 100644 index 000000000..24e2ec09a --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java @@ -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); + } + } + +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java index 44c210c69..4dfe07854 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DeboxingVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DeboxingVisitor.java index 4dd248496..d10fbee2c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DeboxingVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DeboxingVisitor.java @@ -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 diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java new file mode 100644 index 000000000..acd725e23 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java @@ -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 overloadMethods = root.getMethodUtils().collectOverloadedMethods(callCls, callMth); + if (overloadMethods.isEmpty()) { + // not overloaded + return; + } + + overloadMethods.add(mthDetails); + resolveTypeVariablesInMethodArgs(invokeInsn, mthDetails, overloadMethods); + + int argsOffset = invokeInsn.getFirstArgOffset(); + List compilerVarTypes = collectCompilerVarTypes(invokeInsn, argsOffset); + List 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 compilerVarTypes = collectCompilerVarTypes(invokeInsn, argsOffset); + List 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 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 typeVarsMapping = root.getTypeUtils().getTypeVariablesMapping(callClsType); + resolveTypeVars(mthDetails, typeVarsMapping); + for (IMethodDetails m : overloadedMethods) { + resolveTypeVars(m, typeVarsMapping); + } + } + + private void applyArgsCast(BaseInvokeNode invokeInsn, int argsOffset, List compilerVarTypes, List 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 typeVarsMapping) { + List 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 searchCastTypes(MethodNode parentMth, IMethodDetails mthDetails, List overloadedMethods, + List compilerVarTypes) { + // try compile types + if (isOverloadResolved(mthDetails, overloadedMethods, compilerVarTypes)) { + return compilerVarTypes; + } + int argsCount = compilerVarTypes.size(); + List 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 castTypes, List 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 overloadedMethods, List castTypes) { + if (overloadedMethods.isEmpty()) { + return false; + } + // TODO: search closest method, instead filtering + List strictMethods = filterApplicableMethods(overloadedMethods, castTypes, MethodInvokeVisitor::isStrictTypes); + if (strictMethods.size() == 1) { + return strictMethods.get(0).equals(expectedMthDetails); + } + List 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 filterApplicableMethods(List methods, List types, + Function acceptFunction) { + List 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 types, + Function acceptFunction) { + List 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 collectCompilerVarTypes(BaseInvokeNode insn, int argOffset) { + int argsCount = insn.getArgsCount(); + List 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); + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java index 96971ab60..64ee4d9e4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java @@ -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()); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java index 2e8ae2ddf..33e487345 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java @@ -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()); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java index ffdd7d220..4e703458e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java index 726aa4a12..561e92cd3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java @@ -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; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java index f25ca9b64..35a5fa122 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/IfRegionVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/IfRegionVisitor.java index dc6b1e3b0..b3e09b379 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/IfRegionVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/IfRegionVisitor.java @@ -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; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ReturnVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ReturnVisitor.java index 5fad69e3e..87d3c7598 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ReturnVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ReturnVisitor.java @@ -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()); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java index 6fc11c4d0..36b0042fd 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundInvokeAssign.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundInvokeAssign.java index ad263a789..b40227a3c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundInvokeAssign.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundInvokeAssign.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java index ad1239fe3..3e7905695 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java @@ -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)) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompareEnum.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompareEnum.java index 1a42c8a32..7af383b2d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompareEnum.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompareEnum.java @@ -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; + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java index 888aa4039..a86e3f4c7 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java @@ -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); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java index 636293a9c..94149629b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/utils/TypeUtils.java b/jadx-core/src/main/java/jadx/core/utils/TypeUtils.java deleted file mode 100644 index 13a35bb9b..000000000 --- a/jadx-core/src/main/java/jadx/core/utils/TypeUtils.java +++ /dev/null @@ -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 - *
- * Example: - *
    - *
  • {@code instanceType: Set} - *
  • {@code typeWithGeneric: Iterator} - *
  • {@code return: Iterator} - *
- */ - @Nullable - public static ArgType replaceClassGenerics(RootNode root, ArgType instanceType, ArgType typeWithGeneric) { - if (typeWithGeneric == null) { - return null; - } - if (instanceType.isGeneric()) { - List 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 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 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 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() { - } -} diff --git a/jadx-core/src/main/java/jadx/core/utils/Utils.java b/jadx-core/src/main/java/jadx/core/utils/Utils.java index a775df368..daf73e7e8 100644 --- a/jadx-core/src/main/java/jadx/core/utils/Utils.java +++ b/jadx-core/src/main/java/jadx/core/utils/Utils.java @@ -247,6 +247,13 @@ public class Utils { return list.get(list.size() - 1); } + public static T getOrElse(@Nullable T obj, T defaultObj) { + if (obj == null) { + return defaultObj; + } + return obj; + } + public static boolean isEmpty(Collection col) { return col == null || col.isEmpty(); } diff --git a/jadx-core/src/test/java/jadx/core/dex/instructions/args/ArgTypeTest.java b/jadx-core/src/test/java/jadx/core/dex/instructions/args/ArgTypeTest.java index fbed8cad4..8a70a8e2d 100644 --- a/jadx-core/src/test/java/jadx/core/dex/instructions/args/ArgTypeTest.java +++ b/jadx-core/src/test/java/jadx/core/dex/instructions/args/ArgTypeTest.java @@ -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 diff --git a/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java b/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java index f96c01b69..7465b6742 100644 --- a/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java +++ b/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java @@ -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()); } } diff --git a/jadx-core/src/test/java/jadx/core/utils/TypeUtilsTest.java b/jadx-core/src/test/java/jadx/core/utils/TypeUtilsTest.java index c04cce9ab..ce1ae066e 100644 --- a/jadx-core/src/test/java/jadx/core/utils/TypeUtilsTest.java +++ b/jadx-core/src/test/java/jadx/core/utils/TypeUtilsTest.java @@ -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 classGenerics = root.getClassGenerics(ArgType.object("java.util.ArrayList")); + List 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)); diff --git a/jadx-core/src/test/java/jadx/tests/functional/JadxClasspathTest.java b/jadx-core/src/test/java/jadx/tests/functional/JadxClasspathTest.java index e9911980e..8437c6e3f 100644 --- a/jadx-core/src/test/java/jadx/tests/functional/JadxClasspathTest.java +++ b/jadx-core/src/test/java/jadx/tests/functional/JadxClasspathTest.java @@ -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)); } } diff --git a/jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java b/jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java index 116023bea..c724aa9cc 100644 --- a/jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java +++ b/jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java @@ -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 genericsList = new SignatureParser(g).consumeGenericMap(); - List expectedList = new ArrayList<>(); + List genericsList = new SignatureParser(g).consumeGenericTypeParameters(); + List expectedList = new ArrayList<>(); for (int i = 0; i < objs.length; i += 2) { ArgType generic = genericType((String) objs[i]); List list = (List) 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 list = new SignatureParser(" list = new SignatureParser(") null); + call((ArrayList) null); + } + public void call(String str) { c += 1; } public void call(List list) { - c += 2; + c += 10; } public void call(ArrayList 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(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/invoke/TestCastInOverloadedInvoke3.java b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestCastInOverloadedInvoke3.java new file mode 100644 index 000000000..f60353456 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestCastInOverloadedInvoke3.java @@ -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 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);"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/invoke/TestHierarchyOverloadedInvoke.java b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestHierarchyOverloadedInvoke.java new file mode 100644 index 000000000..7569de41a --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestHierarchyOverloadedInvoke.java @@ -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 list) { + c += 10; + } + } + + public static class B extends A { + public void call(ArrayList list) { + c += 100; + } + } + + public void test() { + b.call(new ArrayList<>()); + b.call((List) new ArrayList()); + } + + public void test2(Object obj) { + if (obj instanceof String) { + b.call((String) obj); + } + } + + public void test3() { + b.call((String) null); + b.call((List) null); + b.call((ArrayList) null); + } + + public void test4() { + ((I) b).call(null); + ((A) b).call((String) null); + ((A) b).call((List) 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) new ArrayList());")); + assertThat(code, containsOne("b.call((List) 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<>());")); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/invoke/TestSuperInvokeWithGenerics.java b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestSuperInvokeWithGenerics.java index f7693bc21..dd6f96e58 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/invoke/TestSuperInvokeWithGenerics.java +++ b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestSuperInvokeWithGenerics.java @@ -12,7 +12,7 @@ public class TestSuperInvokeWithGenerics extends IntegrationTest { public static class TestCls { - public class A { + public static class A { public A(T t) { System.out.println("t" + t); } @@ -22,7 +22,7 @@ public class TestSuperInvokeWithGenerics extends IntegrationTest { } } - public class B extends A { + public static class B extends A { public B(String s) { super(s); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestDeboxing2.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestDeboxing2.java index 7bdd21745..91066c011 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestDeboxing2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestDeboxing2.java @@ -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)")); diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestLoopInTry2.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestLoopInTry2.java index 24e789b24..9942167eb 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestLoopInTry2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestLoopInTry2.java @@ -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")); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestStringBuilderElimination2.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestStringBuilderElimination2.java index d478c0309..bab664d8a 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestStringBuilderElimination2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestStringBuilderElimination2.java @@ -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 { diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally2.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally2.java index eceb4f2c5..7a2966302 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally2.java @@ -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) {")); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics2.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics2.java index 6d1fb65f1..d6e529026 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics2.java @@ -32,5 +32,6 @@ public class TestGenerics2 extends SmaliTest { assertThat(code, containsOne("Entry next")); assertThat(code, containsOne("useInt(next.getKey().intValue());")); // no Integer cast + assertThat(code, containsOne("next.getValue().trim();")); // no String cast } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics4.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics4.java index 7ed964d60..35d5dd4fb 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics4.java +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics4.java @@ -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(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver12.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver12.java index 8b0c63db1..078b85985 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver12.java +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver12.java @@ -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();"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver13.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver13.java new file mode 100644 index 000000000..ae40038e5 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver13.java @@ -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, List> map = new HashMap<>(); + + @SuppressWarnings("unchecked") + public List test(Set type) { + List obj = this.map.get(type == null ? CONST : type); + if (obj != null) { + return (List) obj; + } + return null; + } + } + + @NotYetImplemented("additional cast for generic types") + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("public List test(Set type) {") + .containsOne("return (List) obj;"); + } +}