diff --git a/src/main/java/jadx/codegen/AnnotationGen.java b/src/main/java/jadx/codegen/AnnotationGen.java index 3bb8d1eb8..d7c35d64f 100644 --- a/src/main/java/jadx/codegen/AnnotationGen.java +++ b/src/main/java/jadx/codegen/AnnotationGen.java @@ -62,11 +62,15 @@ public class AnnotationGen { String aCls = a.getAnnotationClass(); if (aCls.startsWith("dalvik.annotation.")) { // skip - if (aCls.equals("dalvik.annotation.Signature")) - code.startLine("// signature: " - + Utils.mergeSignature((List) a.getValues().get("value"))); - else if (Consts.DEBUG) + 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) { code.startLine("// " + a); + } } else { code.startLine(); code.add(formatAnnotation(a)); diff --git a/src/main/java/jadx/codegen/ClassGen.java b/src/main/java/jadx/codegen/ClassGen.java index 307152ce7..233630c46 100644 --- a/src/main/java/jadx/codegen/ClassGen.java +++ b/src/main/java/jadx/codegen/ClassGen.java @@ -248,7 +248,29 @@ public class ClassGen { } public String useClass(ArgType clsType) { - return useClass(ClassInfo.fromType(cls.dex(), clsType)); + String baseClass = useClass(ClassInfo.fromType(cls.dex(), clsType)); + + ArgType[] generics = clsType.getGenericTypes(); + if (generics != null) { + StringBuilder sb = new StringBuilder(); + sb.append(baseClass); + sb.append("<"); + int len = generics.length; + for (int i = 0; i < len; i++) { + if (i != 0) { + sb.append(", "); + } + ArgType gt = generics[i]; + if (gt.isTypeKnown()) + sb.append(useClass(gt)); + else + sb.append('?'); + } + sb.append(">"); + return sb.toString(); + } else { + return baseClass; + } } public String useClass(ClassInfo classInfo) { diff --git a/src/main/java/jadx/codegen/InsnGen.java b/src/main/java/jadx/codegen/InsnGen.java index 092b9c860..d3f7339a4 100644 --- a/src/main/java/jadx/codegen/InsnGen.java +++ b/src/main/java/jadx/codegen/InsnGen.java @@ -94,19 +94,7 @@ public class InsnGen { } public String declareVar(RegisterArg arg) throws CodegenException { - String type = useType(arg.getType()); - ArgType[] generics = arg.getType().getGenericTypes(); - if (generics != null) { - StringBuilder sb = new StringBuilder(); - sb.append(type); - sb.append("/*<"); - for (ArgType gt : generics) { - sb.append(useType(gt)); - } - sb.append(">*/"); - type = sb.toString(); - } - return type + " " + arg(arg); + return useType(arg.getType()) + " " + arg(arg); } private String lit(LiteralArg arg) { @@ -144,13 +132,6 @@ public class InsnGen { } private String useType(ArgType type) { - if (type.isObject()) - return mgen.getClassGen().useClass(type); - else - return translate(type); - } - - private String translate(ArgType type) { return TypeGen.translate(mgen.getClassGen(), type); } @@ -160,7 +141,6 @@ public class InsnGen { private void makeInsn(InsnNode insn, CodeWriter code, boolean bodyOnly) throws CodegenException { try { - // code.startLine("/* " + insn + "*/"); EnumSet state = EnumSet.noneOf(InsnGenState.class); if (bodyOnly) { state.add(InsnGenState.BODY_ONLY); @@ -213,7 +193,7 @@ public class InsnGen { case CHECK_CAST: case CAST: code.add("(("); - code.add(translate(((ArgType) ((IndexInsnNode) insn).getIndex()))); + code.add(useType(((ArgType) ((IndexInsnNode) insn).getIndex()))); code.add(") ("); code.add(arg(insn.getArg(0))); code.add("))"); @@ -445,7 +425,7 @@ public class InsnGen { } int len = str.length(); str.delete(len - 2, len); - code.add("new ").add(translate(elType)).add("[] { ").add(str.toString()).add(" }"); + code.add("new ").add(useType(elType)).add("[] { ").add(str.toString()).add(" }"); } private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet state) diff --git a/src/main/java/jadx/codegen/MethodGen.java b/src/main/java/jadx/codegen/MethodGen.java index c26c92b01..c694f789c 100644 --- a/src/main/java/jadx/codegen/MethodGen.java +++ b/src/main/java/jadx/codegen/MethodGen.java @@ -81,13 +81,12 @@ public class MethodGen { if (mth.getAccessFlags().isConstructor()) { code.add(classGen.getClassNode().getShortName()); // constructor } else { - code.add(TypeGen.translate(classGen, mth.getMethodInfo().getReturnType())); - code.add(" "); + code.add(TypeGen.translate(classGen, mth.getReturnType())); + code.add(' '); code.add(mth.getName()); } - code.add("("); + code.add('('); - mth.resetArgsTypes(); List args = mth.getArguments(false); if (mth.getMethodInfo().isConstructor() && mth.getParentClass().getAttributes().contains(AttributeType.ENUM_CLASS)) { diff --git a/src/main/java/jadx/dex/info/LocalVarInfo.java b/src/main/java/jadx/dex/info/LocalVarInfo.java index 412296fa1..95da2fc43 100644 --- a/src/main/java/jadx/dex/info/LocalVarInfo.java +++ b/src/main/java/jadx/dex/info/LocalVarInfo.java @@ -28,7 +28,7 @@ public class LocalVarInfo extends RegisterArg { private void init(String name, ArgType type, String sign) { if (sign != null) { - type = ArgType.generic(type.getObject(), sign); + type = ArgType.generic(sign); } TypedVar tv = new TypedVar(type); tv.setName(name); diff --git a/src/main/java/jadx/dex/instructions/InsnDecoder.java b/src/main/java/jadx/dex/instructions/InsnDecoder.java index cfd557d90..7fd8f171b 100644 --- a/src/main/java/jadx/dex/instructions/InsnDecoder.java +++ b/src/main/java/jadx/dex/instructions/InsnDecoder.java @@ -401,7 +401,7 @@ public class InsnDecoder { case Opcodes.RETURN_OBJECT: return insn(InsnType.RETURN, null, - InsnArg.reg(insn, 0, method.getMethodInfo().getReturnType())); + InsnArg.reg(insn, 0, method.getReturnType())); case Opcodes.INSTANCE_OF: { InsnNode node = new IndexInsnNode(method, InsnType.INSTANCE_OF, dex.getType(insn.getIndex()), 1); diff --git a/src/main/java/jadx/dex/instructions/args/ArgType.java b/src/main/java/jadx/dex/instructions/args/ArgType.java index 77e393077..55b09c9d7 100644 --- a/src/main/java/jadx/dex/instructions/args/ArgType.java +++ b/src/main/java/jadx/dex/instructions/args/ArgType.java @@ -24,6 +24,7 @@ public abstract class ArgType { public static final ArgType THROWABLE = object(Consts.CLASS_THROWABLE); public static final ArgType UNKNOWN = unknown(PrimitiveType.values()); + public static final ArgType UNKNOWN_OBJECT = unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY); public static final ArgType NARROW = unknown( PrimitiveType.INT, PrimitiveType.FLOAT, @@ -32,8 +33,6 @@ public abstract class ArgType { public static final ArgType WIDE = unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE); - public static final ArgType UNKNOWN_OBJECT = unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY); - protected int hash; private static ArgType primitive(PrimitiveType stype) { @@ -41,11 +40,15 @@ public abstract class ArgType { } public static ArgType object(String obj) { - return new ObjectArg(Utils.cleanObjectName(obj)); + return new ObjectArg(obj); } - public static ArgType generic(String obj, String signature) { - return new GenericObjectArg(obj, signature); + public static ArgType generic(String sign) { + return parseSignature(sign); + } + + public static ArgType generic(String obj, ArgType[] generics) { + return new GenericObjectArg(obj, generics); } public static ArgType array(ArgType vtype) { @@ -91,7 +94,7 @@ public abstract class ArgType { private final String object; public ObjectArg(String obj) { - this.object = obj; + this.object = Utils.cleanObjectName(obj); this.hash = obj.hashCode(); } @@ -119,9 +122,9 @@ public abstract class ArgType { private static final class GenericObjectArg extends ObjectArg { private final ArgType[] generics; - public GenericObjectArg(String obj, String signature) { + public GenericObjectArg(String obj, ArgType[] generics) { super(obj); - this.generics = parseSignature(signature); + this.generics = generics; this.hash = obj.hashCode() + 31 * Arrays.hashCode(generics); } @@ -132,7 +135,7 @@ public abstract class ArgType { @Override public String toString() { - return super.toString() + "<" + Arrays.toString(generics) + ">"; + return super.toString() + "<" + Utils.arrayToString(generics) + ">"; } } @@ -177,7 +180,7 @@ public abstract class ArgType { @Override public String toString() { - return arrayElement.toString(); + return arrayElement.toString() + "[]"; } } @@ -220,7 +223,7 @@ public abstract class ArgType { @Override public String toString() { if (possibleTypes.length == PrimitiveType.values().length) - return "*"; + return "?"; else return "?" + Arrays.toString(possibleTypes); } @@ -363,30 +366,83 @@ public abstract class ArgType { } } - public static ArgType[] parseSignature(String signature) { - int b = signature.indexOf('<') + 1; - int e = signature.lastIndexOf('>'); - String gens = signature.substring(b, e); - String[] split = gens.split(";"); - ArgType[] result = new ArgType[split.length]; - for (int i = 0; i < split.length; i++) { - String g = split[i]; - switch (g.charAt(0)) { + public static ArgType parseSignature(String sign) { + int b = sign.indexOf('<'); + if (b == -1) + return parse(sign); + + 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; + } + + public static List parseSignatureList(String str) { + List signs = new ArrayList(3); + if (str.equals("*")) { + signs.add(UNKNOWN); + return signs; + } + + int obj = 0; + int objStart = 0; + int gen = 0; + int arr = 0; + + int pos = 0; + ArgType type = null; + while (pos < str.length()) { + char c = str.charAt(pos); + switch (c) { case 'L': - result[i] = object(g + ";"); + if (obj == 0 && gen == 0) { + obj++; + objStart = pos; + } break; - case '*': - case '?': - result[i] = UNKNOWN; + case ';': + if (obj == 1 && gen == 0) { + obj--; + String o = str.substring(objStart, pos + 1); + type = parseSignature(o); + } + break; + + case '<': + gen++; + break; + case '>': + gen--; + break; + + case '[': + arr++; break; default: - result[i] = UNKNOWN_OBJECT; + if (obj == 0 && gen == 0) { + type = parse(c); + } break; } + + if (type != null) { + if (arr == 0) { + signs.add(type); + } else { + for (int i = 0; i < arr; i++) { + type = array(type); + } + signs.add(type); + arr = 0; + } + type = null; + } + pos++; } - return result; + return signs; } private static ArgType parse(char f) { @@ -410,7 +466,7 @@ public abstract class ArgType { case 'V': return VOID; } - throw new RuntimeException("Unknown type: " + f); + return null; } public int getRegCount() { diff --git a/src/main/java/jadx/dex/nodes/MethodNode.java b/src/main/java/jadx/dex/nodes/MethodNode.java index 983cffa99..56e1f406d 100644 --- a/src/main/java/jadx/dex/nodes/MethodNode.java +++ b/src/main/java/jadx/dex/nodes/MethodNode.java @@ -2,7 +2,10 @@ package jadx.dex.nodes; 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; @@ -28,12 +31,16 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.android.dx.io.ClassData.Method; import com.android.dx.io.Code; import com.android.dx.io.Code.CatchHandler; import com.android.dx.io.Code.Try; public class MethodNode extends AttrNode implements ILoadable { + private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class); private final MethodInfo mthInfo; private final ClassNode parentClass; @@ -44,6 +51,7 @@ public class MethodNode extends AttrNode implements ILoadable { private List instructions; private boolean noCode; + private ArgType retType; private RegisterArg thisArg; private List argsList; @@ -65,7 +73,8 @@ public class MethodNode extends AttrNode implements ILoadable { if (methodData.getCodeOffset() == 0) { noCode = true; regsCount = 0; - initArguments(); + retType = mthInfo.getReturnType(); + initArguments(mthInfo.getArgumentsTypes()); } else { noCode = false; } @@ -81,6 +90,11 @@ public class MethodNode extends AttrNode implements ILoadable { Code mthCode = dex.readCode(methodData); regsCount = mthCode.getRegistersSize(); + if (!parseSignature()) { + retType = mthInfo.getReturnType(); + initArguments(mthInfo.getArgumentsTypes()); + } + InsnDecoder decoder = new InsnDecoder(this, mthCode); InsnNode[] insnByOffset = decoder.run(); instructions = new ArrayList(); @@ -90,7 +104,6 @@ public class MethodNode extends AttrNode implements ILoadable { } ((ArrayList) instructions).trimToSize(); - initArguments(); initTryCatches(mthCode, insnByOffset); initJumps(insnByOffset); @@ -116,8 +129,43 @@ public class MethodNode extends AttrNode implements ILoadable { noCode = true; } - private void initArguments() { - List args = mthInfo.getArgumentsTypes(); + @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"); + 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 returnType = sign.substring(lastBracket + 1); + + retType = ArgType.parseSignature(returnType); + 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 + ); + } + return false; + } + initArguments(argsTypes); + return true; + } + + private void initArguments(List args) { int pos; if (!noCode) { pos = regsCount; @@ -134,6 +182,11 @@ public class MethodNode extends AttrNode implements ILoadable { thisArg.getTypedVar().setName("this"); } + if (args.isEmpty()) { + argsList = Collections.emptyList(); + return; + } + argsList = new ArrayList(args.size()); for (ArgType arg : args) { argsList.add(InsnArg.reg(pos, arg)); @@ -156,15 +209,8 @@ public class MethodNode extends AttrNode implements ILoadable { return thisArg; } - // TODO: args types can change during type resolving => reset and copy back names - @Deprecated - public void resetArgsTypes() { - List modArgs = new ArrayList(argsList); - initArguments(); - - for (int i = 0; i < argsList.size(); i++) { - argsList.get(i).getTypedVar().setName(modArgs.get(i).getTypedVar().getName()); - } + public ArgType getReturnType() { + return retType; } // move to external class @@ -394,7 +440,7 @@ public class MethodNode extends AttrNode implements ILoadable { @Override public String toString() { - return mthInfo.getReturnType() + return retType + " " + parentClass.getFullName() + "." + mthInfo.getName() + "(" + Utils.listToString(mthInfo.getArgumentsTypes()) + ")"; } diff --git a/src/main/java/jadx/dex/visitors/ConstInlinerVisitor.java b/src/main/java/jadx/dex/visitors/ConstInlinerVisitor.java index f137d737d..f61006e03 100644 --- a/src/main/java/jadx/dex/visitors/ConstInlinerVisitor.java +++ b/src/main/java/jadx/dex/visitors/ConstInlinerVisitor.java @@ -137,7 +137,7 @@ public class ConstInlinerVisitor extends AbstractVisitor { case RETURN: if (insn.getArgsCount() != 0) { - insn.getArg(0).merge(mth.getMethodInfo().getReturnType()); + insn.getArg(0).merge(mth.getReturnType()); } break; diff --git a/src/main/java/jadx/dex/visitors/DotGraphVisitor.java b/src/main/java/jadx/dex/visitors/DotGraphVisitor.java index 89427eaf9..ac41ecedb 100644 --- a/src/main/java/jadx/dex/visitors/DotGraphVisitor.java +++ b/src/main/java/jadx/dex/visitors/DotGraphVisitor.java @@ -64,7 +64,7 @@ public class DotGraphVisitor extends AbstractVisitor { dot.startLine("MethodNode[shape=record,label=\"{" + escape(mth.getAccessFlags().makeString()) - + escape(mth.getMethodInfo().getReturnType() + " " + + escape(mth.getReturnType() + " " + mth.getParentClass().getFullName() + "." + mth.getName() + "(" + Utils.listToString(mth.getArguments(true)) + ") ") + (attrs.length() == 0 ? "" : " | " + attrs) diff --git a/src/main/java/jadx/dex/visitors/regions/PostRegionVisitor.java b/src/main/java/jadx/dex/visitors/regions/PostRegionVisitor.java index cb34efdb4..2f44220b2 100644 --- a/src/main/java/jadx/dex/visitors/regions/PostRegionVisitor.java +++ b/src/main/java/jadx/dex/visitors/regions/PostRegionVisitor.java @@ -29,7 +29,7 @@ public class PostRegionVisitor extends AbstractVisitor { * Remove useless return at end */ private void removeReturn(MethodNode mth) { - if (!mth.getMethodInfo().getReturnType().equals(ArgType.VOID)) + if (!mth.getReturnType().equals(ArgType.VOID)) return; if (!(mth.getRegion() instanceof Region)) diff --git a/src/main/java/jadx/utils/Utils.java b/src/main/java/jadx/utils/Utils.java index a3a2835a2..c6099d834 100644 --- a/src/main/java/jadx/utils/Utils.java +++ b/src/main/java/jadx/utils/Utils.java @@ -28,8 +28,13 @@ public class Utils { } public static String escape(String str) { - return str.replace('.', '_').replace('/', '_').replace(';', '_') - .replace('$', '_').replace("[]", "_A"); + return str.replace('.', '_') + .replace('/', '_') + .replace(';', '_') + .replace('$', '_') + .replace('<', '_') + .replace('>', '_') + .replace("[]", "_A"); } public static String listToString(Iterable list) { @@ -46,6 +51,16 @@ public class Utils { return str.toString(); } + public static String arrayToString(Object[] array) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < array.length; i++) { + if (i != 0) + sb.append(", "); + sb.append(array[i]); + } + return sb.toString(); + } + public static String getStackTrace(Throwable throwable) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw, true); diff --git a/src/samples/java/jadx/samples/TestCF3.java b/src/samples/java/jadx/samples/TestCF3.java index 12adef9c0..1be4fbfc8 100644 --- a/src/samples/java/jadx/samples/TestCF3.java +++ b/src/samples/java/jadx/samples/TestCF3.java @@ -68,6 +68,19 @@ public class TestCF3 extends AbstractTest { return l1.size() == 0; } + public boolean testNestedLoops2(List list) { + int i = 0; + int j = 0; + while (i < list.size()) { + String s = list.get(i); + while (j < s.length()) { + j++; + } + i++; + } + return j > 10; + } + public static boolean testLabeledBreakContinue() { String searchMe = "Look for a substring in me"; String substring = "sub"; diff --git a/src/samples/java/jadx/samples/TestGenerics.java b/src/samples/java/jadx/samples/TestGenerics.java new file mode 100644 index 000000000..4cd3c9250 --- /dev/null +++ b/src/samples/java/jadx/samples/TestGenerics.java @@ -0,0 +1,35 @@ +package jadx.samples; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TestGenerics extends AbstractTest { + + private List test1(Map map) { + List list = new ArrayList(); + String str = map.get("key"); + list.add(str); + return list; + } + + public void test2(Map map, List list) { + String str = map.get("key"); + list.add(str); + } + + public void test3(List list, int a, float[] b, String[] c, String[][][] d) { + + } + + @Override + public boolean testRun() throws Exception { + assertTrue(test1(new HashMap()) != null); + return true; + } + + public static void main(String[] args) throws Exception { + new TestGenerics().testRun(); + } +}