From 47d65fcd87c8cf1c764be46320c35cf48c948e76 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 1 Mar 2014 22:38:18 +0400 Subject: [PATCH] core: improve signature parser --- .../main/java/jadx/core/codegen/ClassGen.java | 47 +-- .../java/jadx/core/codegen/MethodGen.java | 11 +- .../core/dex/instructions/args/ArgType.java | 268 +++++++--------- .../java/jadx/core/dex/nodes/ClassNode.java | 60 ++-- .../java/jadx/core/dex/nodes/FieldNode.java | 2 +- .../java/jadx/core/dex/nodes/MethodNode.java | 82 ++--- .../dex/nodes/parser/SignatureParser.java | 288 ++++++++++++++++++ .../src/main/java/jadx/core/utils/Utils.java | 35 +-- .../dex/nodes/parser/TestSignatureParser.java | 108 +++++++ .../tests/internal/generics/TestGenerics.java | 39 +++ 10 files changed, 634 insertions(+), 306 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java create mode 100644 jadx-core/src/test/java/jadx/core/dex/nodes/parser/TestSignatureParser.java create mode 100644 jadx-core/src/test/java/jadx/tests/internal/generics/TestGenerics.java diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index 220d582be..2d8bb9e6f 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -338,28 +338,35 @@ public class ClassGen { public String useClass(ClassInfo classInfo) { String baseClass = useClassInternal(cls.getClassInfo(), classInfo); - ArgType[] generics = classInfo.getType().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(TypeGen.translate(this, gt)); - } else { - sb.append('?'); - } - } - sb.append('>'); - return sb.toString(); - } else { + ArgType type = classInfo.getType(); + ArgType[] generics = type.getGenericTypes(); + if (generics == null) { return baseClass; } + + 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]; + ArgType wt = gt.getWildcardType(); + if (wt != null) { + sb.append('?'); + int bounds = gt.getWildcardBounds(); + if (bounds != 0) { + sb.append(bounds == -1 ? " super " : " extends "); + sb.append(TypeGen.translate(this, wt)); + } + } else { + sb.append(TypeGen.translate(this, gt)); + } + } + sb.append('>'); + return sb.toString(); } private String useClassInternal(ClassInfo useCls, ClassInfo classInfo) { diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index 2c01a5a20..d9965e768 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -279,15 +279,20 @@ public class MethodGen { mth.load(); DepthTraverser.visit(new FallbackModeVisitor(), mth); } catch (DecodeException e) { - // ignore - code.startLine("Can't loadFile method instructions"); + LOG.error("Error reload instructions in fallback mode:", e); + code.startLine("// Can't loadFile method instructions: " + e.getMessage()); return; } } + List insns = mth.getInstructions(); + if (insns == null) { + code.startLine("// Can't load method instructions."); + return; + } if (mth.getThisArg() != null) { code.startLine(getFallbackMethodGen(mth).makeArgName(mth.getThisArg())).add(" = this;"); } - addFallbackInsns(code, mth, mth.getInstructions(), true); + addFallbackInsns(code, mth, insns, true); } public static void addFallbackInsns(CodeWriter code, MethodNode mth, List insns, boolean addLabels) { diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java index 22b228a7f..67d966b6c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java @@ -2,20 +2,14 @@ package jadx.core.dex.instructions.args; import jadx.core.Consts; import jadx.core.clsp.ClspGraph; +import jadx.core.dex.nodes.parser.SignatureParser; import jadx.core.utils.Utils; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -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); @@ -59,19 +53,31 @@ public abstract class ArgType { } public static ArgType object(String obj) { - return new ObjectArg(obj); + return new ObjectType(obj); } public static ArgType genericType(String type) { - return new GenericTypeArg(type); + return new GenericType(type); + } + + public static ArgType wildcard() { + return new WildcardType(OBJECT, 0); + } + + public static ArgType wildcard(ArgType obj, int bound) { + return new WildcardType(obj, bound); } public static ArgType generic(String sign) { - return parseSignature(sign); + return new SignatureParser(sign).consumeType(); } public static ArgType generic(String obj, ArgType[] generics) { - return new GenericObjectArg(obj, generics); + return new GenericObject(obj, generics); + } + + public static ArgType genericInner(ArgType genericType, String innerName, ArgType[] generics) { + return new GenericObject((GenericObject) genericType, innerName, generics); } public static ArgType array(ArgType vtype) { @@ -82,14 +88,14 @@ public abstract class ArgType { return new UnknownArg(types); } - private abstract static class KnownTypeArg extends ArgType { + private abstract static class KnownType extends ArgType { @Override public boolean isTypeKnown() { return true; } } - private static final class PrimitiveArg extends KnownTypeArg { + private static final class PrimitiveArg extends KnownType { private final PrimitiveType type; public PrimitiveArg(PrimitiveType type) { @@ -118,10 +124,10 @@ public abstract class ArgType { } } - private static class ObjectArg extends KnownTypeArg { + private static class ObjectType extends KnownType { private final String object; - public ObjectArg(String obj) { + public ObjectType(String obj) { this.object = Utils.cleanObjectName(obj); this.hash = object.hashCode(); } @@ -143,7 +149,7 @@ public abstract class ArgType { @Override boolean internalEquals(Object obj) { - return object.equals(((ObjectArg) obj).object); + return object.equals(((ObjectType) obj).object); } @Override @@ -152,8 +158,8 @@ public abstract class ArgType { } } - private static final class GenericTypeArg extends ObjectArg { - public GenericTypeArg(String obj) { + private static final class GenericType extends ObjectType { + public GenericType(String obj) { super(obj); } @@ -163,24 +169,82 @@ public abstract class ArgType { } } - private static final class GenericObjectArg extends ObjectArg { - private final ArgType[] generics; + private static final class WildcardType extends ObjectType { + private final ArgType type; + private final int bounds; - public GenericObjectArg(String obj, ArgType[] generics) { + public WildcardType(ArgType obj, int bound) { + super(obj.getObject()); + this.type = obj; + this.bounds = bound; + } + + @Override + public ArgType getWildcardType() { + return type; + } + + /** + * Return wildcard bounds: + *
    + *
  • 1 for upper bound (? extends A)
  • + *
  • 0 no bounds (?)
  • + *
  • -1 for lower bound (? super A)
  • + *
+ */ + @Override + public int getWildcardBounds() { + return bounds; + } + + @Override + boolean internalEquals(Object obj) { + return super.internalEquals(obj) + && bounds == ((WildcardType) obj).bounds; + } + + @Override + public String toString() { + if (bounds == 0) { + return "?"; + } + return "? " + (bounds == -1 ? "super" : "extends") + " " + super.toString(); + } + } + + private static class GenericObject extends ObjectType { + private final ArgType[] generics; + private final GenericObject outerType; + + public GenericObject(String obj, ArgType[] generics) { super(obj); + this.outerType = null; this.generics = generics; this.hash = obj.hashCode() + 31 * Arrays.hashCode(generics); } + public GenericObject(GenericObject outerType, String innerName, ArgType[] generics) { + super(outerType.getObject() + "." + innerName); + this.outerType = outerType; + this.generics = generics; + this.hash = outerType.hashCode() + 31 * innerName.hashCode() + + 31 * 31 * Arrays.hashCode(generics); + } + @Override public ArgType[] getGenericTypes() { return generics; } + @Override + public ArgType getOuterType() { + return outerType; + } + @Override boolean internalEquals(Object obj) { return super.internalEquals(obj) - && Arrays.equals(generics, ((GenericObjectArg) obj).generics); + && Arrays.equals(generics, ((GenericObject) obj).generics); } @Override @@ -189,7 +253,7 @@ public abstract class ArgType { } } - private static final class ArrayArg extends KnownTypeArg { + private static final class ArrayArg extends KnownType { private final ArgType arrayElement; public ArrayArg(ArgType arrayElement) { @@ -314,6 +378,21 @@ public abstract class ArgType { return null; } + public ArgType getWildcardType() { + return null; + } + + /** + * @see jadx.core.dex.instructions.args.ArgType.WildcardType#getWildcardBounds() + */ + public int getWildcardBounds() { + return 0; + } + + public ArgType getOuterType() { + return null; + } + public boolean isArray() { return false; } @@ -459,148 +538,7 @@ public abstract class ArgType { } } - public static ArgType parseSignature(String sign) { - int b = sign.indexOf('<'); - if (b == -1) { - return parse(sign); - } - if (sign.charAt(0) == '[') { - return array(parseSignature(sign.substring(1))); - } - String obj = sign.substring(0, b) + ";"; - String genericsStr = sign.substring(b + 1, sign.length() - 2); - List generics = parseSignatureList(genericsStr); - 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) { - if (str.isEmpty()) { - return Collections.emptyList(); - } - if (str.equals("*")) { - return Arrays.asList(UNKNOWN); - } - List signs = new ArrayList(3); - 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': - case 'T': - if (obj == 0 && gen == 0) { - obj++; - objStart = pos; - } - break; - - case ';': - if (obj == 1 && gen == 0) { - obj--; - String o = str.substring(objStart, pos + 1); - type = parseSignature(o); - } - 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; - case '>': - gen--; - break; - - case '[': - if (obj == 0 && gen == 0) { - arr++; - } - break; - - default: - if (parsePrimitives && 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; - 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); - } - } - return genericMap; - } catch (Throwable e) { - LOG.warn("Generic map parse exception: {}", gen, e); - return null; - } - } - - private static ArgType parse(char f) { + public static ArgType parse(char f) { switch (f) { case 'Z': return BOOLEAN; diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 86be04414..1e605bf07 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -16,9 +16,10 @@ import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.nodes.parser.AnnotationsParser; import jadx.core.dex.nodes.parser.FieldValueAttr; +import jadx.core.dex.nodes.parser.SignatureParser; import jadx.core.dex.nodes.parser.StaticValuesParser; -import jadx.core.utils.Utils; import jadx.core.utils.exceptions.DecodeException; +import jadx.core.utils.exceptions.JadxRuntimeException; import java.util.ArrayList; import java.util.Collections; @@ -159,48 +160,41 @@ public class ClassNode extends LineAttrNode implements ILoadable { } } - @SuppressWarnings("unchecked") private void parseClassSignature() { - Annotation a = this.getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE); - if (a == null) { + SignatureParser sp = SignatureParser.fromNode(this); + if (sp == 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(st); - int i = 0; - for (ArgType it : list) { - ClassInfo interf = ClassInfo.fromType(it); - interfaces.set(i, interf); - i++; + try { + // parse class generic map + genericMap = sp.consumeGenericMap(); + // parse super class signature + superClass = ClassInfo.fromType(sp.consumeType()); + // parse interfaces signatures + for (int i = 0; i < interfaces.size(); i++) { + ArgType type = sp.consumeType(); + if (type != null) { + interfaces.set(i, ClassInfo.fromType(type)); + } else { + break; } - } catch (Throwable e) { - LOG.warn("Can't set signatures for class: {}, sign: {}", this, sign, e); } + } catch (JadxRuntimeException e) { + LOG.error("Class signature parse error: " + this, e); } } - @SuppressWarnings("unchecked") private void setFieldsTypesFromSignature() { for (FieldNode field : fields) { - Annotation a = field.getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE); - if (a != null) { - String sign = Utils.mergeSignature((List) a.getDefaultValue()); - ArgType gType = ArgType.parseSignature(sign); - if (gType != null) { - field.setType(gType); + SignatureParser sp = SignatureParser.fromNode(field); + if (sp != null) { + try { + ArgType gType = sp.consumeType(); + if (gType != null) { + field.setType(gType); + } + } catch (JadxRuntimeException e) { + LOG.error("Field signature parse error: " + field, e); } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java index 4be3cfa8e..5580d7a6e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java @@ -60,6 +60,6 @@ public class FieldNode extends LineAttrNode { @Override public String toString() { - return fieldInfo.getDeclClass() + "." + fieldInfo.getName() + " " + type; + return fieldInfo.getDeclClass() + "." + fieldInfo.getName() + " :" + type; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index ed2c2f9ef..16552c8ef 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -1,11 +1,9 @@ package jadx.core.dex.nodes; -import jadx.core.Consts; import jadx.core.dex.attributes.AttributeFlag; import jadx.core.dex.attributes.JumpAttribute; import jadx.core.dex.attributes.LineAttrNode; import jadx.core.dex.attributes.LoopAttr; -import jadx.core.dex.attributes.annotations.Annotation; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo.AFType; import jadx.core.dex.info.ClassInfo; @@ -18,12 +16,14 @@ 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.parser.DebugInfoParser; +import jadx.core.dex.nodes.parser.SignatureParser; import jadx.core.dex.regions.Region; import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.TryCatchBlock; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.DecodeException; +import jadx.core.utils.exceptions.JadxRuntimeException; import java.util.ArrayList; import java.util.Collections; @@ -140,62 +140,42 @@ public class MethodNode extends LineAttrNode implements ILoadable { noCode = true; } - @SuppressWarnings("unchecked") private boolean parseSignature() { - Annotation a = getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE); - if (a == null) { + SignatureParser sp = SignatureParser.fromNode(this); + if (sp == null) { return false; } + try { + genericMap = sp.consumeGenericMap(); + List argsTypes = sp.consumeMethodArgs(); + retType = sp.consumeType(); - 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; - } - - List argsTypes = ArgType.parseSignatureList(argsTypesStr); - if (argsTypes == null) { - return false; - } - - List mthArgs = mthInfo.getArgumentsTypes(); - if (argsTypes.size() != mthArgs.size()) { - if (argsTypes.isEmpty()) { - return false; - } - if (!mthInfo.isConstructor()) { - LOG.warn("Wrong signature parse result: " + sign + " -> " + argsTypes - + ", not generic version: " + mthArgs); - return false; - } else if (getParentClass().getAccessFlags().isEnum()) { - // TODO: - argsTypes.add(0, mthArgs.get(0)); - argsTypes.add(1, mthArgs.get(1)); - } else { - // add synthetic arg for outer class - argsTypes.add(0, mthArgs.get(0)); - } + List mthArgs = mthInfo.getArgumentsTypes(); if (argsTypes.size() != mthArgs.size()) { - return false; + if (argsTypes.isEmpty()) { + return false; + } + if (!mthInfo.isConstructor()) { + LOG.warn("Wrong signature parse result: " + sp + " -> " + argsTypes + + ", not generic version: " + mthArgs); + return false; + } else if (getParentClass().getAccessFlags().isEnum()) { + // TODO: + argsTypes.add(0, mthArgs.get(0)); + argsTypes.add(1, mthArgs.get(1)); + } else { + // add synthetic arg for outer class + argsTypes.add(0, mthArgs.get(0)); + } + if (argsTypes.size() != mthArgs.size()) { + return false; + } } + initArguments(argsTypes); + } catch (JadxRuntimeException e) { + LOG.error("Method signature parse error: " + this, e); + return false; } - - initArguments(argsTypes); return true; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java new file mode 100644 index 000000000..88bd4939e --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java @@ -0,0 +1,288 @@ +package jadx.core.dex.nodes.parser; + +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.utils.exceptions.JadxRuntimeException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class SignatureParser { + private static final char STOP_CHAR = 0; + + private final String sign; + private final int end; + private int pos; + private int mark; + + public SignatureParser(String signature) { + sign = signature; + pos = -1; + mark = 0; + end = sign.length(); + } + + @SuppressWarnings("unchecked") + public static SignatureParser fromNode(IAttributeNode node) { + Annotation a = node.getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE); + if (a == null) { + return null; + } + return new SignatureParser(mergeSignature((List) a.getDefaultValue())); + } + + private char next() { + pos++; + if (pos >= end) { + return STOP_CHAR; + } + return sign.charAt(pos); + } + + private boolean lookAhead(char ch) { + int next = pos + 1; + return next < end && sign.charAt(next) == ch; + } + + private void mark() { + mark = pos; + } + + /** + * Exclusive slice. + * + * @return string from 'mark' to current position (not including current character) + */ + private String slice() { + if (mark >= pos) { + return ""; + } + return sign.substring(mark, pos); + } + + /** + * Inclusive slice (includes current character) + */ + private String inclusiveSlice() { + if (mark >= pos) { + return ""; + } + return sign.substring(mark, pos + 1); + } + + private boolean forwardTo(char lastChar) { + int startPos = pos; + char ch; + while ((ch = next()) != STOP_CHAR) { + if (ch == lastChar) { + return true; + } + } + pos = startPos; + return false; + } + + private void consume(char exp) { + char c = next(); + if (exp != c) { + throw new JadxRuntimeException("Consume wrong char: '" + c + "' != '" + exp + + "', sign: " + debugString()); + } + } + + private boolean tryConsume(char exp) { + if (lookAhead(exp)) { + next(); + return true; + } + return false; + } + + private String consumeUntil(char lastChar) { + mark(); + return forwardTo(lastChar) ? slice() : null; + } + + public ArgType consumeType() { + char ch = next(); + mark(); + switch (ch) { + case 'L': + ArgType obj = consumeObjectType(false); + if (obj != null) { + return obj; + } + break; + case 'T': + next(); + mark(); + if (forwardTo(';')) { + return ArgType.genericType(slice()); + } + break; + case '[': + return ArgType.array(consumeType()); + + case STOP_CHAR: + return null; + + default: + // primitive type (one char) + ArgType type = ArgType.parse(ch); + if (type != null) { + return type; + } + break; + } + throw new JadxRuntimeException("Can't parse type: " + debugString()); + } + + private ArgType consumeObjectType(boolean incompleteType) { + mark(); + int ch; + do { + ch = next(); + if (ch == STOP_CHAR) { + return null; + } + } while (ch != '<' && ch != ';'); + + if (ch == ';') { + return ArgType.object(incompleteType ? slice() : inclusiveSlice()); + } else { + // generic type start ('<') + String obj = slice(); + if (!incompleteType) { + obj += ";"; + } + ArgType[] genArr = consumeGenericArgs(); + consume('>'); + + ArgType genericType = ArgType.generic(obj, genArr); + if (lookAhead('.')) { + consume('.'); + next(); + // type parsing not completed, proceed to inner class + ArgType inner = consumeObjectType(true); + return ArgType.genericInner(genericType, inner.getObject(), inner.getGenericTypes()); + } else { + consume(';'); + return genericType; + } + } + } + + private ArgType[] consumeGenericArgs() { + List list = new ArrayList(1); + ArgType type; + do { + if (lookAhead('*')) { + next(); + type = ArgType.wildcard(); + } else if (lookAhead('+')) { + next(); + type = ArgType.wildcard(consumeType(), 1); + } else if (lookAhead('-')) { + next(); + type = ArgType.wildcard(consumeType(), -1); + } else { + type = consumeType(); + } + if (type != null) { + list.add(type); + } + } while (type != null && !lookAhead('>')); + return list.toArray(new ArgType[list.size()]); + } + + /** + * Map of generic types names to extends classes. + *

+ * Example: "" + */ + public Map> consumeGenericMap() { + if (!lookAhead('<')) { + return null; + } + Map> map = new LinkedHashMap>(2); + consume('<'); + while (true) { + if (lookAhead('>') || next() == STOP_CHAR) { + break; + } + String id = consumeUntil(':'); + tryConsume(':'); + List types = consumeExtendsTypesList(); + map.put(ArgType.genericType(id), types); + } + consume('>'); + return map; + } + + /** + * List of types separated by ':' last type is 'java.lang.Object'. + *

+ * Example: "Ljava/lang/Exception;:Ljava/lang/Object;" + */ + private List consumeExtendsTypesList() { + List types = Collections.emptyList(); + boolean next; + do { + ArgType argType = consumeType(); + if (!argType.equals(ArgType.OBJECT)) { + if (types.isEmpty()) { + types = new LinkedList(); + } + types.add(argType); + } + next = lookAhead(':'); + if (next) { + consume(':'); + } + } while (next); + return types; + } + + public List consumeMethodArgs() { + consume('('); + if (lookAhead(')')) { + consume(')'); + return Collections.emptyList(); + } + List args = new LinkedList(); + do { + args.add(consumeType()); + } while (!lookAhead(')')); + consume(')'); + return args; + } + + private static String mergeSignature(List list) { + if (list.size() == 1) { + return list.get(0); + } + StringBuilder sb = new StringBuilder(); + for (String s : list) { + sb.append(s); + } + return sb.toString(); + } + + private String debugString() { + return sign + " at position " + pos + " ('" + sign.charAt(pos) + "')"; + } + + @Override + public String toString() { + if (pos == -1) { + return sign; + } + return sign.substring(0, mark) + '{' + sign.substring(mark, pos) + '}' + sign.substring(pos); + } +} diff --git a/jadx-core/src/main/java/jadx/core/utils/Utils.java b/jadx-core/src/main/java/jadx/core/utils/Utils.java index 0b224141f..a995b7666 100644 --- a/jadx-core/src/main/java/jadx/core/utils/Utils.java +++ b/jadx-core/src/main/java/jadx/core/utils/Utils.java @@ -6,13 +6,10 @@ import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Iterator; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class Utils { - private static final Logger LOG = LoggerFactory.getLogger(Utils.class); + private Utils() { + } public static String cleanObjectName(String obj) { int last = obj.length() - 1; @@ -97,34 +94,6 @@ public class Utils { return sw.getBuffer().toString(); } - public static String mergeSignature(List list) { - StringBuilder sb = new StringBuilder(); - for (String s : list) { - sb.append(s); - } - 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 void makeDirsForFile(File file) { File dir = file.getParentFile(); if (!dir.exists()) { diff --git a/jadx-core/src/test/java/jadx/core/dex/nodes/parser/TestSignatureParser.java b/jadx-core/src/test/java/jadx/core/dex/nodes/parser/TestSignatureParser.java new file mode 100644 index 000000000..9e8a4bb2f --- /dev/null +++ b/jadx-core/src/test/java/jadx/core/dex/nodes/parser/TestSignatureParser.java @@ -0,0 +1,108 @@ +package jadx.core.dex.nodes.parser; + +import jadx.core.dex.instructions.args.ArgType; + +import java.util.List; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class TestSignatureParser { + + private SignatureParser p(String str) { + return new SignatureParser(str); + } + + @Test + public void testType() { + assertEquals(p("").consumeType(), null); + assertEquals(p("I").consumeType(), ArgType.INT); + assertEquals(p("[I").consumeType(), ArgType.array(ArgType.INT)); + assertEquals(p("Ljava/lang/Object;").consumeType(), ArgType.OBJECT); + assertEquals(p("[Ljava/lang/Object;").consumeType(), ArgType.array(ArgType.OBJECT)); + assertEquals(p("[[I").consumeType(), ArgType.array(ArgType.array(ArgType.INT))); + } + + @Test + public void testType2() { + assertEquals(p("TD;").consumeType(), ArgType.genericType("D")); + } + + @Test + public void testGenericType() { + assertEquals(p("La;").consumeType(), + ArgType.generic("La;", new ArgType[]{ArgType.genericType("V"), ArgType.object("b")})); + + assertEquals(p("La;>;").consumeType(), + ArgType.generic("La;", new ArgType[]{ + ArgType.generic("Lb;", new ArgType[]{ + ArgType.object("Lc;")})})); + } + + @Test + public void testGenericInnerType() { + assertEquals(p("La.c;").consumeType(), + ArgType.genericInner(ArgType.generic("La;", new ArgType[]{ArgType.genericType("D")}), "c", null)); + + assertEquals(p("La.c;").consumeType(), + ArgType.genericInner(ArgType.generic("La;", new ArgType[]{ArgType.object("Lb;")}), + "c", new ArgType[]{ArgType.genericType("V")})); + + assertEquals(p("La.LinkedHashIterator;>;").consumeType().getObject(), + "a.LinkedHashIterator"); + + } + + @Test + public void testWildCards() { + assertEquals(p("La<*>;").consumeType(), + ArgType.generic("La;", new ArgType[]{ArgType.wildcard()})); + + assertEquals(p("La<+Lb;>;").consumeType(), + ArgType.generic("La;", new ArgType[]{ArgType.wildcard(ArgType.object("b"), 1)})); + + assertEquals(p("La<-Lb;>;").consumeType(), + ArgType.generic("La;", new ArgType[]{ArgType.wildcard(ArgType.object("b"), -1)})); + + assertEquals(p("La<+TV;>;").consumeType(), + ArgType.generic("La;", new ArgType[]{ArgType.wildcard(ArgType.genericType("V"), 1)})); + + assertEquals(p("La<-TV;>;").consumeType(), + ArgType.generic("La;", new ArgType[]{ArgType.wildcard(ArgType.genericType("V"), -1)})); + } + + @Test + public void testWildCards2() { + assertEquals(p("La<*>;").consumeType(), + ArgType.generic("La;", new ArgType[]{ArgType.wildcard()})); + + assertEquals(p("La<**>;").consumeType(), + ArgType.generic("La;", new ArgType[]{ArgType.wildcard(), ArgType.wildcard()})); + + assertEquals(p("La<*Lb;>;").consumeType(), + ArgType.generic("La;", new ArgType[]{ArgType.wildcard(), ArgType.object("b")})); + + assertEquals(p("La<*TV;>;").consumeType(), + ArgType.generic("La;", new ArgType[]{ArgType.wildcard(), ArgType.genericType("V")})); + } + + @Test + public void testGenericMap() { + assertEquals(p("").consumeGenericMap().toString(), + "{T=[]}"); + + assertEquals(p("").consumeGenericMap().toString(), + "{K=[], LongGenericType=[]}"); + + assertEquals(p("").consumeGenericMap().toString(), + "{ResultT=[java.lang.Exception]}"); + } + + @Test + public void testMethodsArgs() { + List argTypes = p("(Ljava/util/List<*>;)V").consumeMethodArgs(); + assertEquals(argTypes.size(), 1); + assertEquals(argTypes.get(0), ArgType.generic("Ljava/util/List;", new ArgType[]{ArgType.wildcard()})); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/internal/generics/TestGenerics.java b/jadx-core/src/test/java/jadx/tests/internal/generics/TestGenerics.java new file mode 100644 index 000000000..9098b6ca2 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/internal/generics/TestGenerics.java @@ -0,0 +1,39 @@ +package jadx.tests.internal.generics; + +import jadx.api.InternalJadxTest; +import jadx.core.dex.nodes.ClassNode; + +import java.util.List; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; + +public class TestGenerics extends InternalJadxTest { + + public static class TestCls { + class A { + } + + public static void mthWildcard(List list) { + } + + public static void mthExtends(List list) { + } + + public static void mthSuper(List list) { + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + System.out.println(code); + + assertThat(code, containsString("mthWildcard(List list)")); + assertThat(code, containsString("mthExtends(List list)")); + assertThat(code, containsString("mthSuper(List list)")); + } +}