未验证 提交 1a2e702b 编写于 作者: S Skylot

fix: inline nested anonymous classes (#1379)

上级 1da20b8e
......@@ -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<JavaNode> 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() {
......
......@@ -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
......
......@@ -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;
}
}
......
......@@ -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)
......
......@@ -35,7 +35,6 @@ public enum AFlag {
SKIP_ARG, // skip argument in invoke call
NO_SKIP_ARGS,
ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS,
THIS,
SUPER,
......
......@@ -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<T extends IJadxAttribute> implements IJadxAttrType<T> {
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
public static final AType<AnonymousClassBaseAttr> ANONYMOUS_CLASS_BASE = new AType<>();
public static final AType<AnonymousClassAttr> ANONYMOUS_CLASS = new AType<>();
// field
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
......
......@@ -3,26 +3,33 @@ 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 AnonymousClassBaseAttr extends PinnedAttribute {
public class AnonymousClassAttr extends PinnedAttribute {
private final ClassNode outerCls;
private final ArgType baseType;
public AnonymousClassBaseAttr(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<AnonymousClassBaseAttr> getAttrType() {
return AType.ANONYMOUS_CLASS_BASE;
public AType<AnonymousClassAttr> getAttrType() {
return AType.ANONYMOUS_CLASS;
}
@Override
public String toString() {
return "AnonymousClassBaseAttr{" + baseType + '}';
return "AnonymousClass{" + outerCls + ", base: " + baseType + '}';
}
}
......@@ -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<ClassNode> 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();
}
......
......@@ -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);
......
......@@ -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;
......
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<ClassNode, ClassNode> inlineMap = new HashMap<>();
Map<ClassNode, List<ClassNode>> 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<ClassNode, ClassNode> inlineMap) {
List<ClassNode> 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) {
......
......@@ -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();
}
......
......@@ -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 <T> boolean isSingleElement(@Nullable List<T> list, T obj) {
......@@ -48,7 +48,19 @@ public class ListUtils {
return list.get(list.size() - 1);
}
public static List<BlockNode> distinctList(List<BlockNode> list) {
public static <T extends Comparable<T>> List<T> distinctMergeSortedLists(List<T> first, List<T> second) {
if (first.isEmpty()) {
return second;
}
if (second.isEmpty()) {
return first;
}
Set<T> set = new TreeSet<>(first);
set.addAll(second);
return new ArrayList<>(set);
}
public static <T> List<T> distinctList(List<T> list) {
return new ArrayList<>(new LinkedHashSet<>(list));
}
......
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<Runnable>() {
@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<Runnable> r) {
}
}
@Test
public void test() {
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("use(new Callable<Runnable>() {")
.containsOne("return new Runnable() {");
}
@Test
public void testSmali() {
getArgs().setRenameFlags(Collections.emptySet());
List<ClassNode> classes = loadFromSmaliFiles();
assertThat(searchCls(classes, "A"))
.code()
.containsOne("use(new Callable<Runnable>() {")
.containsOne("return new Runnable() {");
}
}
.class public Linner/A;
.super Ljava/lang/Object;
.method public constructor <init>()V
.registers 1
.prologue
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public test()V
.registers 2
.prologue
new-instance v0, Linner/B;
invoke-direct {v0, p0}, Linner/B;-><init>(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
.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 <init>(Linner/A;)V
.registers 2
.prologue
iput-object p1, p0, Linner/B;->this$0:Linner/A;
invoke-direct {p0}, Ljava/lang/Object;-><init>()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;-><init>(Linner/B;)V
return-object v0
.end method
.class synthetic Linner/C;
.super Ljava/lang/Object;
.implements Ljava/lang/Runnable;
.field final synthetic this$1:Linner/B;
.method constructor <init>(Linner/B;)V
.registers 2
.prologue
iput-object p1, p0, Linner/C;->this$1:Linner/B;
invoke-direct {p0}, Ljava/lang/Object;-><init>()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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册