core: fix decompiled lines info
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IGState> 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 "--"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user