Commit 3b84aec5 authored by Skylot's avatar Skylot

Add generic types to methods declarations

parent cc318b13
......@@ -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<String>) a.getValues().get("value")));
else if (Consts.DEBUG)
if (aCls.equals("dalvik.annotation.Signature")) {
if (!(node instanceof MethodNode)) {
String sign = Utils.mergeSignature((List<String>) a.getValues().get("value"));
List<ArgType> types = ArgType.parseSignatureList(sign);
code.startLine("// signature: " + Utils.listToString(types));
}
} else if (Consts.DEBUG) {
code.startLine("// " + a);
}
} else {
code.startLine();
code.add(formatAnnotation(a));
......
......@@ -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) {
......
......@@ -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<InsnGenState> 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<InsnGenState> state)
......
......@@ -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<RegisterArg> args = mth.getArguments(false);
if (mth.getMethodInfo().isConstructor()
&& mth.getParentClass().getAttributes().contains(AttributeType.ENUM_CLASS)) {
......
......@@ -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);
......
......@@ -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);
......
......@@ -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<ArgType> generics = parseSignatureList(genericsStr);
ArgType res = generic(obj + ";", generics.toArray(new ArgType[generics.size()]));
return res;
}
public static List<ArgType> parseSignatureList(String str) {
List<ArgType> signs = new ArrayList<ArgType>(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 ';':
if (obj == 1 && gen == 0) {
obj--;
String o = str.substring(objStart, pos + 1);
type = parseSignature(o);
}
break;
case '<':
gen++;
break;
case '>':
gen--;
break;
case '*':
case '?':
result[i] = UNKNOWN;
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() {
......
......@@ -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<InsnNode> instructions;
private boolean noCode;
private ArgType retType;
private RegisterArg thisArg;
private List<RegisterArg> 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<InsnNode>();
......@@ -90,7 +104,6 @@ public class MethodNode extends AttrNode implements ILoadable {
}
((ArrayList<InsnNode>) 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<ArgType> 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<String>) 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<ArgType> 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<ArgType> 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<RegisterArg>(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<InsnArg> modArgs = new ArrayList<InsnArg>(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()) + ")";
}
......
......@@ -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;
......
......@@ -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)
......
......@@ -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))
......
......@@ -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);
......
......@@ -68,6 +68,19 @@ public class TestCF3 extends AbstractTest {
return l1.size() == 0;
}
public boolean testNestedLoops2(List<String> 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";
......
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<String> test1(Map<String, String> map) {
List<String> list = new ArrayList<String>();
String str = map.get("key");
list.add(str);
return list;
}
public void test2(Map<String, String> map, List<Object> list) {
String str = map.get("key");
list.add(str);
}
public void test3(List<Object> list, int a, float[] b, String[] c, String[][][] d) {
}
@Override
public boolean testRun() throws Exception {
assertTrue(test1(new HashMap<String, String>()) != null);
return true;
}
public static void main(String[] args) throws Exception {
new TestGenerics().testRun();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment