fix: precalculate class deps for inline methods (#1339)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ClassNode> useIn = new HashSet<>(cls.getUseIn());
|
||||
List<ClassNode> 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<ClassNode> dependencies = Collections.emptyList();
|
||||
/**
|
||||
* Top level classes needed for code generation stage
|
||||
*/
|
||||
private List<ClassNode> 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<ClassNode> getCodegenDeps() {
|
||||
return codegenDeps;
|
||||
}
|
||||
|
||||
public void setCodegenDeps(List<ClassNode> codegenDeps) {
|
||||
this.codegenDeps = codegenDeps;
|
||||
}
|
||||
|
||||
public List<ClassNode> getUseIn() {
|
||||
return useIn;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<InsnNode> 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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <T> List<T> safeRemoveAndTrim(List<T> list, T obj) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return list;
|
||||
}
|
||||
if (list.remove(obj)) {
|
||||
if (list.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static <T> List<T> safeAdd(List<T> list, T obj) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
List<T> newList = new ArrayList<>(1);
|
||||
newList.add(obj);
|
||||
return newList;
|
||||
}
|
||||
list.add(obj);
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search exactly one element in list by filter
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user