feat: add methods information from standard library, improve generics and varargs restore (#836)

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