diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index fa8a34293..02f89ab8b 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -38,6 +38,7 @@ import jadx.core.dex.visitors.OverrideMethodVisitor; import jadx.core.dex.visitors.PrepareForCodeGen; import jadx.core.dex.visitors.ProcessAnonymous; import jadx.core.dex.visitors.ProcessInstructionsVisitor; +import jadx.core.dex.visitors.ProcessMethodsForInline; import jadx.core.dex.visitors.ReSugarCode; import jadx.core.dex.visitors.ShadowFieldVisitor; import jadx.core.dex.visitors.SignatureProcessor; @@ -89,6 +90,7 @@ public class Jadx { passes.add(new RenameVisitor()); passes.add(new UsageInfoVisitor()); passes.add(new ProcessAnonymous()); + passes.add(new ProcessMethodsForInline()); return passes; } diff --git a/jadx-core/src/main/java/jadx/core/ProcessClass.java b/jadx-core/src/main/java/jadx/core/ProcessClass.java index be40fe245..1631f21e1 100644 --- a/jadx-core/src/main/java/jadx/core/ProcessClass.java +++ b/jadx-core/src/main/java/jadx/core/ProcessClass.java @@ -1,10 +1,5 @@ package jadx.core; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -94,21 +89,13 @@ public final class ProcessClass { return generateCode(topParentClass); } try { - Set useIn = new HashSet<>(cls.getUseIn()); - List usedInDeps = new ArrayList<>(); for (ClassNode depCls : cls.getDependencies()) { - if (useIn.contains(depCls)) { - // postpone to resolve cross dependencies - usedInDeps.add(depCls); - } else { - process(depCls, false); - } + process(depCls, false); } - if (!usedInDeps.isEmpty()) { - // process current class before its usage + if (!cls.getCodegenDeps().isEmpty()) { process(cls, false); - for (ClassNode depCls : usedInDeps) { - process(depCls, false); + for (ClassNode codegenDep : cls.getCodegenDeps()) { + process(codegenDep, false); } } ICodeInfo code = process(cls, true); 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 507979f11..c7439f879 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 @@ -79,6 +79,8 @@ public enum AFlag { REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again RERUN_SSA_TRANSFORM, + METHOD_CANDIDATE_FOR_INLINE, + // Class processing flags RESTART_CODEGEN, // codegen must be executed again RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage 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 f6b2bf055..098ccb3ce 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 @@ -78,6 +78,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN * Top level classes used in this class (only for top level classes, empty for inners) */ private List dependencies = Collections.emptyList(); + /** + * Top level classes needed for code generation stage + */ + private List codegenDeps = Collections.emptyList(); /** * Classes which uses this class */ @@ -735,6 +739,14 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN this.dependencies = dependencies; } + public List getCodegenDeps() { + return codegenDeps; + } + + public void setCodegenDeps(List codegenDeps) { + this.codegenDeps = codegenDeps; + } + public List getUseIn() { return useIn; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/FixAccessModifiers.java b/jadx-core/src/main/java/jadx/core/dex/visitors/FixAccessModifiers.java index e7c6dde7a..c78a35ad0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/FixAccessModifiers.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/FixAccessModifiers.java @@ -86,7 +86,7 @@ public class FixAccessModifiers extends AbstractVisitor { if (!accessFlags.isPublic()) { // if class is used in inlinable method => make it public for (MethodNode useMth : cls.getUseInMth()) { - boolean canInline = MarkMethodsForInline.canInline(useMth) || useMth.contains(AType.METHOD_INLINE); + boolean canInline = useMth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE) || useMth.contains(AType.METHOD_INLINE); if (canInline && !useMth.getUseIn().isEmpty()) { return AccessFlags.PUBLIC; } 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 3cfcc3402..4878fc6b0 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 @@ -10,7 +10,6 @@ import jadx.core.Consts; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.MethodInlineAttr; -import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnType; @@ -47,7 +46,7 @@ public class MarkMethodsForInline extends AbstractVisitor { if (mia != null) { return mia; } - if (canInline(mth)) { + if (mth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE)) { if (mth.getBasicBlocks() == null) { return null; } @@ -59,14 +58,6 @@ public class MarkMethodsForInline extends AbstractVisitor { return MethodInlineAttr.inlineNotNeeded(mth); } - public static boolean canInline(MethodNode mth) { - if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) { - return false; - } - AccessInfo accessFlags = mth.getAccessFlags(); - return accessFlags.isSynthetic() && accessFlags.isStatic(); - } - @Nullable private static MethodInlineAttr inlineMth(MethodNode mth) { List insns = BlockUtils.collectInsnsWithLimit(mth.getBasicBlocks(), 2); @@ -79,7 +70,11 @@ public class MarkMethodsForInline extends AbstractVisitor { if (insn.getType() == InsnType.RETURN && insn.getArgsCount() == 1) { // synthetic field getter // set arg from 'return' instruction - return addInlineAttr(mth, InsnNode.wrapArg(insn.getArg(0))); + InsnArg arg = insn.getArg(0); + if (!arg.isInsnWrap()) { + return null; + } + return addInlineAttr(mth, ((InsnWrapArg) arg).getWrapInsn()); } // method invoke return addInlineAttr(mth, insn); 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 53f9c45d3..26d8f4088 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 @@ -66,8 +66,15 @@ public class ProcessAnonymous extends AbstractVisitor { // actual usage of outer class will be removed at anonymous class process, // see ModVisitor.processAnonymousConstructor method ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass(); - ListUtils.safeRemove(cls.getDependencies(), outerCls); + ClassNode topOuterCls = outerCls.getTopParentClass(); + ListUtils.safeRemove(cls.getDependencies(), 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)); + } } /** diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessMethodsForInline.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessMethodsForInline.java new file mode 100644 index 000000000..884ea329f --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessMethodsForInline.java @@ -0,0 +1,60 @@ +package jadx.core.dex.visitors; + +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.info.AccessInfo; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.usage.UsageInfoVisitor; +import jadx.core.utils.ListUtils; +import jadx.core.utils.exceptions.JadxException; + +@JadxVisitor( + name = "ProcessMethodsForInline", + desc = "Mark methods for future inline", + runAfter = { + UsageInfoVisitor.class + } +) +public class ProcessMethodsForInline extends AbstractVisitor { + + private boolean inlineMethods; + + @Override + public void init(RootNode root) { + inlineMethods = root.getArgs().isInlineMethods(); + } + + @Override + public boolean visit(ClassNode cls) throws JadxException { + if (!inlineMethods) { + return false; + } + for (MethodNode mth : cls.getMethods()) { + if (canInline(mth)) { + mth.add(AFlag.METHOD_CANDIDATE_FOR_INLINE); + fixClassDependencies(mth); + } + } + return true; + } + + private static boolean canInline(MethodNode mth) { + if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) { + return false; + } + AccessInfo accessFlags = mth.getAccessFlags(); + boolean isSynthetic = accessFlags.isSynthetic() || mth.getName().contains("$"); + return isSynthetic && accessFlags.isStatic(); + } + + private static void fixClassDependencies(MethodNode mth) { + ClassNode parentClass = mth.getTopParentClass(); + for (MethodNode useInMth : mth.getUseIn()) { + // remove possible cross dependency to force class with inline method to be processed before its + // usage + ClassNode useTopCls = useInMth.getTopParentClass(); + parentClass.setDependencies(ListUtils.safeRemoveAndTrim(parentClass.getDependencies(), useTopCls)); + } + } +} 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 740d0a960..a2f224cd1 100644 --- a/jadx-core/src/main/java/jadx/core/utils/ListUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/ListUtils.java @@ -63,8 +63,12 @@ public class ListUtils { newList.add(newObj); return newList; } - list.remove(oldObj); - list.add(newObj); + int idx = list.indexOf(oldObj); + if (idx != -1) { + list.set(idx, newObj); + } else { + list.add(newObj); + } return list; } @@ -74,6 +78,28 @@ public class ListUtils { } } + public static List safeRemoveAndTrim(List list, T obj) { + if (list == null || list.isEmpty()) { + return list; + } + if (list.remove(obj)) { + if (list.isEmpty()) { + return Collections.emptyList(); + } + } + return list; + } + + public static List safeAdd(List list, T obj) { + if (list == null || list.isEmpty()) { + List newList = new ArrayList<>(1); + newList.add(obj); + return newList; + } + list.add(obj); + return list; + } + /** * Search exactly one element in list by filter *