From d2bde0be21680c809bdbc0e9fa400c3819e1ba2a Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 12 Dec 2021 21:12:05 +0000 Subject: [PATCH] fix: invoke in nested anonymous classes (#1305) --- .../main/java/jadx/core/codegen/InsnGen.java | 17 +++++- .../core/dex/instructions/args/InsnArg.java | 21 +++++++ .../dex/visitors/rename/RenameVisitor.java | 2 +- .../api/utils/assertj/JadxCodeAssertions.java | 7 +++ .../inner/TestAnonymousClass18.java | 60 +++++++++++++++++++ 5 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass18.java 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 d481c3acc..9dc8da3ae 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -763,8 +763,7 @@ public class InsnGen { case VIRTUAL: case INTERFACE: InsnArg arg = insn.getArg(0); - // FIXME: add 'this' for equals methods in scope - if (!arg.isThis()) { + if (needInvokeArg(arg)) { addArgDot(code, arg); } k++; @@ -799,6 +798,20 @@ public class InsnGen { generateMethodArguments(code, insn, k, callMthNode); } + // FIXME: add 'this' for equals methods in scope + private boolean needInvokeArg(InsnArg arg) { + if (arg.isAnyThis()) { + if (arg.isThis()) { + return false; + } + ClassNode clsNode = mth.root().resolveClass(arg.getType()); + if (clsNode != null && clsNode.contains(AFlag.DONT_GENERATE)) { + return false; + } + } + return true; + } + private void makeInvokeLambda(ICodeWriter code, InvokeCustomNode customNode) throws CodegenException { if (customNode.isUseRef()) { makeRefLambda(code, customNode); 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 bf16dd89c..ccb6e8368 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 @@ -232,6 +232,27 @@ public abstract class InsnArg extends Typed { return contains(AFlag.THIS); } + /** + * Return true for 'this' from other classes (often occur in anonymous classes) + */ + public boolean isAnyThis() { + if (contains(AFlag.THIS)) { + return true; + } + InsnNode wrappedInsn = unwrap(); + if (wrappedInsn != null && wrappedInsn.getType() == InsnType.IGET) { + return wrappedInsn.getArg(0).isAnyThis(); + } + return false; + } + + public InsnNode unwrap() { + if (isInsnWrap()) { + return ((InsnWrapArg) this).getWrapInsn(); + } + return null; + } + public boolean isConst() { return isLiteral() || (isInsnWrap() && ((InsnWrapArg) this).getWrapInsn().isConstInsn()); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java index 24b560803..ff774d90e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java @@ -97,7 +97,7 @@ public class RenameVisitor extends AbstractVisitor { // check inner classes names ClassInfo parentClass = classInfo.getParentClass(); while (parentClass != null) { - if (parentClass.getAliasShortName().equals(clsName)) { + if (parentClass.getAliasShortName().equals(newShortName)) { String clsAlias = deobfuscator.getClsAlias(cls); classInfo.changeShortName(clsAlias); cls.addAttr(new RenameReasonAttr(cls).append("collision with other inner class name")); diff --git a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java index 62efd703c..b9cfb3d13 100644 --- a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java +++ b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java @@ -74,6 +74,13 @@ public class JadxCodeAssertions extends AbstractStringAssert return newCode; } + public JadxCodeAssertions removeLineComments() { + String code = actual.replaceAll("//.*(?!$)", ""); + JadxCodeAssertions newCode = new JadxCodeAssertions(code); + newCode.print(); + return newCode; + } + public JadxCodeAssertions print() { System.out.println("-----------------------------------------------------------"); System.out.println(actual); diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass18.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass18.java new file mode 100644 index 000000000..8acbce269 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass18.java @@ -0,0 +1,60 @@ +package jadx.tests.integration.inner; + +import org.junit.jupiter.api.Test; + +import jadx.api.CommentsLevel; +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestAnonymousClass18 extends IntegrationTest { + + @SuppressWarnings({ "Convert2Lambda", "Anonymous2MethodRef", "unused" }) + public static class TestCls { + + public interface Job { + void executeJob(); + } + + public void start() { + runJob(new Job() { + @Override + public void executeJob() { + runJob(new Job() { + @Override + public void executeJob() { + doSomething(); + } + }); + } + + private void doSomething() { + } + }); + } + + public static void runJob(Job job) { + } + } + + @Test + public void test() { + getArgs().setCommentsLevel(CommentsLevel.WARN); + assertThat(getClassNode(TestCls.class)) + .code() + .doesNotContain("AnonymousClass1.this") + .doesNotContain("class AnonymousClass1") + // .doesNotContain("TestAnonymousClass18$TestCls.runJob(") // TODO: ??? + .containsOne(indent() + "doSomething();"); + } + + @Test + public void testNoInline() { + getArgs().setInlineAnonymousClasses(false); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("class AnonymousClass1 implements Job {") + .containsOne("class C00001 implements Job {") + .containsOne("AnonymousClass1.this.doSomething();"); + } +}