From 0ee499c54c9f31ecaa2f2742a135c1c99cc04a70 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 15 Apr 2013 22:55:17 +0400 Subject: [PATCH] Add generic types for classes and fields --- src/main/java/jadx/Consts.java | 2 + src/main/java/jadx/codegen/AnnotationGen.java | 25 +--- src/main/java/jadx/codegen/ClassGen.java | 61 +++++++-- src/main/java/jadx/codegen/MethodGen.java | 5 +- .../jadx/dex/attributes/AttributesList.java | 10 ++ .../attributes/annotations/Annotation.java | 4 + src/main/java/jadx/dex/info/ClassInfo.java | 1 + src/main/java/jadx/dex/info/FieldInfo.java | 3 +- .../jadx/dex/instructions/args/ArgType.java | 106 ++++++++++++-- src/main/java/jadx/dex/nodes/ClassNode.java | 77 +++++++++-- src/main/java/jadx/dex/nodes/FieldNode.java | 34 ++++- src/main/java/jadx/dex/nodes/MethodNode.java | 60 +++++--- .../java/jadx/dex/visitors/ClassModifier.java | 18 ++- src/main/java/jadx/utils/Utils.java | 61 +++++++-- .../java/jadx/samples/TestGenerics.java | 129 ++++++++++++++++++ 15 files changed, 504 insertions(+), 92 deletions(-) diff --git a/src/main/java/jadx/Consts.java b/src/main/java/jadx/Consts.java index 1a6c4b675..4dfe22530 100644 --- a/src/main/java/jadx/Consts.java +++ b/src/main/java/jadx/Consts.java @@ -12,4 +12,6 @@ public class Consts { public static final String CLASS_CLASS = "java.lang.Class"; public static final String CLASS_THROWABLE = "java.lang.Throwable"; public static final String CLASS_ENUM = "java.lang.Enum"; + + public static final String DALVIK_SIGNATURE = "dalvik.annotation.Signature"; } diff --git a/src/main/java/jadx/codegen/AnnotationGen.java b/src/main/java/jadx/codegen/AnnotationGen.java index d7c35d64f..fb998e4d6 100644 --- a/src/main/java/jadx/codegen/AnnotationGen.java +++ b/src/main/java/jadx/codegen/AnnotationGen.java @@ -12,7 +12,6 @@ import jadx.dex.nodes.ClassNode; import jadx.dex.nodes.FieldNode; import jadx.dex.nodes.MethodNode; import jadx.utils.StringUtils; -import jadx.utils.Utils; import jadx.utils.exceptions.JadxRuntimeException; import java.util.Iterator; @@ -62,13 +61,7 @@ public class AnnotationGen { String aCls = a.getAnnotationClass(); if (aCls.startsWith("dalvik.annotation.")) { // skip - if (aCls.equals("dalvik.annotation.Signature")) { - if (!(node instanceof MethodNode)) { - String sign = Utils.mergeSignature((List) a.getValues().get("value")); - List types = ArgType.parseSignatureList(sign); - code.startLine("// signature: " + Utils.listToString(types)); - } - } else if (Consts.DEBUG) { + if (Consts.DEBUG) { code.startLine("// " + a); } } else { @@ -104,13 +97,9 @@ public class AnnotationGen { @SuppressWarnings("unchecked") public void addThrows(MethodNode mth, CodeWriter code) { - AnnotationsList anList = (AnnotationsList) mth.getAttributes().get(AttributeType.ANNOTATION_LIST); - if (anList == null || anList.size() == 0) - return; - - Annotation an = anList.get("dalvik.annotation.Throws"); + Annotation an = mth.getAttributes().getAnnotation("dalvik.annotation.Throws"); if (an != null) { - Object exs = an.getValues().get("value"); + Object exs = an.getDefaultValue(); code.add(" throws "); for (Iterator it = ((List) exs).iterator(); it.hasNext();) { ArgType ex = it.next(); @@ -122,13 +111,9 @@ public class AnnotationGen { } public Object getAnnotationDefaultValue(String name) { - AnnotationsList anList = (AnnotationsList) cls.getAttributes().get(AttributeType.ANNOTATION_LIST); - if (anList == null || anList.size() == 0) - return null; - - Annotation an = anList.get("dalvik.annotation.AnnotationDefault"); + Annotation an = cls.getAttributes().getAnnotation("dalvik.annotation.AnnotationDefault"); if (an != null) { - Annotation defAnnotation = (Annotation) an.getValues().get("value"); + Annotation defAnnotation = (Annotation) an.getDefaultValue(); return defAnnotation.getValues().get(name); } return null; diff --git a/src/main/java/jadx/codegen/ClassGen.java b/src/main/java/jadx/codegen/ClassGen.java index 233630c46..3496d1cc1 100644 --- a/src/main/java/jadx/codegen/ClassGen.java +++ b/src/main/java/jadx/codegen/ClassGen.java @@ -22,6 +22,8 @@ import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import com.android.dx.rop.code.AccessFlags; @@ -105,19 +107,22 @@ public class ClassGen { clsCode.add("class "); } clsCode.add(cls.getShortName()); - ClassInfo sup = cls.getSuperClass(); + makeGenericMap(clsCode, cls.getGenericMap()); + clsCode.add(' '); + + ClassInfo sup = cls.getSuperClass(); if (sup != null && !sup.getFullName().equals(Consts.CLASS_OBJECT) && !sup.getFullName().equals(Consts.CLASS_ENUM)) { - clsCode.add(" extends ").add(useClass(sup)); + clsCode.add("extends ").add(useClass(sup)).add(' '); } if (cls.getInterfaces().size() > 0 && !af.isAnnotation()) { if (cls.getAccessFlags().isInterface()) - clsCode.add(" extends "); + clsCode.add("extends "); else - clsCode.add(" implements "); + clsCode.add("implements "); for (Iterator it = cls.getInterfaces().iterator(); it.hasNext();) { ClassInfo interf = it.next(); @@ -125,11 +130,41 @@ public class ClassGen { if (it.hasNext()) clsCode.add(", "); } + if (!cls.getInterfaces().isEmpty()) + clsCode.add(' '); } } + public void makeGenericMap(CodeWriter code, Map> gmap) { + if (gmap == null || gmap.isEmpty()) + return; + + code.add('<'); + int i = 0; + for (Entry> e : gmap.entrySet()) { + ArgType type = e.getKey(); + List list = e.getValue(); + if (i != 0) { + code.add(", "); + } + code.add(useClass(type)); + if (list != null && !list.isEmpty()) { + code.add(" extends "); + for (Iterator it = list.iterator(); it.hasNext();) { + ArgType g = it.next(); + code.add(useClass(g)); + if (it.hasNext()) { + code.add(" & "); + } + } + } + i++; + } + code.add('>'); + } + public void makeClassBody(CodeWriter clsCode) throws CodegenException { - clsCode.add(" {"); + clsCode.add("{"); CodeWriter mthsCode = makeMethods(clsCode, cls.getMethods()); clsCode.add(makeFields(clsCode, cls, cls.getFields())); @@ -248,9 +283,15 @@ public class ClassGen { } public String useClass(ArgType clsType) { - String baseClass = useClass(ClassInfo.fromType(cls.dex(), clsType)); + if (clsType.isGenericType()) { + return clsType.getObject(); + } + return useClass(ClassInfo.fromType(cls.dex(), clsType)); + } - ArgType[] generics = clsType.getGenericTypes(); + public String useClass(ClassInfo classInfo) { + String baseClass = useClassInner(classInfo); + ArgType[] generics = classInfo.getType().getGenericTypes(); if (generics != null) { StringBuilder sb = new StringBuilder(); sb.append(baseClass); @@ -262,7 +303,7 @@ public class ClassGen { } ArgType gt = generics[i]; if (gt.isTypeKnown()) - sb.append(useClass(gt)); + sb.append(TypeGen.translate(this, gt)); else sb.append('?'); } @@ -273,9 +314,9 @@ public class ClassGen { } } - public String useClass(ClassInfo classInfo) { + private String useClassInner(ClassInfo classInfo) { if (parentGen != null) - return parentGen.useClass(classInfo); + return parentGen.useClassInner(classInfo); String clsStr = classInfo.getFullName(); if (fallback) diff --git a/src/main/java/jadx/codegen/MethodGen.java b/src/main/java/jadx/codegen/MethodGen.java index c694f789c..d4ea5f0b4 100644 --- a/src/main/java/jadx/codegen/MethodGen.java +++ b/src/main/java/jadx/codegen/MethodGen.java @@ -76,8 +76,11 @@ public class MethodGen { if (mth.getParentClass().getAccessFlags().isInterface()) { ai = ai.remove(AccessFlags.ACC_ABSTRACT); } - code.startLine(ai.makeString()); + + classGen.makeGenericMap(code, mth.getGenericMap()); + code.add(' '); + if (mth.getAccessFlags().isConstructor()) { code.add(classGen.getClassNode().getShortName()); // constructor } else { diff --git a/src/main/java/jadx/dex/attributes/AttributesList.java b/src/main/java/jadx/dex/attributes/AttributesList.java index dcb6cdcfc..a505d844e 100644 --- a/src/main/java/jadx/dex/attributes/AttributesList.java +++ b/src/main/java/jadx/dex/attributes/AttributesList.java @@ -1,5 +1,7 @@ package jadx.dex.attributes; +import jadx.dex.attributes.annotations.Annotation; +import jadx.dex.attributes.annotations.AnnotationsList; import jadx.utils.Utils; import java.util.ArrayList; @@ -90,6 +92,14 @@ public class AttributesList { } } + public Annotation getAnnotation(String cls) { + AnnotationsList aList = (AnnotationsList) get(AttributeType.ANNOTATION_LIST); + if (aList == null || aList.size() == 0) + return null; + + return aList.get(cls); + } + public List getAll(AttributeType type) { assert type.notUniq(); diff --git a/src/main/java/jadx/dex/attributes/annotations/Annotation.java b/src/main/java/jadx/dex/attributes/annotations/Annotation.java index 69a9ebdfd..68bd291cb 100644 --- a/src/main/java/jadx/dex/attributes/annotations/Annotation.java +++ b/src/main/java/jadx/dex/attributes/annotations/Annotation.java @@ -36,6 +36,10 @@ public class Annotation { return values; } + public Object getDefaultValue() { + return values.get("value"); + } + @Override public String toString() { return "Annotation[" + visibility + ", " + atype + ", " + values + "]"; diff --git a/src/main/java/jadx/dex/info/ClassInfo.java b/src/main/java/jadx/dex/info/ClassInfo.java index d893b093a..044b071a7 100644 --- a/src/main/java/jadx/dex/info/ClassInfo.java +++ b/src/main/java/jadx/dex/info/ClassInfo.java @@ -49,6 +49,7 @@ public final class ClassInfo { } private ClassInfo(DexNode dex, ArgType type) { + assert type.isObject() : "Not class type: " + type; this.type = type; String fullObjectName = type.getObject(); diff --git a/src/main/java/jadx/dex/info/FieldInfo.java b/src/main/java/jadx/dex/info/FieldInfo.java index 6164de28b..923853bdf 100644 --- a/src/main/java/jadx/dex/info/FieldInfo.java +++ b/src/main/java/jadx/dex/info/FieldInfo.java @@ -1,12 +1,11 @@ package jadx.dex.info; -import jadx.dex.attributes.AttrNode; import jadx.dex.instructions.args.ArgType; import jadx.dex.nodes.DexNode; import com.android.dx.io.FieldId; -public class FieldInfo extends AttrNode { +public class FieldInfo { private final String name; private final ArgType type; diff --git a/src/main/java/jadx/dex/instructions/args/ArgType.java b/src/main/java/jadx/dex/instructions/args/ArgType.java index 55b09c9d7..bd7273924 100644 --- a/src/main/java/jadx/dex/instructions/args/ArgType.java +++ b/src/main/java/jadx/dex/instructions/args/ArgType.java @@ -5,9 +5,16 @@ import jadx.utils.Utils; import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class ArgType { + private static final Logger LOG = LoggerFactory.getLogger(ArgType.class); + public static final ArgType INT = primitive(PrimitiveType.INT); public static final ArgType BOOLEAN = primitive(PrimitiveType.BOOLEAN); public static final ArgType BYTE = primitive(PrimitiveType.BYTE); @@ -43,6 +50,10 @@ public abstract class ArgType { return new ObjectArg(obj); } + public static ArgType genericType(String type) { + return new GenericTypeArg(type); + } + public static ArgType generic(String sign) { return parseSignature(sign); } @@ -119,6 +130,17 @@ public abstract class ArgType { } } + private static final class GenericTypeArg extends ObjectArg { + public GenericTypeArg(String obj) { + super(obj); + } + + @Override + public boolean isGenericType() { + return true; + } + } + private static final class GenericObjectArg extends ObjectArg { private final ArgType[] generics; @@ -249,6 +271,10 @@ public abstract class ArgType { return false; } + public boolean isGenericType() { + return false; + } + public ArgType[] getGenericTypes() { return null; } @@ -355,14 +381,16 @@ public abstract class ArgType { } public static ArgType parse(String type) { - assert type.length() > 0 : "Empty type"; char f = type.charAt(0); - if (f == 'L') { - return object(type); - } else if (f == '[') { - return array(parse(type.substring(1))); - } else { - return parse(f); + switch (f) { + case 'L': + return object(type); + case 'T': + return genericType(type.substring(1, type.length() - 1)); + case '[': + return array(parse(type.substring(1))); + default: + return parse(f); } } @@ -374,11 +402,22 @@ public abstract class ArgType { String obj = sign.substring(0, b); String genericsStr = sign.substring(b + 1, sign.length() - 2); List generics = parseSignatureList(genericsStr); - ArgType res = generic(obj + ";", generics.toArray(new ArgType[generics.size()])); - return res; + if (generics != null) + return generic(obj + ";", generics.toArray(new ArgType[generics.size()])); + else + return object(obj + ";"); } public static List parseSignatureList(String str) { + try { + return parseSignatureListInner(str, true); + } catch (Throwable e) { + LOG.warn("Signature parse exception: {}", str, e); + return null; + } + } + + private static List parseSignatureListInner(String str, boolean parsePrimitives) { List signs = new ArrayList(3); if (str.equals("*")) { signs.add(UNKNOWN); @@ -396,6 +435,7 @@ public abstract class ArgType { char c = str.charAt(pos); switch (c) { case 'L': + case 'T': if (obj == 0 && gen == 0) { obj++; objStart = pos; @@ -410,6 +450,15 @@ public abstract class ArgType { } break; + case ':': // generic types map separator + if (gen == 0) { + obj = 0; + String o = str.substring(objStart, pos); + if (o.length() > 0) + type = genericType(o); + } + break; + case '<': gen++; break; @@ -418,11 +467,13 @@ public abstract class ArgType { break; case '[': - arr++; + if (obj == 0 && gen == 0) { + arr++; + } break; default: - if (obj == 0 && gen == 0) { + if (parsePrimitives && obj == 0 && gen == 0) { type = parse(c); } break; @@ -439,12 +490,45 @@ public abstract class ArgType { arr = 0; } type = null; + objStart = pos + 1; } pos++; } return signs; } + public static Map> parseGenericMap(String gen) { + try { + Map> genericMap = null; + List genTypes = parseSignatureListInner(gen, false); + if (genTypes != null) { + genericMap = new LinkedHashMap>(2); + ArgType prev = null; + List genList = new ArrayList(2); + for (ArgType arg : genTypes) { + if (arg.isGenericType()) { + if (prev != null) { + genericMap.put(prev, genList); + genList = new ArrayList(); + } + prev = arg; + } else { + if (!arg.getObject().equals(Consts.CLASS_OBJECT)) + genList.add(arg); + } + } + if (prev != null) { + genericMap.put(prev, genList); + } + // LOG.debug("sign: {} -> {}", gen, genericMap); + } + return genericMap; + } catch (Throwable e) { + LOG.warn("Generic map parse exception: {}", gen, e); + return null; + } + } + private static ArgType parse(char f) { switch (f) { case 'Z': diff --git a/src/main/java/jadx/dex/nodes/ClassNode.java b/src/main/java/jadx/dex/nodes/ClassNode.java index 19e352d60..636cb12e2 100644 --- a/src/main/java/jadx/dex/nodes/ClassNode.java +++ b/src/main/java/jadx/dex/nodes/ClassNode.java @@ -1,10 +1,9 @@ package jadx.dex.nodes; +import jadx.Consts; import jadx.dex.attributes.AttrNode; import jadx.dex.attributes.AttributeType; -import jadx.dex.attributes.IAttribute; import jadx.dex.attributes.annotations.Annotation; -import jadx.dex.attributes.annotations.AnnotationsList; import jadx.dex.info.AccessInfo; import jadx.dex.info.AccessInfo.AFType; import jadx.dex.info.ClassInfo; @@ -14,6 +13,7 @@ import jadx.dex.instructions.args.ArgType; import jadx.dex.nodes.parser.AnnotationsParser; import jadx.dex.nodes.parser.FieldValueAttr; import jadx.dex.nodes.parser.StaticValuesParser; +import jadx.utils.Utils; import jadx.utils.exceptions.DecodeException; import java.util.ArrayList; @@ -31,13 +31,13 @@ import com.android.dx.io.ClassData.Method; import com.android.dx.io.ClassDef; public class ClassNode extends AttrNode implements ILoadable { - private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); private final DexNode dex; private final ClassInfo clsInfo; - private final ClassInfo superClass; - private final List interfaces; + private ClassInfo superClass; + private List interfaces; + private Map> genericMap; private final List methods = new ArrayList(); private final List fields = new ArrayList(); @@ -82,15 +82,15 @@ public class ClassNode extends AttrNode implements ILoadable { loadAnnotations(cls); - int accFlagsValue = cls.getAccessFlags(); + parseClassSignature(); + setFieldsTypesFromSignature(); - IAttribute annotations = getAttributes().get(AttributeType.ANNOTATION_LIST); - if (annotations != null) { - AnnotationsList list = (AnnotationsList) annotations; - Annotation iCls = list.get("dalvik.annotation.InnerClass"); - if (iCls != null) - accFlagsValue = (Integer) iCls.getValues().get("accessFlags"); - } + int accFlagsValue; + Annotation a = getAttributes().getAnnotation("dalvik.annotation.InnerClass"); + if (a != null) + accFlagsValue = (Integer) a.getValues().get("accessFlags"); + else + accFlagsValue = cls.getAccessFlags(); this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS); @@ -134,6 +134,53 @@ public class ClassNode extends AttrNode implements ILoadable { } } + @SuppressWarnings("unchecked") + private void parseClassSignature() { + Annotation a = this.getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE); + if (a == null) + return; + + String sign = Utils.mergeSignature((List) a.getDefaultValue()); + // parse generic map + int end = Utils.getGenericEnd(sign); + if (end != -1) { + String gen = sign.substring(1, end); + genericMap = ArgType.parseGenericMap(gen); + sign = sign.substring(end + 1); + } + + // parse super class signature and interfaces + List list = ArgType.parseSignatureList(sign); + if (list != null && !list.isEmpty()) { + try { + ArgType st = list.remove(0); + this.superClass = ClassInfo.fromType(dex, st); + int i = 0; + for (ArgType it : list) { + ClassInfo interf = ClassInfo.fromType(dex, it); + interfaces.set(i, interf); + i++; + } + } catch (Throwable e) { + LOG.warn("Can't set signatures for class: {}, sign: {}", this, sign, e); + } + } + } + + @SuppressWarnings("unchecked") + private void setFieldsTypesFromSignature() { + for (FieldNode field : fields) { + Annotation a = field.getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE); + if (a == null) + continue; + + String sign = Utils.mergeSignature((List) a.getDefaultValue()); + ArgType gType = ArgType.parseSignature(sign); + if (gType != null) + field.setType(gType); + } + } + @Override public void load() throws DecodeException { for (MethodNode mth : getMethods()) { @@ -162,6 +209,10 @@ public class ClassNode extends AttrNode implements ILoadable { return interfaces; } + public Map> getGenericMap() { + return genericMap; + } + public List getMethods() { return methods; } diff --git a/src/main/java/jadx/dex/nodes/FieldNode.java b/src/main/java/jadx/dex/nodes/FieldNode.java index a7e912a7a..28b367a7e 100644 --- a/src/main/java/jadx/dex/nodes/FieldNode.java +++ b/src/main/java/jadx/dex/nodes/FieldNode.java @@ -1,17 +1,27 @@ package jadx.dex.nodes; +import jadx.dex.attributes.AttrNode; import jadx.dex.info.AccessInfo; import jadx.dex.info.AccessInfo.AFType; +import jadx.dex.info.ClassInfo; import jadx.dex.info.FieldInfo; +import jadx.dex.instructions.args.ArgType; import com.android.dx.io.ClassData.Field; -public class FieldNode extends FieldInfo { +public class FieldNode extends AttrNode { private final AccessInfo accFlags; + private final String name; + private final ClassInfo declClass; + + private ArgType type; public FieldNode(ClassNode cls, Field field) { - super(cls.dex(), field.getFieldIndex()); + FieldInfo f = FieldInfo.fromDex(cls.dex(), field.getFieldIndex()); + this.name = f.getName(); + this.type = f.getType(); + this.declClass = f.getDeclClass(); this.accFlags = new AccessInfo(field.getAccessFlags(), AFType.FIELD); } @@ -19,4 +29,24 @@ public class FieldNode extends FieldInfo { return accFlags; } + public String getName() { + return name; + } + + public ArgType getType() { + return type; + } + + public void setType(ArgType type) { + this.type = type; + } + + public ClassInfo getDeclClass() { + return declClass; + } + + @Override + public String toString() { + return declClass + "." + name + " " + type; + } } diff --git a/src/main/java/jadx/dex/nodes/MethodNode.java b/src/main/java/jadx/dex/nodes/MethodNode.java index 56e1f406d..283fe812e 100644 --- a/src/main/java/jadx/dex/nodes/MethodNode.java +++ b/src/main/java/jadx/dex/nodes/MethodNode.java @@ -1,11 +1,10 @@ package jadx.dex.nodes; +import jadx.Consts; import jadx.dex.attributes.AttrNode; import jadx.dex.attributes.AttributeFlag; -import jadx.dex.attributes.AttributeType; import jadx.dex.attributes.JumpAttribute; import jadx.dex.attributes.annotations.Annotation; -import jadx.dex.attributes.annotations.AnnotationsList; import jadx.dex.info.AccessInfo; import jadx.dex.info.AccessInfo.AFType; import jadx.dex.info.ClassInfo; @@ -29,6 +28,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.slf4j.Logger; @@ -54,6 +54,7 @@ public class MethodNode extends AttrNode implements ILoadable { private ArgType retType; private RegisterArg thisArg; private List argsList; + private Map> genericMap; private List blocks; private BlockNode enterBlock; @@ -131,35 +132,52 @@ public class MethodNode extends AttrNode implements ILoadable { @SuppressWarnings("unchecked") private boolean parseSignature() { - AnnotationsList aList = (AnnotationsList) getAttributes().get(AttributeType.ANNOTATION_LIST); - if (aList == null || aList.size() == 0) - return false; - - Annotation a = aList.get("dalvik.annotation.Signature"); + Annotation a = getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE); if (a == null) return false; - String sign = Utils.mergeSignature((List) a.getValues().get("value")); - int lastBracket = sign.indexOf(')'); - String argsTypesStr = sign.substring(1, lastBracket); + String sign = Utils.mergeSignature((List) a.getDefaultValue()); + + // parse generic map + int end = Utils.getGenericEnd(sign); + if (end != -1) { + String gen = sign.substring(1, end); + genericMap = ArgType.parseGenericMap(gen); + sign = sign.substring(end + 1); + } + + int firstBracket = sign.indexOf('('); + int lastBracket = sign.lastIndexOf(')'); + String argsTypesStr = sign.substring(firstBracket + 1, lastBracket); String returnType = sign.substring(lastBracket + 1); retType = ArgType.parseSignature(returnType); + if (retType == null) { + LOG.warn("Signature parse error: {}", returnType); + return false; + } if (mthInfo.getArgumentsTypes().isEmpty()) { argsList = Collections.emptyList(); return true; } List argsTypes = ArgType.parseSignatureList(argsTypesStr); - if (argsTypes.size() != mthInfo.getArgumentsTypes().size()) { - if (!getParentClass().getAccessFlags().isEnum() && !mthInfo.isConstructor()) { - // error parsing signature - LOG.error("Wrong parse result: " + sign + " -> " + argsTypes - + " must be: " + mthInfo.getArgumentsTypes() - // + " in method " + this - ); - } + if (argsTypes == null) return false; + + if (argsTypes.size() != mthInfo.getArgumentsTypes().size()) { + if (!mthInfo.isConstructor()) { + LOG.warn("Wrong signature parse result: " + sign + " -> " + argsTypes + + ", not generic version: " + mthInfo.getArgumentsTypes()); + return false; + } else if (getParentClass().getAccessFlags().isEnum()) { + // TODO: + argsTypes.add(0, mthInfo.getArgumentsTypes().get(1)); + argsTypes.add(1, mthInfo.getArgumentsTypes().get(1)); + } else { + // add synthetic arg for outer class + argsTypes.add(0, mthInfo.getArgumentsTypes().get(0)); + } } initArguments(argsTypes); return true; @@ -213,7 +231,11 @@ public class MethodNode extends AttrNode implements ILoadable { return retType; } - // move to external class + public Map> getGenericMap() { + return genericMap; + } + + // TODO: move to external class private void initTryCatches(Code mthCode, InsnNode[] insnByOffset) { CatchHandler[] catchBlocks = mthCode.getCatchHandlers(); Try[] tries = mthCode.getTries(); diff --git a/src/main/java/jadx/dex/visitors/ClassModifier.java b/src/main/java/jadx/dex/visitors/ClassModifier.java index 198fba931..a438f55f0 100644 --- a/src/main/java/jadx/dex/visitors/ClassModifier.java +++ b/src/main/java/jadx/dex/visitors/ClassModifier.java @@ -1,6 +1,7 @@ package jadx.dex.visitors; import jadx.dex.info.AccessInfo; +import jadx.dex.info.MethodInfo; import jadx.dex.nodes.BlockNode; import jadx.dex.nodes.ClassNode; import jadx.dex.nodes.MethodNode; @@ -23,7 +24,7 @@ public class ClassModifier extends AbstractVisitor { // remove bridge methods if (af.isBridge() && af.isSynthetic()) { - if (!isMethodIdUniq(cls, mth)) { + if (!isMethodUniq(cls, mth)) { // TODO add more checks before method deletion it.remove(); } @@ -42,12 +43,17 @@ public class ClassModifier extends AbstractVisitor { return false; } - private boolean isMethodIdUniq(ClassNode cls, MethodNode mth) { - String shortId = mth.getMethodInfo().getShortId(); + private boolean isMethodUniq(ClassNode cls, MethodNode mth) { + MethodInfo mi = mth.getMethodInfo(); for (MethodNode otherMth : cls.getMethods()) { - if (otherMth.getMethodInfo().getShortId().equals(shortId) - && otherMth != mth) - return false; + MethodInfo omi = otherMth.getMethodInfo(); + if (omi.getName().equals(mi.getName()) + && otherMth != mth) { + if (omi.getArgumentsTypes().size() == mi.getArgumentsTypes().size()) { + // TODO: check to args objects types + return false; + } + } } return true; } diff --git a/src/main/java/jadx/utils/Utils.java b/src/main/java/jadx/utils/Utils.java index c6099d834..ee822a74a 100644 --- a/src/main/java/jadx/utils/Utils.java +++ b/src/main/java/jadx/utils/Utils.java @@ -28,13 +28,35 @@ public class Utils { } public static String escape(String str) { - return str.replace('.', '_') - .replace('/', '_') - .replace(';', '_') - .replace('$', '_') - .replace('<', '_') - .replace('>', '_') - .replace("[]", "_A"); + int len = str.length(); + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) { + char c = str.charAt(i); + switch (c) { + case '.': + case '/': + case ';': + case '$': + case '<': + case '[': + sb.append('_'); + break; + + case ']': + sb.append('A'); + break; + + case '>': + case ',': + case ' ': + break; + + default: + sb.append(c); + break; + } + } + return sb.toString(); } public static String listToString(Iterable list) { @@ -44,7 +66,7 @@ public class Utils { StringBuilder str = new StringBuilder(); for (Iterator it = list.iterator(); it.hasNext();) { Object o = it.next(); - str.append(o.toString()); + str.append(o); if (it.hasNext()) str.append(", "); } @@ -52,6 +74,9 @@ public class Utils { } public static String arrayToString(Object[] array) { + if (array == null) + return ""; + StringBuilder sb = new StringBuilder(); for (int i = 0; i < array.length; i++) { if (i != 0) @@ -76,6 +101,26 @@ public class Utils { return sb.toString(); } + public static int getGenericEnd(String sign) { + int end = -1; + if (sign.startsWith("<")) { + int pair = 1; + for (int pos = 1; pos < sign.length(); pos++) { + char c = sign.charAt(pos); + if (c == '<') + pair++; + else if (c == '>') + pair--; + + if (pair == 0) { + end = pos; + break; + } + } + } + return end; + } + public static String getJadxVersion() { try { Enumeration resources = diff --git a/src/samples/java/jadx/samples/TestGenerics.java b/src/samples/java/jadx/samples/TestGenerics.java index 4cd3c9250..a65785e58 100644 --- a/src/samples/java/jadx/samples/TestGenerics.java +++ b/src/samples/java/jadx/samples/TestGenerics.java @@ -7,6 +7,134 @@ import java.util.Map; public class TestGenerics extends AbstractTest { + public List strings; + + public static class GenericClass implements Comparable { + @Override + public int compareTo(String o) { + return 0; + } + } + + public static class Box { + private T t; + + public void set(T t) { + this.t = t; + } + + public T get() { + return t; + } + } + + public static Box integerBox = new Box(); + + public interface Pair { + public K getKey(); + + public LongGenericType getValue(); + } + + public static class OrderedPair implements Pair { + private final K key; + private final V value; + + public OrderedPair(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + } + + Pair p1 = new OrderedPair("8", 8); + OrderedPair> p = new OrderedPair>("primes", new Box()); + + public static class Util { + // Generic static method + public static boolean compare(Pair p1, Pair p2) { + return p1.getKey().equals(p2.getKey()) && + p1.getValue().equals(p2.getValue()); + } + } + + public static boolean use() { + Pair p1 = new OrderedPair(1, "str1"); + Pair p2 = new OrderedPair(2, "str2"); + boolean same = Util. compare(p1, p2); + return same; + } + + public class NaturalNumber { + private final T n; + + public NaturalNumber(T n) { + this.n = n; + } + + public boolean isEven() { + return n.intValue() % 2 == 0; + } + } + + class A { + } + + interface B { + } + + interface C { + } + + class D { + } + + public static > int countGreaterThan(T[] anArray, T elem) { + int count = 0; + for (T e : anArray) + if (e.compareTo(elem) > 0) + ++count; + return count; + } + + public static void process(List list) { + } + + public static void printList(List list) { + for (Object elem : list) + System.out.print(elem + " "); + System.out.println(); + } + + public static void addNumbers(List list) { + for (int i = 1; i <= 10; i++) { + list.add(i); + } + } + + public class Node> { + private final T data; + private final Node next; + + public Node(T data, Node next) { + this.data = data; + this.next = next; + } + + public T getData() { + return data; + } + } + private List test1(Map map) { List list = new ArrayList(); String str = map.get("key"); @@ -26,6 +154,7 @@ public class TestGenerics extends AbstractTest { @Override public boolean testRun() throws Exception { assertTrue(test1(new HashMap()) != null); + // TODO: add other checks return true; }