Commit 2a60ac47 authored by Skylot's avatar Skylot

core: annotate generated code with reference to used methods

parent 9cd72fe1
package jadx.api;
public final class CodePosition {
private final JavaClass cls;
private final int line;
private final int offset;
public CodePosition(JavaClass cls, int line, int offset) {
this.cls = cls;
this.line = line;
this.offset = offset;
}
public CodePosition(int line, int offset) {
this.cls = null;
this.line = line;
this.offset = offset;
}
public JavaClass getJavaClass() {
return cls;
}
public int getLine() {
return line;
}
public int getOffset() {
return offset;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CodePosition that = (CodePosition) o;
return line == that.line && offset == that.offset;
}
@Override
public int hashCode() {
return line + 31 * offset;
}
@Override
public String toString() {
return line + ":" + offset + (cls != null ? " " + cls : "");
}
}
......@@ -2,6 +2,7 @@ package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
......@@ -12,6 +13,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
public final class JavaClass {
......@@ -27,12 +29,23 @@ public final class JavaClass {
this.cls = classNode;
}
public String getCode() {
CodeWriter code = cls.getCode();
if (code == null) {
decompile();
code = cls.getCode();
}
return code != null ? code.toString() : "error processing class";
}
public void decompile() {
if (decompiler == null) {
throw new JadxRuntimeException("Can't decompile inner class");
}
decompiler.processClass(cls);
load();
if (cls.getCode() == null) {
decompiler.processClass(cls);
load();
}
}
private void load() {
......@@ -78,13 +91,32 @@ public final class JavaClass {
}
}
public String getCode() {
CodeWriter code = cls.getCode();
if (code == null) {
decompile();
code = cls.getCode();
private Map<CodePosition, Object> getCodeAnnotations() {
getCode();
return cls.getCode().getAnnotations();
}
public CodePosition getDefinitionPosition(int line, int offset) {
Map<CodePosition, Object> map = getCodeAnnotations();
Object obj = map.get(new CodePosition(line, offset));
if (obj instanceof LineAttrNode) {
ClassNode clsNode = null;
if (obj instanceof ClassNode) {
clsNode = (ClassNode) obj;
} else if (obj instanceof MethodNode) {
clsNode = ((MethodNode) obj).getParentClass();
} else if (obj instanceof FieldNode) {
clsNode = ((FieldNode) obj).getParentClass();
}
if (clsNode != null) {
clsNode = clsNode.getParentClass();
JavaClass jCls = new JavaClass(decompiler, clsNode);
jCls.decompile();
int defLine = ((LineAttrNode) obj).getDecompiledLine();
return new CodePosition(jCls, defLine, 0);
}
}
return code != null ? code.toString() : "error processing class";
return null;
}
public String getFullName() {
......@@ -115,12 +147,22 @@ public final class JavaClass {
return methods;
}
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
@Override
public String toString() {
return getFullName();
public boolean equals(Object o) {
return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
}
public int getDecompiledLine() {
return cls.getDecompiledLine();
@Override
public int hashCode() {
return cls.hashCode();
}
@Override
public String toString() {
return getFullName();
}
}
......@@ -12,6 +12,7 @@ import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.ProcessVariables;
......@@ -82,6 +83,7 @@ public class Jadx {
passes.add(new MethodInlineVisitor());
passes.add(new ClassModifier());
passes.add(new PrepareForCodeGen());
}
passes.add(new CodeGen(args));
return passes;
......
......@@ -47,7 +47,7 @@ public class AnnotationGen {
return;
}
for (Annotation a : aList.getAll()) {
code.add(formatAnnotation(a));
formatAnnotation(code, a);
code.add(' ');
}
}
......@@ -66,26 +66,25 @@ public class AnnotationGen {
}
} else {
code.startLine();
code.add(formatAnnotation(a));
formatAnnotation(code, a);
}
}
}
private CodeWriter formatAnnotation(Annotation a) {
CodeWriter code = new CodeWriter();
private void formatAnnotation(CodeWriter code, Annotation a) {
code.add('@');
code.add(classGen.useClass(a.getType()));
Map<String, Object> vl = a.getValues();
if (!vl.isEmpty()) {
code.add('(');
if (vl.size() == 1 && vl.containsKey("value")) {
code.add(encValueToString(vl.get("value")));
encodeValue(code, vl.get("value"));
} else {
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext(); ) {
Entry<String, Object> e = it.next();
code.add(e.getKey());
code.add(" = ");
code.add(encValueToString(e.getValue()));
encodeValue(code, e.getValue());
if (it.hasNext()) {
code.add(", ");
}
......@@ -93,7 +92,6 @@ public class AnnotationGen {
}
code.add(')');
}
return code;
}
@SuppressWarnings("unchecked")
......@@ -122,70 +120,52 @@ public class AnnotationGen {
}
// TODO: refactor this boilerplate code
@SuppressWarnings("unchecked")
public String encValueToString(Object val) {
public void encodeValue(CodeWriter code, Object val) {
if (val == null) {
return "null";
code.add("null");
return;
}
if (val instanceof String) {
return StringUtils.unescapeString((String) val);
}
if (val instanceof Integer) {
return TypeGen.formatInteger((Integer) val);
}
if (val instanceof Character) {
return StringUtils.unescapeChar((Character) val);
}
if (val instanceof Boolean) {
return Boolean.TRUE.equals(val) ? "true" : "false";
}
if (val instanceof Float) {
return TypeGen.formatFloat((Float) val);
}
if (val instanceof Double) {
return TypeGen.formatDouble((Double) val);
}
if (val instanceof Long) {
return TypeGen.formatLong((Long) val);
}
if (val instanceof Short) {
return TypeGen.formatShort((Short) val);
}
if (val instanceof Byte) {
return TypeGen.formatByte((Byte) val);
}
if (val instanceof ArgType) {
return TypeGen.translate(classGen, (ArgType) val) + ".class";
}
if (val instanceof FieldInfo) {
code.add(StringUtils.unescapeString((String) val));
} else if (val instanceof Integer) {
code.add(TypeGen.formatInteger((Integer) val));
} else if (val instanceof Character) {
code.add(StringUtils.unescapeChar((Character) val));
} else if (val instanceof Boolean) {
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
} else if (val instanceof Float) {
code.add(TypeGen.formatFloat((Float) val));
} else if (val instanceof Double) {
code.add(TypeGen.formatDouble((Double) val));
} else if (val instanceof Long) {
code.add(TypeGen.formatLong((Long) val));
} else if (val instanceof Short) {
code.add(TypeGen.formatShort((Short) val));
} else if (val instanceof Byte) {
code.add(TypeGen.formatByte((Byte) val));
} else if (val instanceof ArgType) {
code.add(TypeGen.translate(classGen, (ArgType) val)).add(".class");
} else if (val instanceof FieldInfo) {
// must be a static field
FieldInfo field = (FieldInfo) val;
// FIXME: !!code from InsnGen.sfield
String thisClass = cls.getFullName();
if (field.getDeclClass().getFullName().equals(thisClass)) {
return field.getName();
} else {
return classGen.useClass(field.getDeclClass()) + '.' + field.getName();
}
}
if (val instanceof List) {
StringBuilder str = new StringBuilder();
str.append('{');
List<Object> list = (List<Object>) val;
for (Iterator<Object> it = list.iterator(); it.hasNext(); ) {
code.add(InsnGen.makeStaticFieldAccess(field, classGen));
} else if (val instanceof List) {
code.add('{');
List list = (List) val;
Iterator it = list.iterator();
while (it.hasNext()) {
Object obj = it.next();
str.append(encValueToString(obj));
encodeValue(code, obj);
if (it.hasNext()) {
str.append(", ");
code.add(", ");
}
}
str.append('}');
return str.toString();
}
if (val instanceof Annotation) {
return formatAnnotation((Annotation) val).toString();
code.add('}');
} else if (val instanceof Annotation) {
formatAnnotation(code, (Annotation) val);
} else {
// TODO: also can be method values
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
}
// TODO: also can be method values
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
}
}
......@@ -151,7 +151,7 @@ public class ClassGen {
}
}
clsCode.attachAnnotation(cls);
clsCode.attachDefinition(cls);
}
public boolean makeGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
......@@ -228,8 +228,8 @@ public class ClassGen {
if (cls.getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
if (def != null) {
String v = annotationGen.encValueToString(def);
code.add(" default ").add(v);
code.add(" default ");
annotationGen.encodeValue(code, def);
}
}
code.add(';');
......@@ -282,11 +282,11 @@ public class ClassGen {
if (fv.getValue() == null) {
code.add(TypeGen.literalToString(0, f.getType()));
} else {
code.add(annotationGen.encValueToString(fv.getValue()));
annotationGen.encodeValue(code, fv.getValue());
}
}
code.add(';');
code.attachAnnotation(f);
code.attachDefinition(f);
}
return code;
}
......
package jadx.core.codegen;
import jadx.api.CodePosition;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.utils.Utils;
......@@ -7,6 +8,7 @@ import java.io.File;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.slf4j.Logger;
......@@ -17,7 +19,7 @@ public class CodeWriter {
private static final int MAX_FILENAME_LENGTH = 128;
public static final String NL = System.getProperty("line.separator");
public static final String INDENT = "\t";
public static final String INDENT = " ";
private static final String[] INDENT_CACHE = {
"",
......@@ -33,7 +35,8 @@ public class CodeWriter {
private int indent;
private int line = 1;
private Map<Object, Integer> annotations = Collections.emptyMap();
private int offset = 0;
private Map<CodePosition, Object> annotations = Collections.emptyMap();
public CodeWriter() {
this.indent = 0;
......@@ -47,56 +50,66 @@ public class CodeWriter {
public CodeWriter startLine() {
addLine();
buf.append(indentStr);
addIndent();
return this;
}
public CodeWriter startLine(char c) {
addLine();
buf.append(indentStr);
buf.append(c);
addIndent();
add(c);
return this;
}
public CodeWriter startLine(String str) {
addLine();
buf.append(indentStr);
buf.append(str);
addIndent();
add(str);
return this;
}
public CodeWriter startLine(int ind, String str) {
addLine();
buf.append(indentStr);
addIndent();
for (int i = 0; i < ind; i++) {
buf.append(INDENT);
addIndent();
}
buf.append(str);
add(str);
return this;
}
public CodeWriter add(Object obj) {
buf.append(obj);
add(obj.toString());
return this;
}
public CodeWriter add(String str) {
buf.append(str);
offset += str.length();
return this;
}
public CodeWriter add(char c) {
buf.append(c);
offset++;
return this;
}
@Deprecated
public CodeWriter add(CodeWriter code) {
line--;
for (Map.Entry<Object, Integer> entry : code.annotations.entrySet()) {
attachAnnotation(entry.getKey(), line + entry.getValue());
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
CodePosition pos = entry.getKey();
attachAnnotation(entry.getValue(), new CodePosition(line + pos.getLine(), pos.getOffset()));
}
line += code.line;
buf.append(code);
String str = code.toString();
buf.append(str);
if (str.contains(NL)) {
offset = code.offset;
} else {
offset += code.offset;
}
return this;
}
......@@ -108,25 +121,12 @@ public class CodeWriter {
private void addLine() {
buf.append(NL);
line++;
offset = 0;
}
public int getLine() {
return line;
}
public Object attachAnnotation(Object obj) {
return attachAnnotation(obj, line);
}
public Object attachAnnotation(Object obj, int line) {
if (annotations.isEmpty()) {
annotations = new HashMap<Object, Integer>();
}
return annotations.put(obj, line);
}
public CodeWriter indent() {
public CodeWriter addIndent() {
buf.append(indentStr);
offset += indentStr.length();
return this;
}
......@@ -169,16 +169,49 @@ public class CodeWriter {
updateIndent();
}
private static class DefinitionWrapper {
private final LineAttrNode node;
private DefinitionWrapper(LineAttrNode node) {
this.node = node;
}
public LineAttrNode getNode() {
return node;
}
}
public Object attachDefinition(LineAttrNode obj) {
return attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
}
public Object attachAnnotation(Object obj) {
return attachAnnotation(obj, new CodePosition(line, offset + 1));
}
private Object attachAnnotation(Object obj, CodePosition pos) {
if (annotations.isEmpty()) {
annotations = new HashMap<CodePosition, Object>();
}
return annotations.put(pos, obj);
}
public Map<CodePosition, Object> getAnnotations() {
return annotations;
}
public void finish() {
buf.trimToSize();
for (Map.Entry<Object, Integer> entry : annotations.entrySet()) {
Object v = entry.getKey();
if (v instanceof LineAttrNode) {
LineAttrNode l = (LineAttrNode) v;
l.setDecompiledLine(entry.getValue());
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<CodePosition, Object> entry = it.next();
Object v = entry.getValue();
if (v instanceof DefinitionWrapper) {
LineAttrNode l = ((DefinitionWrapper) v).getNode();
l.setDecompiledLine(entry.getKey().getLine());
it.remove();
}
}
annotations.clear();
}
private static String removeFirstEmptyLine(String str) {
......
......@@ -61,13 +61,13 @@ public class MethodGen {
public boolean addDefinition(CodeWriter code) {
if (mth.getMethodInfo().isClassInit()) {
code.startLine("static");
code.attachAnnotation(mth);
code.attachDefinition(mth);
return true;
}
if (mth.getAttributes().contains(AttributeFlag.ANONYMOUS_CONSTRUCTOR)) {
// don't add method name and arguments
code.startLine();
code.attachAnnotation(mth);
code.attachDefinition(mth);
return false;
}
annotationGen.addForMethod(code, mth);
......@@ -110,17 +110,15 @@ public class MethodGen {
));
}
}
code.add(makeArguments(args));
code.add(")");
addMethodArguments(code, args);
code.add(')');
annotationGen.addThrows(mth, code);
code.attachAnnotation(mth);
code.attachDefinition(mth);
return true;
}
public CodeWriter makeArguments(List<RegisterArg> args) {
CodeWriter argsCode = new CodeWriter();
private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
MethodParameters paramsAnnotation =
(MethodParameters) mth.getAttributes().get(AttributeType.ANNOTATION_MTH_PARAMETERS);
......@@ -154,7 +152,6 @@ public class MethodGen {
argsCode.add(", ");
}
}
return argsCode;
}
/**
......
......@@ -51,7 +51,7 @@ public class ArithNode extends InsnNode {
}
public ArithNode(ArithOp op, RegisterArg res, InsnArg a) {
super(InsnType.ARITH, 1);
super(InsnType.ARITH_ONEARG, 1);
this.op = op;
setResult(res);
addArg(a);
......
......@@ -54,6 +54,7 @@ public enum InsnType {
CONTINUE,
STR_CONCAT, // strings concatenation
ARITH_ONEARG,
TERNARY,
ARGS, // just generate arguments
......
......@@ -50,7 +50,10 @@ public class ClassNode extends LineAttrNode implements ILoadable {
private Map<Object, FieldNode> constFields = Collections.emptyMap();
private List<ClassNode> innerClasses = Collections.emptyList();
// store decompiled code
private CodeWriter code;
// store parent for inner classes or 'this' otherwise
private ClassNode parentClass;
public ClassNode(DexNode dex, ClassDef cls) throws DecodeException {
this.dex = dex;
......@@ -332,6 +335,19 @@ public class ClassNode extends LineAttrNode implements ILoadable {
return searchMethodByName(MethodInfo.fromDex(dex, id).getShortId());
}
public ClassNode getParentClass() {
if (parentClass == null) {
if (clsInfo.isInner()) {
ClassNode parent = dex().resolveClass(clsInfo.getParentClass());
parent = parent == null ? this : parent;
parentClass = parent;
} else {
parentClass = this;
}
}
return parentClass;
}
public List<ClassNode> getInnerClasses() {
return innerClasses;
}
......
......@@ -10,12 +10,14 @@ import com.android.dx.io.ClassData.Field;
public class FieldNode extends LineAttrNode {
private final ClassNode parent;
private final FieldInfo fieldInfo;
private final AccessInfo accFlags;
private ArgType type; // store signature
public FieldNode(ClassNode cls, Field field) {
this.parent = cls;
this.fieldInfo = FieldInfo.fromDex(cls.dex(), field.getFieldIndex());
this.type = fieldInfo.getType();
this.accFlags = new AccessInfo(field.getAccessFlags(), AFType.FIELD);
......@@ -41,6 +43,10 @@ public class FieldNode extends LineAttrNode {
this.type = type;
}
public ClassNode getParentClass() {
return parent;
}
@Override
public int hashCode() {
return fieldInfo.hashCode();
......
......@@ -159,7 +159,7 @@ public class ClassModifier extends AbstractVisitor {
&& af.isPublic()
&& mth.getArguments(false).isEmpty()) {
List<BlockNode> bb = mth.getBasicBlocks();
if (bb.isEmpty() || allBlocksEmpty(bb)) {
if (bb == null || bb.isEmpty() || allBlocksEmpty(bb)) {
mth.getAttributes().add(AttributeFlag.DONT_GENERATE);
}
}
......
package jadx.core.dex.visitors;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.JadxException;
import java.util.Iterator;
import java.util.List;
public class PrepareForCodeGen extends AbstractVisitor {
@Override
public void visit(MethodNode mth) throws JadxException {
List<BlockNode> blocks = mth.getBasicBlocks();
if (blocks == null) {
return;
}
for (BlockNode block : blocks) {
removeInstructions(block);
modifyArith(block);
}
}
private static void removeInstructions(BlockNode block) {
Iterator<InsnNode> it = block.getInstructions().iterator();
while (it.hasNext()) {
InsnNode insn = it.next();
switch (insn.getType()) {
case NOP:
case MONITOR_ENTER:
case MONITOR_EXIT:
it.remove();
break;
case CONSTRUCTOR:
ConstructorInsn co = (ConstructorInsn) insn;
if (co.isSelf()) {
it.remove();
}
break;
}
}
}
private static void modifyArith(BlockNode block) {
List<InsnNode> list = block.getInstructions();
for (int i = 0; i < list.size(); i++) {
InsnNode insn = list.get(i);
if (insn.getType() == InsnType.ARITH) {
ArithNode arith = (ArithNode) insn;
RegisterArg res = arith.getResult();
InsnArg arg = arith.getArg(0);
if (res.equals(arg)) {
ArithNode newArith = new ArithNode(arith.getOp(), res, arith.getArg(1));
list.set(i, newArith);
}
}
}
}
}
package jadx.tests.internal;
import jadx.api.InternalJadxTest;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import java.util.List;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;
......@@ -39,15 +31,9 @@ public class TestFieldIncrement extends InternalJadxTest {
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
MethodNode mth = getMethod(cls, "method");
List<InsnNode> insns = mth.getBasicBlocks().get(0).getInstructions();
assertEquals(insns.size(), 1);
InsnNode insnNode = insns.get(0);
assertEquals(InsnType.ARITH, insnNode.getType());
assertEquals(ArithOp.ADD, ((ArithNode) insnNode).getOp());
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("instanceField++;"));
assertThat(code, containsString("staticField--;"));
assertThat(code, containsString("result += s + '_';"));
......
......@@ -37,5 +37,7 @@ public class TestSynchronized extends InternalJadxTest {
assertThat(code, containsString("public synchronized boolean test1() {"));
assertThat(code, containsString("return this.f"));
assertThat(code, containsString("synchronized (this.o) {"));
assertThat(code, not(containsString(makeIndent(3) + ";")));
}
}
package jadx.tests.internal.arith;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;
public class TestArith extends InternalJadxTest {
public static class TestCls {
public void method(int a) {
a += 2;
}
public void method2(int a) {
a++;
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("a += 2;"));
assertThat(code, containsString("a++;"));
}
}
package jadx.tests.internal.arith;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestArith2 extends InternalJadxTest {
public static class TestCls {
public int test1(int a) {
return (a + 2) * 3;
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("return (a + 2) * 3;"));
assertThat(code, not(containsString("a + 2 * 3")));
}
}
package jadx.tests.internal;
package jadx.tests.internal.debuginfo;
import jadx.api.InternalJadxTest;
import jadx.core.codegen.CodeWriter;
......
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