From 1a2e702b251cc6eec93884b99e715920028a2b92 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 14 Feb 2022 15:03:15 +0000 Subject: [PATCH] fix: inline nested anonymous classes (#1379) --- .../src/main/java/jadx/api/JavaClass.java | 16 ++-- .../src/main/java/jadx/core/ProcessClass.java | 4 +- .../main/java/jadx/core/codegen/ClassGen.java | 2 +- .../main/java/jadx/core/codegen/InsnGen.java | 4 +- .../java/jadx/core/dex/attributes/AFlag.java | 1 - .../java/jadx/core/dex/attributes/AType.java | 4 +- .../attributes/nodes/AnonymousClassAttr.java | 35 ++++++++ .../nodes/AnonymousClassBaseAttr.java | 28 ------- .../java/jadx/core/dex/nodes/ClassNode.java | 12 ++- .../dex/visitors/AnonymousClassVisitor.java | 3 +- .../jadx/core/dex/visitors/EnumVisitor.java | 3 +- .../core/dex/visitors/ProcessAnonymous.java | 83 +++++++++++++++---- .../typeinference/TypeInferenceVisitor.java | 4 +- .../main/java/jadx/core/utils/ListUtils.java | 18 +++- .../inner/TestNestedAnonymousClass.java | 57 +++++++++++++ .../inner/TestNestedAnonymousClass/A.smali | 34 ++++++++ .../inner/TestNestedAnonymousClass/B.smali | 46 ++++++++++ .../inner/TestNestedAnonymousClass/C.smali | 23 +++++ 18 files changed, 309 insertions(+), 68 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/dex/attributes/nodes/AnonymousClassAttr.java delete mode 100644 jadx-core/src/main/java/jadx/core/dex/attributes/nodes/AnonymousClassBaseAttr.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/inner/TestNestedAnonymousClass.java create mode 100644 jadx-core/src/test/smali/inner/TestNestedAnonymousClass/A.smali create mode 100644 jadx-core/src/test/smali/inner/TestNestedAnonymousClass/B.smali create mode 100644 jadx-core/src/test/smali/inner/TestNestedAnonymousClass/C.smali diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index 3f9acff6..785514e6 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -11,6 +11,8 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.AnonymousClassAttr; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; @@ -241,7 +243,7 @@ public final class JavaClass implements JavaNode { @Override public JavaClass getTopParentClass() { - if (cls.contains(AFlag.ANONYMOUS_CLASS)) { + if (cls.contains(AType.ANONYMOUS_CLASS)) { // moved to usage class return getParentForAnonymousClass(); } @@ -249,15 +251,9 @@ public final class JavaClass implements JavaNode { } private JavaClass getParentForAnonymousClass() { - List useIn = getUseIn(); - if (useIn.isEmpty()) { - return this; - } - JavaNode useNode = useIn.get(0); - if (useNode.equals(this)) { - return this; - } - return useNode.getTopParentClass(); + AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS); + ClassNode topParentClass = attr.getOuterCls().getTopParentClass(); + return getRootDecompiler().convertClassNode(topParentClass); } public AccessInfo getAccessInfo() { diff --git a/jadx-core/src/main/java/jadx/core/ProcessClass.java b/jadx-core/src/main/java/jadx/core/ProcessClass.java index 3f155edb..031f54ff 100644 --- a/jadx-core/src/main/java/jadx/core/ProcessClass.java +++ b/jadx-core/src/main/java/jadx/core/ProcessClass.java @@ -34,11 +34,11 @@ public final class ProcessClass { if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) { cls.remove(AFlag.CLASS_DEEP_RELOAD); cls.deepUnload(); - cls.root().runPreDecompileStageForClass(cls); + cls.add(AFlag.CLASS_UNLOADED); } if (cls.contains(AFlag.CLASS_UNLOADED)) { - cls.remove(AFlag.CLASS_UNLOADED); cls.root().runPreDecompileStageForClass(cls); + cls.remove(AFlag.CLASS_UNLOADED); } if (cls.getState() == GENERATED_AND_UNLOADED) { // force loading code again 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 cbd81976..5e6f344b 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -285,7 +285,7 @@ public class ClassGen { private boolean isInnerClassesPresents() { for (ClassNode innerCls : cls.getInnerClasses()) { - if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) { + if (!innerCls.contains(AType.ANONYMOUS_CLASS)) { return true; } } 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 62bb5529..dd5c61c2 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -719,13 +719,13 @@ public class InsnGen { private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException { if (this.mth.getParentClass() == cls) { - cls.remove(AFlag.ANONYMOUS_CLASS); + cls.remove(AType.ANONYMOUS_CLASS); cls.remove(AFlag.DONT_GENERATE); mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN); throw new CodegenException("Anonymous inner class unlimited recursion detected." + " Convert class to inner: " + cls.getClassInfo().getFullName()); } - ArgType parent = cls.get(AType.ANONYMOUS_CLASS_BASE).getBaseType(); + ArgType parent = cls.get(AType.ANONYMOUS_CLASS).getBaseType(); // hide empty anonymous constructors for (MethodNode ctor : cls.getMethods()) { if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR) diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java index c7439f87..e225c9e0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java @@ -35,7 +35,6 @@ public enum AFlag { SKIP_ARG, // skip argument in invoke call NO_SKIP_ARGS, ANONYMOUS_CONSTRUCTOR, - ANONYMOUS_CLASS, THIS, SUPER, diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java index 6988813d..d62a157f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java @@ -2,7 +2,7 @@ package jadx.core.dex.attributes; import jadx.api.plugins.input.data.attributes.IJadxAttrType; import jadx.api.plugins.input.data.attributes.IJadxAttribute; -import jadx.core.dex.attributes.nodes.AnonymousClassBaseAttr; +import jadx.core.dex.attributes.nodes.AnonymousClassAttr; import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.EdgeInsnAttr; @@ -54,7 +54,7 @@ public final class AType implements IJadxAttrType { public static final AType ENUM_CLASS = new AType<>(); public static final AType ENUM_MAP = new AType<>(); public static final AType CLASS_TYPE_VARS = new AType<>(); - public static final AType ANONYMOUS_CLASS_BASE = new AType<>(); + public static final AType ANONYMOUS_CLASS = new AType<>(); // field public static final AType FIELD_INIT_INSN = new AType<>(); diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/AnonymousClassAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/AnonymousClassAttr.java new file mode 100644 index 00000000..0f34bf10 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/AnonymousClassAttr.java @@ -0,0 +1,35 @@ +package jadx.core.dex.attributes.nodes; + +import jadx.api.plugins.input.data.attributes.PinnedAttribute; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.nodes.ClassNode; + +public class AnonymousClassAttr extends PinnedAttribute { + + private final ClassNode outerCls; + private final ArgType baseType; + + public AnonymousClassAttr(ClassNode outerCls, ArgType baseType) { + this.outerCls = outerCls; + this.baseType = baseType; + } + + public ClassNode getOuterCls() { + return outerCls; + } + + public ArgType getBaseType() { + return baseType; + } + + @Override + public AType getAttrType() { + return AType.ANONYMOUS_CLASS; + } + + @Override + public String toString() { + return "AnonymousClass{" + outerCls + ", base: " + baseType + '}'; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/AnonymousClassBaseAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/AnonymousClassBaseAttr.java deleted file mode 100644 index 849ef0ca..00000000 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/AnonymousClassBaseAttr.java +++ /dev/null @@ -1,28 +0,0 @@ -package jadx.core.dex.attributes.nodes; - -import jadx.api.plugins.input.data.attributes.PinnedAttribute; -import jadx.core.dex.attributes.AType; -import jadx.core.dex.instructions.args.ArgType; - -public class AnonymousClassBaseAttr extends PinnedAttribute { - - private final ArgType baseType; - - public AnonymousClassBaseAttr(ArgType baseType) { - this.baseType = baseType; - } - - public ArgType getBaseType() { - return baseType; - } - - @Override - public AType getAttrType() { - return AType.ANONYMOUS_CLASS_BASE; - } - - @Override - public String toString() { - return "AnonymousClassBaseAttr{" + baseType + '}'; - } -} 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 0d1ac8cb..d3038756 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 @@ -33,6 +33,7 @@ import jadx.api.plugins.input.data.impl.ListConsumer; import jadx.core.Consts; import jadx.core.ProcessClass; import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.NotificationAttrNode; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo.AFType; @@ -42,6 +43,7 @@ import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.nodes.utils.TypeUtils; +import jadx.core.utils.ListUtils; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -619,7 +621,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN } public boolean isAnonymous() { - return contains(AFlag.ANONYMOUS_CLASS); + return contains(AType.ANONYMOUS_CLASS); } public boolean isInner() { @@ -750,6 +752,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN this.dependencies = dependencies; } + public void removeDependency(ClassNode dep) { + this.dependencies = ListUtils.safeRemoveAndTrim(this.dependencies, dep); + } + public List getCodegenDeps() { return codegenDeps; } @@ -758,6 +764,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN this.codegenDeps = codegenDeps; } + public void addCodegenDep(ClassNode dep) { + this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep); + } + public int getTotalDepsCount() { return dependencies.size() + codegenDeps.size(); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/AnonymousClassVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/AnonymousClassVisitor.java index 04a91383..8c7a70eb 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/AnonymousClassVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/AnonymousClassVisitor.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.info.FieldInfo; @@ -36,7 +37,7 @@ public class AnonymousClassVisitor extends AbstractVisitor { @Override public boolean visit(ClassNode cls) throws JadxException { - if (cls.contains(AFlag.ANONYMOUS_CLASS)) { + if (cls.contains(AType.ANONYMOUS_CLASS)) { for (MethodNode mth : cls.getMethods()) { if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) { processAnonymousConstructor(mth); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index 638799c5..1acf6744 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -15,6 +15,7 @@ import jadx.api.plugins.input.data.AccessFlags; import jadx.core.codegen.TypeGen; import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; @@ -389,7 +390,7 @@ public class EnumVisitor extends AbstractVisitor { } if (constrCls.equals(cls)) { // allow same class - } else if (constrCls.contains(AFlag.ANONYMOUS_CLASS)) { + } else if (constrCls.contains(AType.ANONYMOUS_CLASS)) { // allow external class already marked as anonymous } else { return null; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java index 63e2b798..c9393719 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java @@ -1,11 +1,15 @@ package jadx.core.dex.visitors; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.jetbrains.annotations.Nullable; import jadx.core.dex.attributes.AFlag; -import jadx.core.dex.attributes.nodes.AnonymousClassBaseAttr; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.AnonymousClassAttr; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.ClassNode; @@ -25,20 +29,32 @@ import jadx.core.utils.exceptions.JadxException; ) public class ProcessAnonymous extends AbstractVisitor { - private boolean inlineAnonymous; + private boolean inlineAnonymousClasses; @Override public void init(RootNode root) { - inlineAnonymous = root.getArgs().isInlineAnonymousClasses(); + inlineAnonymousClasses = root.getArgs().isInlineAnonymousClasses(); + if (!inlineAnonymousClasses) { + return; + } + for (ClassNode cls : root.getClasses()) { + markAnonymousClass(cls); + } + mergeAnonymousDeps(root); } @Override public boolean visit(ClassNode cls) throws JadxException { - if (!inlineAnonymous) { - return false; + if (inlineAnonymousClasses && cls.contains(AFlag.CLASS_UNLOADED)) { + // enter only on class reload + visitClassAndInners(cls); } + return false; + } + + private void visitClassAndInners(ClassNode cls) { markAnonymousClass(cls); - return true; + cls.getInnerClasses().forEach(this::visitClassAndInners); } private static void markAnonymousClass(ClassNode cls) { @@ -53,25 +69,64 @@ public class ProcessAnonymous extends AbstractVisitor { if (baseType == null) { return; } - - cls.add(AFlag.ANONYMOUS_CLASS); - cls.addAttr(new AnonymousClassBaseAttr(baseType)); + ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass(); + cls.addAttr(new AnonymousClassAttr(outerCls, baseType)); cls.add(AFlag.DONT_GENERATE); - anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR); + // force anonymous class to be processed before outer class, // actual usage of outer class will be removed at anonymous class process, // see ModVisitor.processAnonymousConstructor method - ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass(); ClassNode topOuterCls = outerCls.getTopParentClass(); - ListUtils.safeRemove(cls.getDependencies(), topOuterCls); + cls.removeDependency(topOuterCls); ListUtils.safeRemove(outerCls.getUseIn(), cls); // move dependency to codegen stage if (cls.isTopClass()) { - topOuterCls.setDependencies(ListUtils.safeRemoveAndTrim(topOuterCls.getDependencies(), cls)); - topOuterCls.setCodegenDeps(ListUtils.safeAdd(topOuterCls.getCodegenDeps(), cls)); + topOuterCls.removeDependency(cls); + topOuterCls.addCodegenDep(cls); + } + } + + private void mergeAnonymousDeps(RootNode root) { + // Collect edges to build bidirectional tree: + // inline edge: anonymous -> outer (one-to-one) + // use edges: outer -> *anonymous (one-to-many) + Map inlineMap = new HashMap<>(); + Map> useMap = new HashMap<>(); + for (ClassNode anonymousCls : root.getClasses()) { + AnonymousClassAttr attr = anonymousCls.get(AType.ANONYMOUS_CLASS); + if (attr != null) { + ClassNode outerCls = attr.getOuterCls(); + useMap.computeIfAbsent(outerCls, k -> new ArrayList<>()).add(anonymousCls); + useMap.putIfAbsent(anonymousCls, new ArrayList<>()); // put leaf explicitly + inlineMap.put(anonymousCls, outerCls); + } + } + if (inlineMap.isEmpty()) { + return; + } + // starting from leaf process deps in nodes up to root + useMap.forEach((key, list) -> { + if (list.isEmpty()) { + updateDeps(key, inlineMap); + } + }); + } + + private void updateDeps(ClassNode leafCls, Map inlineMap) { + List list = new ArrayList<>(); + ClassNode current = leafCls; + while (current != null) { + list.add(current.getTopParentClass()); + current = inlineMap.get(current); + } + if (list.size() <= 2) { + // first level deps already processed + return; } + ClassNode topNode = list.remove(list.size() - 1); + topNode.setCodegenDeps(ListUtils.distinctMergeSortedLists(topNode.getCodegenDeps(), list)); } private static boolean canBeAnonymous(ClassNode cls) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java index bdbba4d5..a1791017 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java @@ -19,7 +19,7 @@ import jadx.core.Consts; import jadx.core.clsp.ClspGraph; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; -import jadx.core.dex.attributes.nodes.AnonymousClassBaseAttr; +import jadx.core.dex.attributes.nodes.AnonymousClassAttr; import jadx.core.dex.attributes.nodes.PhiListAttr; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.instructions.ArithNode; @@ -321,7 +321,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { if (ctr.isNewInstance()) { ClassNode ctrCls = root.resolveClass(ctr.getClassType()); if (ctrCls != null && ctrCls.contains(AFlag.DONT_GENERATE)) { - AnonymousClassBaseAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS_BASE); + AnonymousClassAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS); if (baseTypeAttr != null) { return baseTypeAttr.getBaseType(); } diff --git a/jadx-core/src/main/java/jadx/core/utils/ListUtils.java b/jadx-core/src/main/java/jadx/core/utils/ListUtils.java index 27d94a8c..346461bb 100644 --- a/jadx-core/src/main/java/jadx/core/utils/ListUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/ListUtils.java @@ -6,13 +6,13 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; import java.util.function.Function; import java.util.function.Predicate; import org.jetbrains.annotations.Nullable; -import jadx.core.dex.nodes.BlockNode; - public class ListUtils { public static boolean isSingleElement(@Nullable List list, T obj) { @@ -48,7 +48,19 @@ public class ListUtils { return list.get(list.size() - 1); } - public static List distinctList(List list) { + public static > List distinctMergeSortedLists(List first, List second) { + if (first.isEmpty()) { + return second; + } + if (second.isEmpty()) { + return first; + } + Set set = new TreeSet<>(first); + set.addAll(second); + return new ArrayList<>(set); + } + + public static List distinctList(List list) { return new ArrayList<>(new LinkedHashSet<>(list)); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestNestedAnonymousClass.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestNestedAnonymousClass.java new file mode 100644 index 00000000..3e794f01 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestNestedAnonymousClass.java @@ -0,0 +1,57 @@ +package jadx.tests.integration.inner; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestNestedAnonymousClass extends SmaliTest { + + @SuppressWarnings("Convert2Lambda") + public static class TestCls { + public void test() { + use(new Callable() { + @Override + public Runnable call() { + return new Runnable() { + @Override + public void run() { + System.out.println("run"); + } + }; + } + }); + } + + public void testLambda() { + use(() -> () -> System.out.println("lambda")); + } + + public void use(Callable r) { + } + } + + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("use(new Callable() {") + .containsOne("return new Runnable() {"); + } + + @Test + public void testSmali() { + getArgs().setRenameFlags(Collections.emptySet()); + List classes = loadFromSmaliFiles(); + assertThat(searchCls(classes, "A")) + .code() + .containsOne("use(new Callable() {") + .containsOne("return new Runnable() {"); + } +} diff --git a/jadx-core/src/test/smali/inner/TestNestedAnonymousClass/A.smali b/jadx-core/src/test/smali/inner/TestNestedAnonymousClass/A.smali new file mode 100644 index 00000000..e5b6c6d7 --- /dev/null +++ b/jadx-core/src/test/smali/inner/TestNestedAnonymousClass/A.smali @@ -0,0 +1,34 @@ +.class public Linner/A; +.super Ljava/lang/Object; + +.method public constructor ()V + .registers 1 + .prologue + invoke-direct {p0}, Ljava/lang/Object;->()V + return-void +.end method + +.method public test()V + .registers 2 + + .prologue + new-instance v0, Linner/B; + invoke-direct {v0, p0}, Linner/B;->(Linner/A;)V + invoke-virtual {p0, v0}, Linner/A;->use(Ljava/util/concurrent/Callable;)V + return-void +.end method + +.method public use(Ljava/util/concurrent/Callable;)V + .registers 2 + .annotation system Ldalvik/annotation/Signature; + value = { + "(", + "Ljava/util/concurrent/Callable", + "<", + "Ljava/lang/Runnable;", + ">;)V" + } + .end annotation + .prologue + return-void +.end method diff --git a/jadx-core/src/test/smali/inner/TestNestedAnonymousClass/B.smali b/jadx-core/src/test/smali/inner/TestNestedAnonymousClass/B.smali new file mode 100644 index 00000000..3e59db62 --- /dev/null +++ b/jadx-core/src/test/smali/inner/TestNestedAnonymousClass/B.smali @@ -0,0 +1,46 @@ +.class synthetic Linner/B; +.super Ljava/lang/Object; +.implements Ljava/util/concurrent/Callable; + +.annotation system Ldalvik/annotation/Signature; + value = { + "Ljava/lang/Object;", + "Ljava/util/concurrent/Callable", + "<", + "Ljava/lang/Runnable;", + ">;" + } +.end annotation + +.field final synthetic this$0:Linner/A; + +.method constructor (Linner/A;)V + .registers 2 + .prologue + iput-object p1, p0, Linner/B;->this$0:Linner/A; + invoke-direct {p0}, Ljava/lang/Object;->()V + return-void +.end method + + +.method public bridge synthetic call()Ljava/lang/Object; + .registers 2 + .annotation system Ldalvik/annotation/Throws; + value = { + Ljava/lang/Exception; + } + .end annotation + + .prologue + invoke-virtual {p0}, Linner/B;->call()Ljava/lang/Runnable; + move-result-object v0 + return-object v0 +.end method + +.method public call()Ljava/lang/Runnable; + .registers 2 + .prologue + new-instance v0, Linner/C; + invoke-direct {v0, p0}, Linner/C;->(Linner/B;)V + return-object v0 +.end method diff --git a/jadx-core/src/test/smali/inner/TestNestedAnonymousClass/C.smali b/jadx-core/src/test/smali/inner/TestNestedAnonymousClass/C.smali new file mode 100644 index 00000000..9de384c0 --- /dev/null +++ b/jadx-core/src/test/smali/inner/TestNestedAnonymousClass/C.smali @@ -0,0 +1,23 @@ +.class synthetic Linner/C; +.super Ljava/lang/Object; + +.implements Ljava/lang/Runnable; + +.field final synthetic this$1:Linner/B; + +.method constructor (Linner/B;)V + .registers 2 + .prologue + iput-object p1, p0, Linner/C;->this$1:Linner/B; + invoke-direct {p0}, Ljava/lang/Object;->()V + return-void +.end method + +.method public run()V + .registers 3 + .prologue + sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v1, "run" + invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V + return-void +.end method -- GitLab