diff --git a/jadx-core/src/main/java/jadx/api/CodePosition.java b/jadx-core/src/main/java/jadx/api/CodePosition.java new file mode 100644 index 000000000..86da18b6c --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/CodePosition.java @@ -0,0 +1,54 @@ +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 : ""); + } +} diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index 26e70133b..cbf2176b0 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -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 getCodeAnnotations() { + getCode(); + return cls.getCode().getAnnotations(); + } + + public CodePosition getDefinitionPosition(int line, int offset) { + Map 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 boolean equals(Object o) { + return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls); + } + + @Override + public int hashCode() { + return cls.hashCode(); + } + @Override public String toString() { return getFullName(); } - - public int getDecompiledLine() { - return cls.getDecompiledLine(); - } } diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 94d81f508..1d344b2d6 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -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; diff --git a/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java b/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java index 75002022c..572f2df37 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java @@ -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 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> it = vl.entrySet().iterator(); it.hasNext(); ) { Entry 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 list = (List) val; - for (Iterator 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(); + 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() + ")"); } - if (val instanceof Annotation) { - return formatAnnotation((Annotation) val).toString(); - } - // TODO: also can be method values - throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")"); } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index c5c40e7c9..7afbfc68b 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -151,7 +151,7 @@ public class ClassGen { } } - clsCode.attachAnnotation(cls); + clsCode.attachDefinition(cls); } public boolean makeGenericMap(CodeWriter code, Map> 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; } 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 590422197..e47d0f067 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java +++ b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java @@ -1,5 +1,6 @@ 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 annotations = Collections.emptyMap(); + private int offset = 0; + private Map 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 entry : code.annotations.entrySet()) { - attachAnnotation(entry.getKey(), line + entry.getValue()); + for (Map.Entry 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(); - } - 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(); + } + return annotations.put(pos, obj); + } + + public Map getAnnotations() { + return annotations; + } + public void finish() { buf.trimToSize(); - for (Map.Entry entry : annotations.entrySet()) { - Object v = entry.getKey(); - if (v instanceof LineAttrNode) { - LineAttrNode l = (LineAttrNode) v; - l.setDecompiledLine(entry.getValue()); + Iterator> it = annotations.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry 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) { 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 1c75b2398..580f5b8f1 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -16,6 +16,7 @@ import jadx.core.dex.instructions.FillArrayNode; import jadx.core.dex.instructions.GotoNode; import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IndexInsnNode; +import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.InvokeType; import jadx.core.dex.instructions.SwitchNode; @@ -38,6 +39,7 @@ import jadx.core.utils.InsnUtils; import jadx.core.utils.RegionUtils; import jadx.core.utils.StringUtils; import jadx.core.utils.exceptions.CodegenException; +import jadx.core.utils.exceptions.JadxRuntimeException; import java.util.ArrayList; import java.util.EnumSet; @@ -57,12 +59,7 @@ public class InsnGen { protected final RootNode root; private final boolean fallback; - private static enum IGState { - SKIP, - - NO_SEMICOLON, - NO_RESULT, - + private static enum Flags { BODY_ONLY, BODY_ONLY_NOWRAP, } @@ -113,7 +110,7 @@ public class InsnGen { } else if (arg.isLiteral()) { code.add(lit((LiteralArg) arg)); } else if (arg.isInsnWrap()) { - IGState flag = wrap ? IGState.BODY_ONLY : IGState.BODY_ONLY_NOWRAP; + Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP; makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag); } else if (arg.isNamed()) { code.add(((NamedArg) arg).getName()); @@ -164,18 +161,17 @@ public class InsnGen { code.add(field.getName()); } - protected String staticField(FieldInfo field) { - String thisClass = mth.getParentClass().getFullName(); + public static String makeStaticFieldAccess(FieldInfo field, ClassGen clsGen) { ClassInfo declClass = field.getDeclClass(); - if (thisClass.startsWith(declClass.getFullName())) { + if (clsGen.getClassNode().getFullName().startsWith(declClass.getFullName())) { return field.getName(); } // Android specific resources class handler ClassInfo parentClass = declClass.getParentClass(); if (parentClass != null && parentClass.getShortName().equals("R")) { - return useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName(); + return clsGen.useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName(); } - return useClass(declClass) + '.' + field.getName(); + return clsGen.useClass(declClass) + '.' + field.getName(); } private void fieldPut(IndexInsnNode insn) { @@ -190,6 +186,10 @@ public class InsnGen { } } + protected String staticField(FieldInfo field) { + return makeStaticFieldAccess(field, mgen.getClassGen()); + } + public String useClass(ClassInfo cls) { return mgen.getClassGen().useClass(cls); } @@ -202,31 +202,25 @@ public class InsnGen { return makeInsn(insn, code, null); } - private boolean makeInsn(InsnNode insn, CodeWriter code, IGState flag) throws CodegenException { + private boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException { try { - EnumSet state = EnumSet.noneOf(IGState.class); - if (flag == IGState.BODY_ONLY || flag == IGState.BODY_ONLY_NOWRAP) { + if (insn.getType() == InsnType.NOP) { + return false; + } + EnumSet state = EnumSet.noneOf(Flags.class); + if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) { state.add(flag); makeInsnBody(code, insn, state); } else { - CodeWriter body = new CodeWriter(code.getIndent()); - makeInsnBody(body, insn, state); - if (state.contains(IGState.SKIP)) { - return false; - } - code.startLine(); if (insn.getSourceLine() != 0) { code.attachAnnotation(insn.getSourceLine()); } - if (insn.getResult() != null && !state.contains(IGState.NO_RESULT)) { + if (insn.getResult() != null && insn.getType() != InsnType.ARITH_ONEARG) { code.add(assignVar(insn)).add(" = "); } - code.add(body); - - if (!state.contains(IGState.NO_SEMICOLON)) { - code.add(';'); - } + makeInsnBody(code, insn, state); + code.add(';'); } } catch (Throwable th) { throw new CodegenException(mth, "Error generate insn: " + insn, th); @@ -234,7 +228,7 @@ public class InsnGen { return true; } - private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet state) throws CodegenException { + private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet state) throws CodegenException { switch (insn.getType()) { case CONST_STR: String str = ((ConstStringNode) insn).getString(); @@ -257,7 +251,7 @@ public class InsnGen { case CHECK_CAST: case CAST: { - boolean wrap = state.contains(IGState.BODY_ONLY); + boolean wrap = state.contains(Flags.BODY_ONLY); if (wrap) { code.add('('); } @@ -270,13 +264,18 @@ public class InsnGen { } break; } + case ARITH: makeArith((ArithNode) insn, code, state); break; + case ARITH_ONEARG: + makeArithOneArg((ArithNode) insn, code, state); + break; + case NEG: String base = "-" + arg(insn.getArg(0)); - if (state.contains(IGState.BODY_ONLY)) { + if (state.contains(Flags.BODY_ONLY)) { code.add('(').add(base).add(')'); } else { code.add(base); @@ -311,7 +310,7 @@ public class InsnGen { break; case INSTANCE_OF: { - boolean wrap = state.contains(IGState.BODY_ONLY); + boolean wrap = state.contains(Flags.BODY_ONLY); if (wrap) { code.add('('); } @@ -400,7 +399,7 @@ public class InsnGen { } } // TODO: wrap in braces only if necessary - if (state.contains(IGState.BODY_ONLY)) { + if (state.contains(Flags.BODY_ONLY)) { code.add('(').add(sb.toString()).add(')'); } else { code.add(sb.toString()); @@ -410,16 +409,12 @@ public class InsnGen { case MONITOR_ENTER: if (isFallback()) { code.add("monitor-enter(").add(arg(insn.getArg(0))).add(')'); - } else { - state.add(IGState.SKIP); } break; case MONITOR_EXIT: if (isFallback()) { code.add("monitor-exit(").add(arg(insn, 0)).add(')'); - } else { - state.add(IGState.SKIP); } break; @@ -439,10 +434,6 @@ public class InsnGen { code.add(arg(insn, 0)); break; - case NOP: - state.add(IGState.SKIP); - break; - /* fallback mode instructions */ case IF: assert isFallback() : "if insn in not fallback mode"; @@ -472,7 +463,6 @@ public class InsnGen { code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';'); code.decIndent(); code.startLine('}'); - state.add(IGState.NO_SEMICOLON); break; case NEW_INSTANCE: @@ -556,7 +546,7 @@ public class InsnGen { code.add("new ").add(useType(elType)).add("[]{").add(str.toString()).add('}'); } - private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet state) + private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet state) throws CodegenException { ClassNode cls = mth.dex().resolveClass(insn.getClassType()); if (cls != null && cls.isAnonymous()) { @@ -579,9 +569,7 @@ public class InsnGen { return; } if (insn.isSelf()) { - // skip - state.add(IGState.SKIP); - return; + throw new JadxRuntimeException("Constructor 'self' invoke must be removed!"); } if (insn.isSuper()) { code.add("super"); @@ -632,6 +620,9 @@ public class InsnGen { } break; } + if (callMthNode != null) { + code.attachAnnotation(callMthNode); + } code.add(callMth.getName()); generateArguments(code, insn, k, callMthNode); } @@ -678,7 +669,7 @@ public class InsnGen { IAttribute mia = callMthNode.getAttributes().get(AttributeType.METHOD_INLINE); InsnNode inl = ((MethodInlineAttr) mia).getInsn(); if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) { - makeInsn(inl, code, IGState.BODY_ONLY); + makeInsn(inl, code, Flags.BODY_ONLY); } else { // remap args InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()]; @@ -705,7 +696,7 @@ public class InsnGen { } } } - makeInsn(inl, code, IGState.BODY_ONLY); + makeInsn(inl, code, Flags.BODY_ONLY); // revert changes for (Map.Entry e : toRevert.entrySet()) { inl.replaceArg(e.getValue(), e.getKey()); @@ -713,14 +704,14 @@ public class InsnGen { } } - private void makeTernary(TernaryInsn insn, CodeWriter code, EnumSet state) throws CodegenException { + private void makeTernary(TernaryInsn insn, CodeWriter code, EnumSet state) throws CodegenException { String cond = ConditionGen.make(this, insn.getCondition()); CodeWriter th = arg(insn.getArg(0), false); CodeWriter els = arg(insn.getArg(1), false); if (th.toString().equals("true") && els.toString().equals("false")) { code.add(cond); } else { - if (state.contains(IGState.BODY_ONLY)) { + if (state.contains(Flags.BODY_ONLY)) { code.add("((").add(cond).add(')').add(" ? ").add(th).add(" : ").add(els).add(')'); } else { code.add('(').add(cond).add(')').add(" ? ").add(th).add(" : ").add(els); @@ -728,33 +719,40 @@ public class InsnGen { } } - private void makeArith(ArithNode insn, CodeWriter code, EnumSet state) throws CodegenException { + private void makeArith(ArithNode insn, CodeWriter code, EnumSet state) throws CodegenException { ArithOp op = insn.getOp(); - CodeWriter v1 = arg(insn.getArg(0)); - CodeWriter v2 = arg(insn.getArg(1)); - if (state.contains(IGState.BODY_ONLY)) { + if (state.contains(Flags.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); + code.add('('); + addArg(code, insn.getArg(0)); + code.add(' '); + code.add(op.getSymbol()); + code.add(' '); + addArg(code, insn.getArg(1)); + code.add(')'); } else { - CodeWriter res = arg(insn.getResult()); - if (res.equals(v1) && insn.getResult().equals(insn.getArg(0))) { - state.add(IGState.NO_RESULT); - // "++" or "--" - if (insn.getArg(1).isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) { - LiteralArg lit = (LiteralArg) insn.getArg(1); - if (lit.isInteger() && lit.getLiteral() == 1) { - code.add(assignVar(insn)).add(op.getSymbol()).add(op.getSymbol()); - return; - } - } - // +=, -= ... - v2 = arg(insn.getArg(1), false); - code.add(assignVar(insn)).add(' ').add(op.getSymbol()).add("= ").add(v2); - } else { - code.add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2); - } + addArg(code, insn.getArg(0)); + code.add(' '); + code.add(op.getSymbol()); + code.add(' '); + addArg(code, insn.getArg(1)); } } + + private void makeArithOneArg(ArithNode insn, CodeWriter code, EnumSet state) throws CodegenException { + ArithOp op = insn.getOp(); + InsnArg arg = insn.getArg(0); + // "++" or "--" + if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) { + LiteralArg lit = (LiteralArg) arg; + if (lit.isInteger() && lit.getLiteral() == 1) { + String opSymbol = op.getSymbol(); + code.add(assignVar(insn)).add(opSymbol).add(opSymbol); + return; + } + } + // +=, -= ... + code.add(assignVar(insn)).add(' ').add(op.getSymbol()).add("= "); + addArg(code, arg, false); + } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index fdae5959e..e1f7926d1 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -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 args) { - CodeWriter argsCode = new CodeWriter(); - + private void addMethodArguments(CodeWriter argsCode, List args) { MethodParameters paramsAnnotation = (MethodParameters) mth.getAttributes().get(AttributeType.ANNOTATION_MTH_PARAMETERS); @@ -154,7 +152,6 @@ public class MethodGen { argsCode.add(", "); } } - return argsCode; } /** diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java index e24af8762..2a7bb2fb9 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java index 4a5b60432..79e11913b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java @@ -54,6 +54,7 @@ public enum InsnType { CONTINUE, STR_CONCAT, // strings concatenation + ARITH_ONEARG, TERNARY, ARGS, // just generate arguments 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 62faea8a4..ff154635f 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 @@ -50,7 +50,10 @@ public class ClassNode extends LineAttrNode implements ILoadable { private Map constFields = Collections.emptyMap(); private List 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 getInnerClasses() { return innerClasses; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java index 5580d7a6e..72f8c231c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java @@ -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(); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java index b42d7e780..f93386a5b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java @@ -159,7 +159,7 @@ public class ClassModifier extends AbstractVisitor { && af.isPublic() && mth.getArguments(false).isEmpty()) { List bb = mth.getBasicBlocks(); - if (bb.isEmpty() || allBlocksEmpty(bb)) { + if (bb == null || bb.isEmpty() || allBlocksEmpty(bb)) { mth.getAttributes().add(AttributeFlag.DONT_GENERATE); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java b/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java new file mode 100644 index 000000000..12fadd159 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java @@ -0,0 +1,66 @@ +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 blocks = mth.getBasicBlocks(); + if (blocks == null) { + return; + } + for (BlockNode block : blocks) { + removeInstructions(block); + modifyArith(block); + } + } + + private static void removeInstructions(BlockNode block) { + Iterator 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 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); + } + } + } + } +} diff --git a/jadx-core/src/test/java/jadx/tests/internal/TestFieldIncrement.java b/jadx-core/src/test/java/jadx/tests/internal/TestFieldIncrement.java index 2ed1ead4f..b4806970b 100644 --- a/jadx-core/src/test/java/jadx/tests/internal/TestFieldIncrement.java +++ b/jadx-core/src/test/java/jadx/tests/internal/TestFieldIncrement.java @@ -1,18 +1,10 @@ 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 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 + '_';")); diff --git a/jadx-core/src/test/java/jadx/tests/internal/TestSynchronized.java b/jadx-core/src/test/java/jadx/tests/internal/TestSynchronized.java index 0046475a9..cabafee70 100644 --- a/jadx-core/src/test/java/jadx/tests/internal/TestSynchronized.java +++ b/jadx-core/src/test/java/jadx/tests/internal/TestSynchronized.java @@ -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) + ";"))); } } diff --git a/jadx-core/src/test/java/jadx/tests/internal/arith/TestArith.java b/jadx-core/src/test/java/jadx/tests/internal/arith/TestArith.java new file mode 100644 index 000000000..44d8430b1 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/internal/arith/TestArith.java @@ -0,0 +1,33 @@ +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++;")); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/internal/arith/TestArith2.java b/jadx-core/src/test/java/jadx/tests/internal/arith/TestArith2.java new file mode 100644 index 000000000..ec159a6ce --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/internal/arith/TestArith2.java @@ -0,0 +1,30 @@ +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"))); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/internal/TestLineNumbers.java b/jadx-core/src/test/java/jadx/tests/internal/debuginfo/TestLineNumbers.java similarity index 98% rename from jadx-core/src/test/java/jadx/tests/internal/TestLineNumbers.java rename to jadx-core/src/test/java/jadx/tests/internal/debuginfo/TestLineNumbers.java index e97144412..8eb3e61b3 100644 --- a/jadx-core/src/test/java/jadx/tests/internal/TestLineNumbers.java +++ b/jadx-core/src/test/java/jadx/tests/internal/debuginfo/TestLineNumbers.java @@ -1,4 +1,4 @@ -package jadx.tests.internal; +package jadx.tests.internal.debuginfo; import jadx.api.InternalJadxTest; import jadx.core.codegen.CodeWriter;