diff --git a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java index 428a54825..ff52e7c59 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java +++ b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java @@ -17,7 +17,7 @@ public class CodeWriter { private static final int MAX_FILENAME_LENGTH = 128; public static final String NL = System.getProperty("line.separator"); - private static final String INDENT = "\t"; + public static final String INDENT = "\t"; private final StringBuilder buf = new StringBuilder(); private String indentStr; @@ -183,6 +183,10 @@ public class CodeWriter { } } + public boolean isEmpty() { + return buf.length() == 0; + } + public boolean notEmpty() { return buf.length() != 0; } @@ -227,4 +231,20 @@ public class CodeWriter { } } + @Override + public int hashCode() { + return buf.toString().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CodeWriter)) { + return false; + } + CodeWriter that = (CodeWriter) o; + return buf.toString().equals(that.buf.toString()); + } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java index 5d0d7270e..364222f64 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -77,38 +77,38 @@ public class InsnGen { return fallback; } - public String arg(InsnNode insn, int arg) throws CodegenException { + public CodeWriter arg(InsnNode insn, int arg) throws CodegenException { return arg(insn.getArg(arg)); } - public String arg(InsnArg arg) throws CodegenException { + public CodeWriter arg(InsnArg arg) throws CodegenException { return arg(arg, true); } - public String arg(InsnArg arg, boolean wrap) throws CodegenException { + public CodeWriter arg(InsnArg arg, boolean wrap) throws CodegenException { + CodeWriter code = new CodeWriter(); if (arg.isRegister()) { - return mgen.makeArgName((RegisterArg) arg); + code.add(mgen.makeArgName((RegisterArg) arg)); } else if (arg.isLiteral()) { - return lit((LiteralArg) arg); + code.add(lit((LiteralArg) arg)); } else if (arg.isInsnWrap()) { - CodeWriter code = new CodeWriter(); IGState flag = wrap ? IGState.BODY_ONLY : IGState.BODY_ONLY_NOWRAP; makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag); - return code.toString(); } else if (arg.isNamed()) { - return ((NamedArg) arg).getName(); + code.add(((NamedArg) arg).getName()); } else if (arg.isField()) { FieldArg f = (FieldArg) arg; if (f.isStatic()) { - return sfield(f.getField()); + code.add(sfield(f.getField())); } else { RegisterArg regArg = new RegisterArg(f.getRegNum()); regArg.replaceTypedVar(f); - return ifield(f.getField(), regArg); + code.add(ifield(f.getField(), regArg)); } } else { throw new CodegenException("Unknown arg type " + arg); } + return code; } public String assignVar(InsnNode insn) throws CodegenException { @@ -116,7 +116,7 @@ public class InsnGen { if (insn.getAttributes().contains(AttributeType.DECLARE_VARIABLE)) { return declareVar(arg); } else { - return arg(arg); + return arg(arg).toString(); } } @@ -130,7 +130,7 @@ public class InsnGen { private String ifield(FieldInfo field, InsnArg arg) throws CodegenException { FieldNode fieldNode = mth.getParentClass().searchField(field); - if(fieldNode != null && fieldNode.getAttributes().contains(AttributeFlag.DONT_GENERATE)) { + if (fieldNode != null && fieldNode.getAttributes().contains(AttributeFlag.DONT_GENERATE)) { return ""; } String name = field.getName(); @@ -149,7 +149,7 @@ public class InsnGen { return name; } } - String argStr = arg(arg); + CodeWriter argStr = arg(arg); return argStr.isEmpty() ? name : argStr + "." + name; } @@ -162,7 +162,7 @@ public class InsnGen { // Android specific resources class handler ClassInfo parentClass = declClass.getParentClass(); if (parentClass != null && parentClass.getShortName().equals("R")) { - return useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName(); + return useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName(); } return useClass(declClass) + '.' + field.getName(); } @@ -173,8 +173,9 @@ public class InsnGen { if (field.getDeclClass().getFullName().equals(thisClass)) { // if we generate this field - don't init if its final and used FieldNode fn = mth.getParentClass().searchField(field); - if (fn != null && fn.getAccessFlags().isFinal()) + if (fn != null && fn.getAccessFlags().isFinal()) { fn.getAttributes().remove(AttributeType.FIELD_VALUE); + } } } @@ -246,14 +247,16 @@ public class InsnGen { case CHECK_CAST: case CAST: { boolean wrap = state.contains(IGState.BODY_ONLY); - if (wrap) + if (wrap) { code.add("("); + } code.add("("); code.add(useType(((ArgType) ((IndexInsnNode) insn).getIndex()))); code.add(") "); code.add(arg(insn.getArg(0))); - if (wrap) + if (wrap) { code.add(")"); + } break; } case ARITH: @@ -270,10 +273,11 @@ public class InsnGen { break; case RETURN: - if (insn.getArgsCount() != 0) + if (insn.getArgsCount() != 0) { code.add("return ").add(arg(insn.getArg(0), false)); - else + } else { code.add("return"); + } break; case BREAK: @@ -318,8 +322,9 @@ public class InsnGen { ArgType arrayType = insn.getResult().getType(); int dim = arrayType.getArrayDimension(); code.add("new ").add(useType(arrayType.getArrayRootElement())).add('[').add(arg(insn, 0)).add(']'); - for (int i = 0; i < dim - 1; i++) + for (int i = 0; i < dim - 1; i++) { code.add("[]"); + } break; } @@ -460,8 +465,9 @@ public class InsnGen { code.add('{'); for (int i = 0; i < c; i++) { code.add(arg(insn, i)); - if (i + 1 < c) + if (i + 1 < c) { code.add(", "); + } } code.add('}'); } @@ -581,8 +587,8 @@ public class InsnGen { InsnArg arg = insn.getArg(0); // FIXME: add 'this' for equals methods in scope if (!arg.isThis()) { - String argStr = arg(arg); - if(!argStr.isEmpty()) { + CodeWriter argStr = arg(arg); + if (!argStr.isEmpty()) { code.add(argStr).add('.'); } } @@ -685,15 +691,15 @@ public class InsnGen { private void makeArith(ArithNode insn, CodeWriter code, EnumSet state) throws CodegenException { ArithOp op = insn.getOp(); - String v1 = arg(insn.getArg(0)); - String v2 = arg(insn.getArg(1)); + CodeWriter v1 = arg(insn.getArg(0)); + CodeWriter v2 = arg(insn.getArg(1)); if (state.contains(IGState.BODY_ONLY)) { // wrap insn in brackets for save correct operation order code.add('(').add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2).add(')'); } else if (state.contains(IGState.BODY_ONLY_NOWRAP)) { code.add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2); } else { - String res = arg(insn.getResult()); + CodeWriter res = arg(insn.getResult()); if (res.equals(v1) && insn.getResult().equals(insn.getArg(0))) { state.add(IGState.NO_RESULT); // "++" or "--" diff --git a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java index 047b644c1..e6f6b19cd 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java @@ -60,8 +60,9 @@ public class RegionGen extends InsnGen { if (tc != null) { makeTryCatch(cont, tc.getTryBlock(), code); } else { - for (IContainer c : r.getSubBlocks()) + for (IContainer c : r.getSubBlocks()) { makeRegion(code, c); + } } } else if (cont instanceof IfRegion) { code.startLine(); @@ -214,7 +215,7 @@ public class RegionGen extends InsnGen { op = op.invert(); } if (op == IfOp.EQ) { - return arg(firstArg, false); // == true + return arg(firstArg, false).toString(); // == true } else if (op == IfOp.NE) { return "!" + arg(firstArg); // != true } @@ -257,8 +258,7 @@ public class RegionGen extends InsnGen { code.startLine("case "); if (k instanceof IndexInsnNode) { code.add(sfield((FieldInfo) ((IndexInsnNode) k).getIndex())); - } - else { + } else { code.add(TypeGen.literalToString((Integer) k, arg.getType())); } code.add(':'); @@ -295,8 +295,9 @@ public class RegionGen extends InsnGen { if (!handler.isCatchAll()) { makeCatchBlock(code, handler); } else { - if (allHandler != null) + if (allHandler != null) { LOG.warn("Several 'all' handlers in try/catch block in " + mth); + } allHandler = handler; } } 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 0359f709e..263b5d41c 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 @@ -295,7 +295,10 @@ public class ClassNode extends LineAttrNode implements ILoadable { } public FieldNode searchField(FieldInfo field) { - String name = field.getName(); + return searchFieldByName(field.getName()); + } + + public FieldNode searchFieldByName(String name) { for (FieldNode f : fields) { if (f.getName().equals(name)) return f; diff --git a/jadx-core/src/test/java/jadx/api/InternalJadxTest.java b/jadx-core/src/test/java/jadx/api/InternalJadxTest.java index 1593da324..64e15f46f 100644 --- a/jadx-core/src/test/java/jadx/api/InternalJadxTest.java +++ b/jadx-core/src/test/java/jadx/api/InternalJadxTest.java @@ -25,6 +25,8 @@ import static junit.framework.Assert.fail; public abstract class InternalJadxTest { protected boolean outputCFG = false; + protected boolean deleteTmpJar = true; + protected String outDir = "test-out-tmp"; public ClassNode getClassNode(Class clazz) { @@ -40,7 +42,7 @@ public abstract class InternalJadxTest { String clsName = clazz.getName(); ClassNode cls = null; for (ClassNode aClass : classes) { - if(aClass.getFullName().equals(clsName)) { + if (aClass.getFullName().equals(clsName)) { cls = aClass; } } @@ -90,7 +92,11 @@ public abstract class InternalJadxTest { add(file, path + "/" + file.getName(), jo); } jo.close(); - temp.deleteOnExit(); + if (deleteTmpJar) { + temp.deleteOnExit(); + } else { + System.out.println("Temporary jar file path: " + temp.getAbsolutePath()); + } return temp; } @@ -145,4 +151,10 @@ public abstract class InternalJadxTest { protected void setOutputCFG() { this.outputCFG = true; } + + // Use only for debug purpose + @Deprecated + protected void notDeleteTmpJar() { + this.deleteTmpJar = false; + } } diff --git a/jadx-core/src/test/java/jadx/tests/internal/TestLineNumbers.java b/jadx-core/src/test/java/jadx/tests/internal/TestLineNumbers.java new file mode 100644 index 000000000..b29831ae4 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/internal/TestLineNumbers.java @@ -0,0 +1,79 @@ +package jadx.tests.internal; + +import jadx.api.InternalJadxTest; +import jadx.core.codegen.CodeWriter; +import jadx.core.dex.attributes.LineAttrNode; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class TestLineNumbers extends InternalJadxTest { + + public static class TestCls extends Exception { + int field; + + public void func() { + } + + public static class Inner { + int innerField; + + public void innerFunc() { + } + + public void innerFunc2() { + new Runnable() { + @Override + public void run() { + } + }.run(); + } + + public void innerFunc3() { + } + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + FieldNode field = cls.searchFieldByName("field"); + MethodNode func = cls.searchMethodByName("func()V"); + ClassNode inner = cls.getInnerClasses().get(0); + MethodNode innerFunc = inner.searchMethodByName("innerFunc()V"); + MethodNode innerFunc2 = inner.searchMethodByName("innerFunc2()V"); + MethodNode innerFunc3 = inner.searchMethodByName("innerFunc3()V"); + FieldNode innerField = inner.searchFieldByName("innerField"); + + // check source lines (available only for instructions and methods) + int testClassLine = 18; + assertEquals(testClassLine + 3, func.getSourceLine()); + assertEquals(testClassLine + 9, innerFunc.getSourceLine()); + assertEquals(testClassLine + 12, innerFunc2.getSourceLine()); + assertEquals(testClassLine + 20, innerFunc3.getSourceLine()); + + // check decompiled lines + String[] lines = code.split(CodeWriter.NL); + checkLine(lines, field, "int field;"); + checkLine(lines, func, "public void func() {"); + checkLine(lines, inner, "public static class Inner {"); + checkLine(lines, innerField, "int innerField;"); + checkLine(lines, innerFunc, "public void innerFunc() {"); + checkLine(lines, innerFunc2, "public void innerFunc2() {"); + checkLine(lines, innerFunc3, "public void innerFunc3() {"); + } + + private static void checkLine(String[] lines, LineAttrNode node, String str) { + int lineNumber = node.getDecompiledLine(); + String line = lines[lineNumber - 1]; + assertThat(line, containsString(str)); + } +}