diff --git a/src/main/java/jadx/Main.java b/src/main/java/jadx/Main.java index 0d0aab170..705b9e0a9 100644 --- a/src/main/java/jadx/Main.java +++ b/src/main/java/jadx/Main.java @@ -12,6 +12,7 @@ import jadx.dex.visitors.DotGraphVisitor; import jadx.dex.visitors.EnumVisitor; import jadx.dex.visitors.FallbackModeVisitor; import jadx.dex.visitors.IDexTreeVisitor; +import jadx.dex.visitors.MethodInlinerVisitor; import jadx.dex.visitors.ModVisitor; import jadx.dex.visitors.regions.CheckRegions; import jadx.dex.visitors.regions.CleanRegions; @@ -113,6 +114,7 @@ public class Main { if (args.isCFGOutput()) passes.add(new DotGraphVisitor(args.getOutDir(), true)); + passes.add(new MethodInlinerVisitor()); passes.add(new ClassModifier()); passes.add(new CleanRegions()); } diff --git a/src/main/java/jadx/codegen/ClassGen.java b/src/main/java/jadx/codegen/ClassGen.java index a03272b3e..2f99f0c63 100644 --- a/src/main/java/jadx/codegen/ClassGen.java +++ b/src/main/java/jadx/codegen/ClassGen.java @@ -194,6 +194,9 @@ public class ClassGen { CodeWriter code = new CodeWriter(clsCode.getIndent() + 1); for (Iterator it = mthList.iterator(); it.hasNext();) { MethodNode mth = it.next(); + if (mth.getAttributes().contains(AttributeFlag.DONT_GENERATE)) + continue; + try { if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) { MethodGen mthGen = new MethodGen(this, mth); diff --git a/src/main/java/jadx/codegen/InsnGen.java b/src/main/java/jadx/codegen/InsnGen.java index d3f7339a4..7f64109c8 100644 --- a/src/main/java/jadx/codegen/InsnGen.java +++ b/src/main/java/jadx/codegen/InsnGen.java @@ -1,6 +1,8 @@ package jadx.codegen; import jadx.dex.attributes.AttributeType; +import jadx.dex.attributes.IAttribute; +import jadx.dex.attributes.MethodInlineAttr; import jadx.dex.info.ClassInfo; import jadx.dex.info.FieldInfo; import jadx.dex.info.MethodInfo; @@ -27,7 +29,12 @@ import jadx.dex.nodes.RootNode; import jadx.utils.StringUtils; import jadx.utils.exceptions.CodegenException; +import java.util.ArrayList; import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -324,6 +331,10 @@ public class InsnGen { case TERNARY: break; + case ARGS: + code.add(arg(insn.getArg(0))); + break; + /* fallback mode instructions */ case NOP: state.add(InsnGenState.SKIP); @@ -462,6 +473,14 @@ public class InsnGen { private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException { MethodInfo callMth = insn.getCallMth(); + // inline method if METHOD_INLINE attribute is attached + MethodNode callMthNode = mth.dex().resolveMethod(callMth); + if (callMthNode != null + && callMthNode.getAttributes().contains(AttributeType.METHOD_INLINE)) { + inlineMethod(callMthNode, insn, code); + return; + } + int k = 0; InvokeType type = insn.getInvokeType(); switch (type) { @@ -488,6 +507,45 @@ public class InsnGen { addArgs(code, insn, k); } + private void inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException { + IAttribute mia = callMthNode.getAttributes().get(AttributeType.METHOD_INLINE); + InsnNode inl = ((MethodInlineAttr) mia).getInsn(); + if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) { + makeInsn(inl, code, true); + } else { + // remap args + InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()]; + List callArgs = callMthNode.getArguments(true); + for (int i = 0; i < callArgs.size(); i++) { + InsnArg arg = insn.getArg(i); + RegisterArg callArg = callArgs.get(i); + regs[callArg.getRegNum()] = arg; + } + // replace args + List inlArgs = new ArrayList(); + inl.getRegisterArgs(inlArgs); + Map toRevert = new HashMap(); + for (RegisterArg r : inlArgs) { + if (r.getRegNum() >= regs.length) { + LOG.warn("Unknown register number {} in method call: {}, {}", r, callMthNode, mth); + } else { + InsnArg repl = regs[r.getRegNum()]; + if (repl == null) { + LOG.warn("Not passed register {} in method call: {}, {}", r, callMthNode, mth); + } else { + inl.replaceArg(r, repl); + toRevert.put(r, repl); + } + } + } + makeInsn(inl, code, true); + // revert changes + for (Entry e : toRevert.entrySet()) { + inl.replaceArg(e.getValue(), e.getKey()); + } + } + } + private void addArgs(CodeWriter code, InsnNode insn, int k) throws CodegenException { code.add('('); for (int i = k; i < insn.getArgsCount(); i++) { @@ -495,7 +553,7 @@ public class InsnGen { if (i < insn.getArgsCount() - 1) code.add(", "); } - code.add(")"); + code.add(')'); } private void makeArith(ArithNode insn, CodeWriter code, EnumSet state) throws CodegenException { diff --git a/src/main/java/jadx/dex/attributes/AttributeType.java b/src/main/java/jadx/dex/attributes/AttributeType.java index 815aa4f8a..78c7a0570 100644 --- a/src/main/java/jadx/dex/attributes/AttributeType.java +++ b/src/main/java/jadx/dex/attributes/AttributeType.java @@ -21,6 +21,7 @@ public enum AttributeType { // methods JADX_ERROR(true), + METHOD_INLINE(true), // classes ENUM_CLASS(true), diff --git a/src/main/java/jadx/dex/attributes/MethodInlineAttr.java b/src/main/java/jadx/dex/attributes/MethodInlineAttr.java new file mode 100644 index 000000000..d91c95cf0 --- /dev/null +++ b/src/main/java/jadx/dex/attributes/MethodInlineAttr.java @@ -0,0 +1,26 @@ +package jadx.dex.attributes; + +import jadx.dex.nodes.InsnNode; + +public class MethodInlineAttr implements IAttribute { + + private final InsnNode insn; + + public MethodInlineAttr(InsnNode insn) { + this.insn = insn; + } + + public InsnNode getInsn() { + return insn; + } + + @Override + public AttributeType getType() { + return AttributeType.METHOD_INLINE; + } + + @Override + public String toString() { + return "INLINE: " + insn; + } +} diff --git a/src/main/java/jadx/dex/info/MethodInfo.java b/src/main/java/jadx/dex/info/MethodInfo.java index 1ff0746df..ee5ac61c6 100644 --- a/src/main/java/jadx/dex/info/MethodInfo.java +++ b/src/main/java/jadx/dex/info/MethodInfo.java @@ -76,6 +76,28 @@ public final class MethodInfo { return name.equals(""); } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + declClass.hashCode(); + result = prime * result + retType.hashCode(); + result = prime * result + shortId.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MethodInfo other = (MethodInfo) obj; + if (!shortId.equals(other.shortId)) return false; + if (!retType.equals(other.retType)) return false; + if (!declClass.equals(other.declClass)) return false; + return true; + } + @Override public String toString() { return retType + " " + declClass.getFullName() + "." + name diff --git a/src/main/java/jadx/dex/instructions/InsnType.java b/src/main/java/jadx/dex/instructions/InsnType.java index 074748f70..156984e82 100644 --- a/src/main/java/jadx/dex/instructions/InsnType.java +++ b/src/main/java/jadx/dex/instructions/InsnType.java @@ -50,6 +50,9 @@ public enum InsnType { CONSTRUCTOR, BREAK, CONTINUE, + TERNARY, + ARGS, // just generate arguments + NEW_MULTIDIM_ARRAY // TODO: now multidimensional arrays created using Array.newInstance function } diff --git a/src/main/java/jadx/dex/nodes/ClassNode.java b/src/main/java/jadx/dex/nodes/ClassNode.java index 636cb12e2..e6e81b4c2 100644 --- a/src/main/java/jadx/dex/nodes/ClassNode.java +++ b/src/main/java/jadx/dex/nodes/ClassNode.java @@ -239,7 +239,15 @@ public class ClassNode extends AttrNode implements ILoadable { return null; } - public MethodNode searchMethodById(String shortId) { + public MethodNode searchMethod(MethodInfo mth) { + for (MethodNode m : methods) { + if (m.getMethodInfo().equals(mth)) + return m; + } + return null; + } + + public MethodNode searchMethodByName(String shortId) { for (MethodNode m : methods) { if (m.getMethodInfo().getShortId().equals(shortId)) return m; @@ -248,7 +256,7 @@ public class ClassNode extends AttrNode implements ILoadable { } public MethodNode searchMethodById(int id) { - return searchMethodById(MethodInfo.fromDex(dex, id).getShortId()); + return searchMethodByName(MethodInfo.fromDex(dex, id).getShortId()); } public List getInnerClasses() { diff --git a/src/main/java/jadx/dex/nodes/DexNode.java b/src/main/java/jadx/dex/nodes/DexNode.java index ed84022b8..6f12520e6 100644 --- a/src/main/java/jadx/dex/nodes/DexNode.java +++ b/src/main/java/jadx/dex/nodes/DexNode.java @@ -1,6 +1,7 @@ package jadx.dex.nodes; import jadx.dex.info.ClassInfo; +import jadx.dex.info.MethodInfo; import jadx.dex.instructions.args.ArgType; import jadx.utils.exceptions.DecodeException; import jadx.utils.files.InputFile; @@ -51,6 +52,14 @@ public class DexNode { return root.resolveClass(clsInfo); } + public MethodNode resolveMethod(MethodInfo mth) { + ClassNode cls = resolveClass(mth.getDeclClass()); + if (cls != null) { + return cls.searchMethod(mth); + } + return null; + } + // DexBuffer wrappers public String getString(int index) { diff --git a/src/main/java/jadx/dex/visitors/MethodInlinerVisitor.java b/src/main/java/jadx/dex/visitors/MethodInlinerVisitor.java new file mode 100644 index 000000000..c9b7e90a5 --- /dev/null +++ b/src/main/java/jadx/dex/visitors/MethodInlinerVisitor.java @@ -0,0 +1,55 @@ +package jadx.dex.visitors; + +import jadx.dex.attributes.AttributeFlag; +import jadx.dex.attributes.IAttribute; +import jadx.dex.attributes.MethodInlineAttr; +import jadx.dex.instructions.InsnType; +import jadx.dex.nodes.BlockNode; +import jadx.dex.nodes.InsnNode; +import jadx.dex.nodes.MethodNode; +import jadx.utils.exceptions.JadxException; + +public class MethodInlinerVisitor extends AbstractVisitor { + + @Override + public void visit(MethodNode mth) throws JadxException { + if (mth.getAccessFlags().isSynthetic() && mth.getAccessFlags().isStatic()) { + if (mth.getBasicBlocks().size() == 1) { + BlockNode block = mth.getBasicBlocks().get(0); + // synthetic field getter + if (block.getInstructions().size() == 1) { + InsnNode insn = block.getInstructions().get(0); + if (insn.getType() == InsnType.RETURN) { + InsnNode inl = new InsnNode(InsnType.ARGS, 1); + inl.addArg(insn.getArg(0)); + addInlineAttr(mth, inl); + return; + } + } + + // synthetic field setter + if (block.getInstructions().size() == 2) { + if (block.getInstructions().get(1).getType() == InsnType.RETURN) { + InsnNode insn = block.getInstructions().get(0); + addInlineAttr(mth, insn); + return; + } + } + + // synthetic method invoke + if (block.getInstructions().size() == 1) { + InsnNode insn = block.getInstructions().get(0); + addInlineAttr(mth, insn); + return; + } + } + } + } + + private static void addInlineAttr(MethodNode mth, InsnNode insn) { + IAttribute attr = new MethodInlineAttr(insn); + mth.getAttributes().add(attr); + mth.getAttributes().add(AttributeFlag.DONT_GENERATE); + } + +} diff --git a/src/samples/java/jadx/samples/TestInner2.java b/src/samples/java/jadx/samples/TestInner2.java new file mode 100644 index 000000000..1edd2697e --- /dev/null +++ b/src/samples/java/jadx/samples/TestInner2.java @@ -0,0 +1,41 @@ +package jadx.samples; + +public class TestInner2 extends AbstractTest { + + private String a; + + public class A { + public A() { + a = "a"; + } + + public String a() { + return a; + } + } + + private static String b; + + public static class B { + public B() { + b = "b"; + } + + public String b() { + return b; + } + } + + @Override + public boolean testRun() throws Exception { + assertTrue((new A()).a().equals("a")); + assertTrue(a.equals("a")); + assertTrue((new B()).b().equals("b")); + assertTrue(b.equals("b")); + return true; + } + + public static void main(String[] args) throws Exception { + new TestInner2().testRun(); + } +}