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 ceb62aa5c..d870cbc52 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -215,11 +215,15 @@ public class InsnGen { case CHECK_CAST: case CAST: - code.add("(("); + boolean wrap = state.contains(IGState.BODY_ONLY); + if (wrap) + code.add("("); + code.add("("); code.add(useType(((ArgType) ((IndexInsnNode) insn).getIndex()))); - code.add(") ("); - code.add(arg(insn.getArg(0), false)); - code.add("))"); + code.add(") "); + code.add(arg(insn.getArg(0))); + if (wrap) + code.add(")"); break; case ARITH: @@ -564,6 +568,19 @@ public class InsnGen { } } + private void addArgs(CodeWriter code, InsnNode insn, int k) throws CodegenException { + int argsCount = insn.getArgsCount(); + code.add('('); + if (k < argsCount) { + code.add(arg(insn.getArg(k), false)); + for (int i = k + 1; i < argsCount; i++) { + code.add(", "); + code.add(arg(insn.getArg(i), false)); + } + } + code.add(')'); + } + private void inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException { IAttribute mia = callMthNode.getAttributes().get(AttributeType.METHOD_INLINE); InsnNode inl = ((MethodInlineAttr) mia).getInsn(); @@ -603,19 +620,6 @@ public class InsnGen { } } - private void addArgs(CodeWriter code, InsnNode insn, int k) throws CodegenException { - int argsCount = insn.getArgsCount(); - code.add('('); - if (k < argsCount) { - code.add(arg(insn.getArg(k), false)); - for (int i = k + 1; i < argsCount; i++) { - code.add(", "); - code.add(arg(insn.getArg(i), false)); - } - } - code.add(')'); - } - private void makeArith(ArithNode insn, CodeWriter code, EnumSet state) throws CodegenException { ArithOp op = insn.getOp(); String v1 = arg(insn.getArg(0)); diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java index bc5e9ce6d..5dfa4bdee 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java @@ -384,6 +384,17 @@ public abstract class ArgType { return null; } + public static boolean isCastNeeded(ArgType from, ArgType to) { + if (from.equals(to)) { + return false; + } + if (from.isObject() && to.isObject() + && clsp.isImplements(from.getObject(), to.getObject())) { + return false; + } + return true; + } + public static ArgType parse(String type) { char f = type.charAt(0); switch (f) { 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 469c9db51..165f4a15b 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 @@ -64,9 +64,7 @@ public class ClassNode extends LineAttrNode implements ILoadable { this.interfaces.add(ClassInfo.fromDex(dex, interfaceIdx)); } - if (cls.getClassDataOffset() == 0) { - // nothing to loadFile - } else { + if (cls.getClassDataOffset() != 0) { ClassData clsData = dex.readClassData(cls); for (Method mth : clsData.getDirectMethods()) diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/CodeShrinker.java b/jadx-core/src/main/java/jadx/core/dex/visitors/CodeShrinker.java index ab159bd56..ff65aad8b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/CodeShrinker.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/CodeShrinker.java @@ -10,6 +10,7 @@ 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.args.ArgType; import jadx.core.dex.instructions.args.FieldArg; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnWrapArg; @@ -234,6 +235,17 @@ public class CodeShrinker extends AbstractVisitor { } break; + case CHECK_CAST: + InsnArg castArg = insn.getArg(0); + ArgType castType = (ArgType) ((IndexInsnNode)insn).getIndex(); + if (!ArgType.isCastNeeded(castArg.getType(), castType)) { + InsnNode insnNode = new InsnNode(InsnType.MOVE, 1); + insnNode.setResult(insn.getResult()); + insnNode.addArg(castArg); + return insnNode; + } + break; + default: break; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeresolver/TypeResolver.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeresolver/TypeResolver.java index 1c162717f..c3ba1fe39 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeresolver/TypeResolver.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeresolver/TypeResolver.java @@ -1,6 +1,8 @@ package jadx.core.dex.visitors.typeresolver; import jadx.core.dex.attributes.BlockRegState; +import jadx.core.dex.instructions.IndexInsnNode; +import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.nodes.BlockNode; @@ -17,6 +19,8 @@ public class TypeResolver extends AbstractVisitor { if (mth.isNoCode()) return; + prepare(mth); + visitBlocks(mth); visitEdges(mth); @@ -27,6 +31,22 @@ public class TypeResolver extends AbstractVisitor { } } + /** + * Check argument types (can be broken after merging debug info) + */ + private void prepare(MethodNode mth) { + for (BlockNode block : mth.getBasicBlocks()) { + for (InsnNode insn : block.getInstructions()) { + switch (insn.getType()) { + case CHECK_CAST: + ArgType castType = (ArgType) ((IndexInsnNode) insn).getIndex(); + insn.getResult().getTypedVar().forceSetType(castType); + break; + } + } + } + } + private void visitBlocks(MethodNode mth) { for (BlockNode block : mth.getBasicBlocks()) { BlockRegState state = new BlockRegState(mth); diff --git a/jadx-core/src/test/java/jadx/api/TestDuplicateCast.java b/jadx-core/src/test/java/jadx/api/TestDuplicateCast.java new file mode 100644 index 000000000..5e34606b0 --- /dev/null +++ b/jadx-core/src/test/java/jadx/api/TestDuplicateCast.java @@ -0,0 +1,44 @@ +package jadx.api; + +import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.args.InsnWrapArg; +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 org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class TestDuplicateCast extends InternalJadxTest { + + public static class TestCls { + public int[] method(Object o) { + return (int[]) o; + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + MethodNode mth = getMethod(cls, "method"); + + List insns = mth.getBasicBlocks().get(1).getInstructions(); + assertEquals(insns.size(), 1); + InsnNode insnNode = insns.get(0); + assertEquals(InsnType.RETURN, insnNode.getType()); + assertTrue(insnNode.getArg(0).isInsnWrap()); + InsnNode wrapInsn = ((InsnWrapArg) insnNode.getArg(0)).getWrapInsn(); + assertEquals(InsnType.CHECK_CAST, wrapInsn.getType()); + assertFalse(wrapInsn.getArg(0).isInsnWrap()); + + String code = cls.getCode().toString(); + assertThat(code, containsString("return (int[]) o;")); + } +} diff --git a/jadx-core/src/test/java/jadx/api/TestStringBuilderElimination.java b/jadx-core/src/test/java/jadx/api/TestStringBuilderElimination.java new file mode 100644 index 000000000..730b71e72 --- /dev/null +++ b/jadx-core/src/test/java/jadx/api/TestStringBuilderElimination.java @@ -0,0 +1,31 @@ +package jadx.api; + +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 TestStringBuilderElimination extends InternalJadxTest { + + public static class MyException extends Exception { + public MyException(String str, Exception e) { +// super("msg:" + str, e); + } + + public void method(int k) { + System.out.println("k=" + k); + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(MyException.class); + String code = cls.getCode().toString(); + + assertThat(code, not(containsString("new StringBuilder"))); + assertThat(code, containsString("System.out.println(\"k=\" + k);")); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/JadxClasspathTest.java b/jadx-core/src/test/java/jadx/tests/JadxClasspathTest.java new file mode 100644 index 000000000..0392472d2 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/JadxClasspathTest.java @@ -0,0 +1,41 @@ +package jadx.tests; + +import jadx.core.clsp.ClspGraph; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.utils.exceptions.DecodeException; + +import java.io.IOException; + +import org.junit.Before; +import org.junit.Test; + +import static jadx.core.dex.instructions.args.ArgType.object; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class JadxClasspathTest { + + private static final String JAVA_LANG_EXCEPTION = "java.lang.Exception"; + private static final String JAVA_LANG_THROWABLE = "java.lang.Throwable"; + + ClspGraph clsp; + + @Before + public void initClsp() throws IOException, DecodeException { + clsp = new ClspGraph(); + clsp.load(); + ArgType.setClsp(clsp); + } + + @Test + public void test() { + ArgType objExc = object(JAVA_LANG_EXCEPTION); + ArgType objThr = object(JAVA_LANG_THROWABLE); + + assertTrue(clsp.isImplements(JAVA_LANG_EXCEPTION, JAVA_LANG_THROWABLE)); + assertFalse(clsp.isImplements(JAVA_LANG_THROWABLE, JAVA_LANG_EXCEPTION)); + + assertFalse(ArgType.isCastNeeded(objExc, objThr)); + assertTrue(ArgType.isCastNeeded(objThr, objExc)); + } +}