Commit 917cf20d authored by Skylot's avatar Skylot

core: fix decompiled lines info

parent dabaeed8
...@@ -17,7 +17,7 @@ public class CodeWriter { ...@@ -17,7 +17,7 @@ public class CodeWriter {
private static final int MAX_FILENAME_LENGTH = 128; private static final int MAX_FILENAME_LENGTH = 128;
public static final String NL = System.getProperty("line.separator"); 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 final StringBuilder buf = new StringBuilder();
private String indentStr; private String indentStr;
...@@ -183,6 +183,10 @@ public class CodeWriter { ...@@ -183,6 +183,10 @@ public class CodeWriter {
} }
} }
public boolean isEmpty() {
return buf.length() == 0;
}
public boolean notEmpty() { public boolean notEmpty() {
return buf.length() != 0; return buf.length() != 0;
} }
...@@ -227,4 +231,20 @@ public class CodeWriter { ...@@ -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());
}
} }
...@@ -77,38 +77,38 @@ public class InsnGen { ...@@ -77,38 +77,38 @@ public class InsnGen {
return fallback; 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)); return arg(insn.getArg(arg));
} }
public String arg(InsnArg arg) throws CodegenException { public CodeWriter arg(InsnArg arg) throws CodegenException {
return arg(arg, true); 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()) { if (arg.isRegister()) {
return mgen.makeArgName((RegisterArg) arg); code.add(mgen.makeArgName((RegisterArg) arg));
} else if (arg.isLiteral()) { } else if (arg.isLiteral()) {
return lit((LiteralArg) arg); code.add(lit((LiteralArg) arg));
} else if (arg.isInsnWrap()) { } else if (arg.isInsnWrap()) {
CodeWriter code = new CodeWriter();
IGState flag = wrap ? IGState.BODY_ONLY : IGState.BODY_ONLY_NOWRAP; IGState flag = wrap ? IGState.BODY_ONLY : IGState.BODY_ONLY_NOWRAP;
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag); makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
return code.toString();
} else if (arg.isNamed()) { } else if (arg.isNamed()) {
return ((NamedArg) arg).getName(); code.add(((NamedArg) arg).getName());
} else if (arg.isField()) { } else if (arg.isField()) {
FieldArg f = (FieldArg) arg; FieldArg f = (FieldArg) arg;
if (f.isStatic()) { if (f.isStatic()) {
return sfield(f.getField()); code.add(sfield(f.getField()));
} else { } else {
RegisterArg regArg = new RegisterArg(f.getRegNum()); RegisterArg regArg = new RegisterArg(f.getRegNum());
regArg.replaceTypedVar(f); regArg.replaceTypedVar(f);
return ifield(f.getField(), regArg); code.add(ifield(f.getField(), regArg));
} }
} else { } else {
throw new CodegenException("Unknown arg type " + arg); throw new CodegenException("Unknown arg type " + arg);
} }
return code;
} }
public String assignVar(InsnNode insn) throws CodegenException { public String assignVar(InsnNode insn) throws CodegenException {
...@@ -116,7 +116,7 @@ public class InsnGen { ...@@ -116,7 +116,7 @@ public class InsnGen {
if (insn.getAttributes().contains(AttributeType.DECLARE_VARIABLE)) { if (insn.getAttributes().contains(AttributeType.DECLARE_VARIABLE)) {
return declareVar(arg); return declareVar(arg);
} else { } else {
return arg(arg); return arg(arg).toString();
} }
} }
...@@ -130,7 +130,7 @@ public class InsnGen { ...@@ -130,7 +130,7 @@ public class InsnGen {
private String ifield(FieldInfo field, InsnArg arg) throws CodegenException { private String ifield(FieldInfo field, InsnArg arg) throws CodegenException {
FieldNode fieldNode = mth.getParentClass().searchField(field); 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 ""; return "";
} }
String name = field.getName(); String name = field.getName();
...@@ -149,7 +149,7 @@ public class InsnGen { ...@@ -149,7 +149,7 @@ public class InsnGen {
return name; return name;
} }
} }
String argStr = arg(arg); CodeWriter argStr = arg(arg);
return argStr.isEmpty() ? name : argStr + "." + name; return argStr.isEmpty() ? name : argStr + "." + name;
} }
...@@ -162,7 +162,7 @@ public class InsnGen { ...@@ -162,7 +162,7 @@ public class InsnGen {
// Android specific resources class handler // Android specific resources class handler
ClassInfo parentClass = declClass.getParentClass(); ClassInfo parentClass = declClass.getParentClass();
if (parentClass != null && parentClass.getShortName().equals("R")) { 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(); return useClass(declClass) + '.' + field.getName();
} }
...@@ -173,8 +173,9 @@ public class InsnGen { ...@@ -173,8 +173,9 @@ public class InsnGen {
if (field.getDeclClass().getFullName().equals(thisClass)) { if (field.getDeclClass().getFullName().equals(thisClass)) {
// if we generate this field - don't init if its final and used // if we generate this field - don't init if its final and used
FieldNode fn = mth.getParentClass().searchField(field); FieldNode fn = mth.getParentClass().searchField(field);
if (fn != null && fn.getAccessFlags().isFinal()) if (fn != null && fn.getAccessFlags().isFinal()) {
fn.getAttributes().remove(AttributeType.FIELD_VALUE); fn.getAttributes().remove(AttributeType.FIELD_VALUE);
}
} }
} }
...@@ -246,14 +247,16 @@ public class InsnGen { ...@@ -246,14 +247,16 @@ public class InsnGen {
case CHECK_CAST: case CHECK_CAST:
case CAST: { case CAST: {
boolean wrap = state.contains(IGState.BODY_ONLY); boolean wrap = state.contains(IGState.BODY_ONLY);
if (wrap) if (wrap) {
code.add("("); code.add("(");
}
code.add("("); code.add("(");
code.add(useType(((ArgType) ((IndexInsnNode) insn).getIndex()))); code.add(useType(((ArgType) ((IndexInsnNode) insn).getIndex())));
code.add(") "); code.add(") ");
code.add(arg(insn.getArg(0))); code.add(arg(insn.getArg(0)));
if (wrap) if (wrap) {
code.add(")"); code.add(")");
}
break; break;
} }
case ARITH: case ARITH:
...@@ -270,10 +273,11 @@ public class InsnGen { ...@@ -270,10 +273,11 @@ public class InsnGen {
break; break;
case RETURN: case RETURN:
if (insn.getArgsCount() != 0) if (insn.getArgsCount() != 0) {
code.add("return ").add(arg(insn.getArg(0), false)); code.add("return ").add(arg(insn.getArg(0), false));
else } else {
code.add("return"); code.add("return");
}
break; break;
case BREAK: case BREAK:
...@@ -318,8 +322,9 @@ public class InsnGen { ...@@ -318,8 +322,9 @@ public class InsnGen {
ArgType arrayType = insn.getResult().getType(); ArgType arrayType = insn.getResult().getType();
int dim = arrayType.getArrayDimension(); int dim = arrayType.getArrayDimension();
code.add("new ").add(useType(arrayType.getArrayRootElement())).add('[').add(arg(insn, 0)).add(']'); 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("[]"); code.add("[]");
}
break; break;
} }
...@@ -460,8 +465,9 @@ public class InsnGen { ...@@ -460,8 +465,9 @@ public class InsnGen {
code.add('{'); code.add('{');
for (int i = 0; i < c; i++) { for (int i = 0; i < c; i++) {
code.add(arg(insn, i)); code.add(arg(insn, i));
if (i + 1 < c) if (i + 1 < c) {
code.add(", "); code.add(", ");
}
} }
code.add('}'); code.add('}');
} }
...@@ -581,8 +587,8 @@ public class InsnGen { ...@@ -581,8 +587,8 @@ public class InsnGen {
InsnArg arg = insn.getArg(0); InsnArg arg = insn.getArg(0);
// FIXME: add 'this' for equals methods in scope // FIXME: add 'this' for equals methods in scope
if (!arg.isThis()) { if (!arg.isThis()) {
String argStr = arg(arg); CodeWriter argStr = arg(arg);
if(!argStr.isEmpty()) { if (!argStr.isEmpty()) {
code.add(argStr).add('.'); code.add(argStr).add('.');
} }
} }
...@@ -685,15 +691,15 @@ public class InsnGen { ...@@ -685,15 +691,15 @@ public class InsnGen {
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<IGState> state) throws CodegenException { private void makeArith(ArithNode insn, CodeWriter code, EnumSet<IGState> state) throws CodegenException {
ArithOp op = insn.getOp(); ArithOp op = insn.getOp();
String v1 = arg(insn.getArg(0)); CodeWriter v1 = arg(insn.getArg(0));
String v2 = arg(insn.getArg(1)); CodeWriter v2 = arg(insn.getArg(1));
if (state.contains(IGState.BODY_ONLY)) { if (state.contains(IGState.BODY_ONLY)) {
// wrap insn in brackets for save correct operation order // wrap insn in brackets for save correct operation order
code.add('(').add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2).add(')'); code.add('(').add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2).add(')');
} else if (state.contains(IGState.BODY_ONLY_NOWRAP)) { } else if (state.contains(IGState.BODY_ONLY_NOWRAP)) {
code.add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2); code.add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2);
} else { } else {
String res = arg(insn.getResult()); CodeWriter res = arg(insn.getResult());
if (res.equals(v1) && insn.getResult().equals(insn.getArg(0))) { if (res.equals(v1) && insn.getResult().equals(insn.getArg(0))) {
state.add(IGState.NO_RESULT); state.add(IGState.NO_RESULT);
// "++" or "--" // "++" or "--"
......
...@@ -60,8 +60,9 @@ public class RegionGen extends InsnGen { ...@@ -60,8 +60,9 @@ public class RegionGen extends InsnGen {
if (tc != null) { if (tc != null) {
makeTryCatch(cont, tc.getTryBlock(), code); makeTryCatch(cont, tc.getTryBlock(), code);
} else { } else {
for (IContainer c : r.getSubBlocks()) for (IContainer c : r.getSubBlocks()) {
makeRegion(code, c); makeRegion(code, c);
}
} }
} else if (cont instanceof IfRegion) { } else if (cont instanceof IfRegion) {
code.startLine(); code.startLine();
...@@ -214,7 +215,7 @@ public class RegionGen extends InsnGen { ...@@ -214,7 +215,7 @@ public class RegionGen extends InsnGen {
op = op.invert(); op = op.invert();
} }
if (op == IfOp.EQ) { if (op == IfOp.EQ) {
return arg(firstArg, false); // == true return arg(firstArg, false).toString(); // == true
} else if (op == IfOp.NE) { } else if (op == IfOp.NE) {
return "!" + arg(firstArg); // != true return "!" + arg(firstArg); // != true
} }
...@@ -257,8 +258,7 @@ public class RegionGen extends InsnGen { ...@@ -257,8 +258,7 @@ public class RegionGen extends InsnGen {
code.startLine("case "); code.startLine("case ");
if (k instanceof IndexInsnNode) { if (k instanceof IndexInsnNode) {
code.add(sfield((FieldInfo) ((IndexInsnNode) k).getIndex())); code.add(sfield((FieldInfo) ((IndexInsnNode) k).getIndex()));
} } else {
else {
code.add(TypeGen.literalToString((Integer) k, arg.getType())); code.add(TypeGen.literalToString((Integer) k, arg.getType()));
} }
code.add(':'); code.add(':');
...@@ -295,8 +295,9 @@ public class RegionGen extends InsnGen { ...@@ -295,8 +295,9 @@ public class RegionGen extends InsnGen {
if (!handler.isCatchAll()) { if (!handler.isCatchAll()) {
makeCatchBlock(code, handler); makeCatchBlock(code, handler);
} else { } else {
if (allHandler != null) if (allHandler != null) {
LOG.warn("Several 'all' handlers in try/catch block in " + mth); LOG.warn("Several 'all' handlers in try/catch block in " + mth);
}
allHandler = handler; allHandler = handler;
} }
} }
......
...@@ -295,7 +295,10 @@ public class ClassNode extends LineAttrNode implements ILoadable { ...@@ -295,7 +295,10 @@ public class ClassNode extends LineAttrNode implements ILoadable {
} }
public FieldNode searchField(FieldInfo field) { public FieldNode searchField(FieldInfo field) {
String name = field.getName(); return searchFieldByName(field.getName());
}
public FieldNode searchFieldByName(String name) {
for (FieldNode f : fields) { for (FieldNode f : fields) {
if (f.getName().equals(name)) if (f.getName().equals(name))
return f; return f;
......
...@@ -25,6 +25,8 @@ import static junit.framework.Assert.fail; ...@@ -25,6 +25,8 @@ import static junit.framework.Assert.fail;
public abstract class InternalJadxTest { public abstract class InternalJadxTest {
protected boolean outputCFG = false; protected boolean outputCFG = false;
protected boolean deleteTmpJar = true;
protected String outDir = "test-out-tmp"; protected String outDir = "test-out-tmp";
public ClassNode getClassNode(Class<?> clazz) { public ClassNode getClassNode(Class<?> clazz) {
...@@ -40,7 +42,7 @@ public abstract class InternalJadxTest { ...@@ -40,7 +42,7 @@ public abstract class InternalJadxTest {
String clsName = clazz.getName(); String clsName = clazz.getName();
ClassNode cls = null; ClassNode cls = null;
for (ClassNode aClass : classes) { for (ClassNode aClass : classes) {
if(aClass.getFullName().equals(clsName)) { if (aClass.getFullName().equals(clsName)) {
cls = aClass; cls = aClass;
} }
} }
...@@ -90,7 +92,11 @@ public abstract class InternalJadxTest { ...@@ -90,7 +92,11 @@ public abstract class InternalJadxTest {
add(file, path + "/" + file.getName(), jo); add(file, path + "/" + file.getName(), jo);
} }
jo.close(); jo.close();
temp.deleteOnExit(); if (deleteTmpJar) {
temp.deleteOnExit();
} else {
System.out.println("Temporary jar file path: " + temp.getAbsolutePath());
}
return temp; return temp;
} }
...@@ -145,4 +151,10 @@ public abstract class InternalJadxTest { ...@@ -145,4 +151,10 @@ public abstract class InternalJadxTest {
protected void setOutputCFG() { protected void setOutputCFG() {
this.outputCFG = true; this.outputCFG = true;
} }
// Use only for debug purpose
@Deprecated
protected void notDeleteTmpJar() {
this.deleteTmpJar = false;
}
} }
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));
}
}
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