diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java index ccb6e83687e..8b184162988 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java @@ -264,6 +264,13 @@ public boolean isSameConst(InsnArg other) { return false; } + public boolean isSameVar(RegisterArg arg) { + if (isRegister()) { + return ((RegisterArg) this).sameRegAndSVar(arg); + } + return false; + } + protected final T copyCommonParams(T copy) { copy.copyAttributesFrom(this); copy.setParentInsn(parentInsn); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java index 46a012e6c4a..6df5e766f17 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java @@ -80,13 +80,47 @@ private static MethodInlineAttr inlineMth(MethodNode mth) { return addInlineAttr(mth, insn); } if (insnsCount == 2 && insns.get(1).getType() == InsnType.RETURN) { - // synthetic field setter - return addInlineAttr(mth, insns.get(0)); + InsnNode firstInsn = insns.get(0); + InsnNode retInsn = insns.get(1); + if (retInsn.getArgsCount() == 0 + || isSyntheticAccessPattern(mth, firstInsn, retInsn)) { + return addInlineAttr(mth, firstInsn); + } } // TODO: inline field arithmetics. Disabled tests: TestAnonymousClass3a and TestAnonymousClass5 return null; } + private static boolean isSyntheticAccessPattern(MethodNode mth, InsnNode firstInsn, InsnNode retInsn) { + List mthRegs = mth.getArgRegs(); + switch (firstInsn.getType()) { + case IGET: + return mthRegs.size() == 1 + && retInsn.getArg(0).isSameVar(firstInsn.getResult()) + && firstInsn.getArg(0).isSameVar(mthRegs.get(0)); + case SGET: + return mthRegs.size() == 0 + && retInsn.getArg(0).isSameVar(firstInsn.getResult()); + + case IPUT: + return mthRegs.size() == 2 + && retInsn.getArg(0).isSameVar(mthRegs.get(1)) + && firstInsn.getArg(0).isSameVar(mthRegs.get(1)) + && firstInsn.getArg(1).isSameVar(mthRegs.get(0)); + case SPUT: + return mthRegs.size() == 1 + && retInsn.getArg(0).isSameVar(mthRegs.get(0)) + && firstInsn.getArg(0).isSameVar(mthRegs.get(0)); + + case INVOKE: + return mthRegs.size() >= 1 + && firstInsn.getArg(0).isSameVar(mthRegs.get(0)) + && retInsn.getArg(0).isSameVar(firstInsn.getResult()); + default: + return false; + } + } + private static MethodInlineAttr addInlineAttr(MethodNode mth, InsnNode insn) { if (!fixVisibilityOfInlineCode(mth, insn)) { return null; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java index 367a13e7ebc..496abee0176 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java @@ -9,6 +9,8 @@ import jadx.core.dex.attributes.AFlag; import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.InvokeCustomNode; +import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.Named; @@ -123,6 +125,9 @@ private static void checkInline(MethodNode mth, BlockNode block, InsnList insnLi return; } } + if (!checkLambdaInline(arg, assignInsn)) { + return; + } int assignPos = insnList.getIndex(assignInsn); if (assignPos != -1) { @@ -145,6 +150,26 @@ && canMoveBetweenBlocks(mth, assignInsn, assignBlock, block, argsInfo.getInsn()) } } + /** + * Forbid inline lambda into invoke as an instance arg, i.e. this will not compile: + * {@code () -> { ... }.apply(); } + */ + private static boolean checkLambdaInline(RegisterArg arg, InsnNode assignInsn) { + if (assignInsn.getType() == InsnType.INVOKE && assignInsn instanceof InvokeCustomNode) { + for (RegisterArg useArg : arg.getSVar().getUseList()) { + InsnNode parentInsn = useArg.getParentInsn(); + if (parentInsn != null && parentInsn.getType() == InsnType.INVOKE) { + InvokeNode invokeNode = (InvokeNode) parentInsn; + InsnArg instArg = invokeNode.getInstanceArg(); + if (instArg != null && instArg == useArg) { + return false; + } + } + } + } + return true; + } + private static boolean varWithSameNameExists(MethodNode mth, SSAVar inlineVar) { for (SSAVar ssaVar : mth.getSVars()) { if (ssaVar == inlineVar || ssaVar.getCodeVar() == inlineVar.getCodeVar()) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/java8/TestLambdaReturn.java b/jadx-core/src/test/java/jadx/tests/integration/java8/TestLambdaReturn.java new file mode 100644 index 00000000000..3a5ec828693 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/java8/TestLambdaReturn.java @@ -0,0 +1,61 @@ +package jadx.tests.integration.java8; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; +import jadx.tests.api.extensions.profiles.TestProfile; +import jadx.tests.api.extensions.profiles.TestWithProfiles; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestLambdaReturn extends IntegrationTest { + + @SuppressWarnings("unused") + public static class TestCls { + interface Function0 { + R apply(); + } + + public static class T2 { + public long l; + + public T2(long l) { + this.l = l; + } + + public void w() { + } + } + + public Byte test(Byte b1) { + Function0 f1 = () -> { + new T2(94L).w(); + return null; + }; + f1.apply(); + return null; + } + } + + @TestWithProfiles(TestProfile.DX_J8) + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .containsLines(2, + "Function0 f1 = () -> {", + indent() + "new T2(94L).w();", + indent() + "return null;", + "};"); + } + + @TestWithProfiles(TestProfile.D8_J11_DESUGAR) + public void testLambda() { + getClassNode(TestCls.class); + } + + @Test + public void testNoDebug() { + noDebugInfo(); + getClassNode(TestCls.class); + } +}