core: improve signature parser
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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<InsnNode> 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<InsnNode> insns, boolean addLabels) {
|
||||
|
||||
@@ -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:
|
||||
* <ul>
|
||||
* <li> 1 for upper bound (? extends A) </li>
|
||||
* <li> 0 no bounds (?) </li>
|
||||
* <li>-1 for lower bound (? super A) </li>
|
||||
* </ul>
|
||||
*/
|
||||
@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<ArgType> generics = parseSignatureList(genericsStr);
|
||||
if (generics != null) {
|
||||
return generic(obj, generics.toArray(new ArgType[generics.size()]));
|
||||
} else {
|
||||
return object(obj);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ArgType> parseSignatureList(String str) {
|
||||
try {
|
||||
return parseSignatureListInner(str, true);
|
||||
} catch (Throwable e) {
|
||||
LOG.warn("Signature parse exception: {}", str, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ArgType> parseSignatureListInner(String str, boolean parsePrimitives) {
|
||||
if (str.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (str.equals("*")) {
|
||||
return Arrays.asList(UNKNOWN);
|
||||
}
|
||||
List<ArgType> signs = new ArrayList<ArgType>(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<ArgType, List<ArgType>> parseGenericMap(String gen) {
|
||||
try {
|
||||
Map<ArgType, List<ArgType>> genericMap = null;
|
||||
List<ArgType> genTypes = parseSignatureListInner(gen, false);
|
||||
if (genTypes != null) {
|
||||
genericMap = new LinkedHashMap<ArgType, List<ArgType>>(2);
|
||||
ArgType prev = null;
|
||||
List<ArgType> genList = new ArrayList<ArgType>(2);
|
||||
for (ArgType arg : genTypes) {
|
||||
if (arg.isGenericType()) {
|
||||
if (prev != null) {
|
||||
genericMap.put(prev, genList);
|
||||
genList = new ArrayList<ArgType>();
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -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<String>) 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<ArgType> 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<String>) 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ArgType> argsTypes = sp.consumeMethodArgs();
|
||||
retType = sp.consumeType();
|
||||
|
||||
String sign = Utils.mergeSignature((List<String>) 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<ArgType> argsTypes = ArgType.parseSignatureList(argsTypesStr);
|
||||
if (argsTypes == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<ArgType> 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<ArgType> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String>) 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<ArgType> list = new ArrayList<ArgType>(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.
|
||||
* <p/>
|
||||
* Example: "<T:Ljava/lang/Exception;:Ljava/lang/Object;>"
|
||||
*/
|
||||
public Map<ArgType, List<ArgType>> consumeGenericMap() {
|
||||
if (!lookAhead('<')) {
|
||||
return null;
|
||||
}
|
||||
Map<ArgType, List<ArgType>> map = new LinkedHashMap<ArgType, List<ArgType>>(2);
|
||||
consume('<');
|
||||
while (true) {
|
||||
if (lookAhead('>') || next() == STOP_CHAR) {
|
||||
break;
|
||||
}
|
||||
String id = consumeUntil(':');
|
||||
tryConsume(':');
|
||||
List<ArgType> types = consumeExtendsTypesList();
|
||||
map.put(ArgType.genericType(id), types);
|
||||
}
|
||||
consume('>');
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of types separated by ':' last type is 'java.lang.Object'.
|
||||
* <p/>
|
||||
* Example: "Ljava/lang/Exception;:Ljava/lang/Object;"
|
||||
*/
|
||||
private List<ArgType> consumeExtendsTypesList() {
|
||||
List<ArgType> types = Collections.emptyList();
|
||||
boolean next;
|
||||
do {
|
||||
ArgType argType = consumeType();
|
||||
if (!argType.equals(ArgType.OBJECT)) {
|
||||
if (types.isEmpty()) {
|
||||
types = new LinkedList<ArgType>();
|
||||
}
|
||||
types.add(argType);
|
||||
}
|
||||
next = lookAhead(':');
|
||||
if (next) {
|
||||
consume(':');
|
||||
}
|
||||
} while (next);
|
||||
return types;
|
||||
}
|
||||
|
||||
public List<ArgType> consumeMethodArgs() {
|
||||
consume('(');
|
||||
if (lookAhead(')')) {
|
||||
consume(')');
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ArgType> args = new LinkedList<ArgType>();
|
||||
do {
|
||||
args.add(consumeType());
|
||||
} while (!lookAhead(')'));
|
||||
consume(')');
|
||||
return args;
|
||||
}
|
||||
|
||||
private static String mergeSignature(List<String> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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()) {
|
||||
|
||||
@@ -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<TV;Lb;>;").consumeType(),
|
||||
ArgType.generic("La;", new ArgType[]{ArgType.genericType("V"), ArgType.object("b")}));
|
||||
|
||||
assertEquals(p("La<Lb<Lc;>;>;").consumeType(),
|
||||
ArgType.generic("La;", new ArgType[]{
|
||||
ArgType.generic("Lb;", new ArgType[]{
|
||||
ArgType.object("Lc;")})}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenericInnerType() {
|
||||
assertEquals(p("La<TD;>.c;").consumeType(),
|
||||
ArgType.genericInner(ArgType.generic("La;", new ArgType[]{ArgType.genericType("D")}), "c", null));
|
||||
|
||||
assertEquals(p("La<Lb;>.c<TV;>;").consumeType(),
|
||||
ArgType.genericInner(ArgType.generic("La;", new ArgType[]{ArgType.object("Lb;")}),
|
||||
"c", new ArgType[]{ArgType.genericType("V")}));
|
||||
|
||||
assertEquals(p("La<TV;>.LinkedHashIterator<Lb$c<Ls;TV;>;>;").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("<T:Ljava/lang/Object;>").consumeGenericMap().toString(),
|
||||
"{T=[]}");
|
||||
|
||||
assertEquals(p("<K:Ljava/lang/Object;LongGenericType:Ljava/lang/Object;>").consumeGenericMap().toString(),
|
||||
"{K=[], LongGenericType=[]}");
|
||||
|
||||
assertEquals(p("<ResultT:Ljava/lang/Exception;:Ljava/lang/Object;>").consumeGenericMap().toString(),
|
||||
"{ResultT=[java.lang.Exception]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodsArgs() {
|
||||
List<ArgType> argTypes = p("(Ljava/util/List<*>;)V").consumeMethodArgs();
|
||||
assertEquals(argTypes.size(), 1);
|
||||
assertEquals(argTypes.get(0), ArgType.generic("Ljava/util/List;", new ArgType[]{ArgType.wildcard()}));
|
||||
}
|
||||
}
|
||||
@@ -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<? extends A> list) {
|
||||
}
|
||||
|
||||
public static void mthSuper(List<? super A> 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<? extends A> list)"));
|
||||
assertThat(code, containsString("mthSuper(List<? super A> list)"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user