diff --git a/jadx-core/src/main/java/jadx/api/impl/passes/DecompilePassWrapper.java b/jadx-core/src/main/java/jadx/api/impl/passes/DecompilePassWrapper.java index e56724e15..d9ebdc80f 100644 --- a/jadx-core/src/main/java/jadx/api/impl/passes/DecompilePassWrapper.java +++ b/jadx-core/src/main/java/jadx/api/impl/passes/DecompilePassWrapper.java @@ -3,6 +3,7 @@ package jadx.api.impl.passes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.api.plugins.pass.JadxPass; import jadx.api.plugins.pass.types.JadxDecompilePass; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.MethodNode; @@ -10,7 +11,7 @@ import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.utils.exceptions.JadxException; -public class DecompilePassWrapper extends AbstractVisitor { +public class DecompilePassWrapper extends AbstractVisitor implements IPassWrapperVisitor { private static final Logger LOG = LoggerFactory.getLogger(DecompilePassWrapper.class); private final JadxDecompilePass decompilePass; @@ -19,6 +20,11 @@ public class DecompilePassWrapper extends AbstractVisitor { this.decompilePass = decompilePass; } + @Override + public JadxPass getPass() { + return decompilePass; + } + @Override public void init(RootNode root) throws JadxException { try { @@ -48,7 +54,7 @@ public class DecompilePassWrapper extends AbstractVisitor { } @Override - public String toString() { + public String getName() { return decompilePass.getInfo().getName(); } } diff --git a/jadx-core/src/main/java/jadx/api/impl/passes/IPassWrapperVisitor.java b/jadx-core/src/main/java/jadx/api/impl/passes/IPassWrapperVisitor.java new file mode 100644 index 000000000..d64d9dc9c --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/impl/passes/IPassWrapperVisitor.java @@ -0,0 +1,9 @@ +package jadx.api.impl.passes; + +import jadx.api.plugins.pass.JadxPass; +import jadx.core.dex.visitors.IDexTreeVisitor; + +public interface IPassWrapperVisitor extends IDexTreeVisitor { + + JadxPass getPass(); +} diff --git a/jadx-core/src/main/java/jadx/api/impl/passes/PreparePassWrapper.java b/jadx-core/src/main/java/jadx/api/impl/passes/PreparePassWrapper.java index 57a91f013..0d2f0d5c3 100644 --- a/jadx-core/src/main/java/jadx/api/impl/passes/PreparePassWrapper.java +++ b/jadx-core/src/main/java/jadx/api/impl/passes/PreparePassWrapper.java @@ -3,12 +3,13 @@ package jadx.api.impl.passes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.api.plugins.pass.JadxPass; import jadx.api.plugins.pass.types.JadxPreparePass; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.utils.exceptions.JadxException; -public class PreparePassWrapper extends AbstractVisitor { +public class PreparePassWrapper extends AbstractVisitor implements IPassWrapperVisitor { private static final Logger LOG = LoggerFactory.getLogger(PreparePassWrapper.class); private final JadxPreparePass preparePass; @@ -17,6 +18,11 @@ public class PreparePassWrapper extends AbstractVisitor { this.preparePass = preparePass; } + @Override + public JadxPass getPass() { + return preparePass; + } + @Override public void init(RootNode root) throws JadxException { try { @@ -27,7 +33,7 @@ public class PreparePassWrapper extends AbstractVisitor { } @Override - public String toString() { + public String getName() { return preparePass.getInfo().getName(); } } diff --git a/jadx-core/src/main/java/jadx/api/impl/plugins/PluginsContext.java b/jadx-core/src/main/java/jadx/api/impl/plugins/PluginsContext.java index 3086d447c..444569d81 100644 --- a/jadx-core/src/main/java/jadx/api/impl/plugins/PluginsContext.java +++ b/jadx-core/src/main/java/jadx/api/impl/plugins/PluginsContext.java @@ -55,7 +55,7 @@ public class PluginsContext implements JadxPluginContext { return codeInputs; } - public void setCurrentPlugin(JadxPlugin currentPlugin) { + public void setCurrentPlugin(@Nullable JadxPlugin currentPlugin) { this.currentPlugin = currentPlugin; } diff --git a/jadx-core/src/main/java/jadx/api/plugins/JadxPlugin.java b/jadx-core/src/main/java/jadx/api/plugins/JadxPlugin.java index 598361b40..d63eb34f7 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/JadxPlugin.java +++ b/jadx-core/src/main/java/jadx/api/plugins/JadxPlugin.java @@ -1,8 +1,26 @@ package jadx.api.plugins; +import jadx.api.plugins.pass.types.JadxAfterLoadPass; +import jadx.api.plugins.pass.types.JadxPreparePass; + +/** + * Base interface for all jadx plugins + *
+ * To create new plugin implement this interface and add to resources + * a {@code META-INF/services/jadx.api.plugins.JadxPlugin} file with a full name of your class. + */ public interface JadxPlugin { + /** + * Method for provide plugin information, like name and description. + * Can be invoked several times. + */ JadxPluginInfo getPluginInfo(); + /** + * Init plugin. + * Use {@link JadxPluginContext} to register passes, code inputs and options. + * For long operation, prefer {@link JadxPreparePass} or {@link JadxAfterLoadPass} instead. + */ void init(JadxPluginContext context); } diff --git a/jadx-core/src/main/java/jadx/api/plugins/pass/JadxPassInfo.java b/jadx-core/src/main/java/jadx/api/plugins/pass/JadxPassInfo.java index 3f638cb28..4dc010e36 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/pass/JadxPassInfo.java +++ b/jadx-core/src/main/java/jadx/api/plugins/pass/JadxPassInfo.java @@ -4,11 +4,35 @@ import java.util.List; public interface JadxPassInfo { + /** + * Add this to 'run after' list to place pass before others + */ + String START = "start"; + + /** + * Add this to 'run before' list to place pass at end + */ + String END = "end"; + + /** + * Pass short id, should be unique. + */ String getName(); + /** + * Pass description + */ String getDescription(); + /** + * This pass will be executed after these passes. + * Passes names list. + */ List runAfter(); + /** + * This pass will be executed before these passes. + * Passes names list. + */ List runBefore(); } diff --git a/jadx-core/src/main/java/jadx/api/plugins/pass/impl/OrderedJadxPassInfo.java b/jadx-core/src/main/java/jadx/api/plugins/pass/impl/OrderedJadxPassInfo.java index da0617d19..b04c7928a 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/pass/impl/OrderedJadxPassInfo.java +++ b/jadx-core/src/main/java/jadx/api/plugins/pass/impl/OrderedJadxPassInfo.java @@ -52,4 +52,9 @@ public class OrderedJadxPassInfo implements JadxPassInfo { public List runBefore() { return runBefore; } + + @Override + public String toString() { + return "PassInfo{'" + name + '\'' + ", desc='" + desc + '\'' + ", runAfter=" + runAfter + ", runBefore=" + runBefore + '}'; + } } diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index a1097ddb7..923f369ee 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -201,7 +201,7 @@ public class Jadx { if (args.isRawCFGOutput()) { passes.add(DotGraphVisitor.dumpRaw()); } - passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK))); + passes.add(new MethodVisitor("DisableBlockLock", mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK))); passes.add(new BlockProcessor()); passes.add(new SSATransform()); passes.add(new MoveInlineVisitor()); @@ -220,7 +220,7 @@ public class Jadx { passes.add(new ReSugarCode()); passes.add(new CodeShrinkVisitor()); passes.add(new SimplifyVisitor()); - passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE))); + passes.add(new MethodVisitor("ForceGenerateAll", mth -> mth.remove(AFlag.DONT_GENERATE))); if (args.isCfgOutput()) { passes.add(DotGraphVisitor.dump()); } diff --git a/jadx-core/src/main/java/jadx/core/deobf/DeobfuscatorVisitor.java b/jadx-core/src/main/java/jadx/core/deobf/DeobfuscatorVisitor.java index 6993e9425..7366779e2 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/DeobfuscatorVisitor.java +++ b/jadx-core/src/main/java/jadx/core/deobf/DeobfuscatorVisitor.java @@ -73,7 +73,7 @@ public class DeobfuscatorVisitor extends AbstractVisitor { } @Override - public String toString() { + public String getName() { return "DeobfuscatorVisitor"; } } diff --git a/jadx-core/src/main/java/jadx/core/deobf/SaveDeobfMapping.java b/jadx-core/src/main/java/jadx/core/deobf/SaveDeobfMapping.java index b194e3d2b..925937d16 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/SaveDeobfMapping.java +++ b/jadx-core/src/main/java/jadx/core/deobf/SaveDeobfMapping.java @@ -47,7 +47,7 @@ public class SaveDeobfMapping extends AbstractVisitor { } @Override - public String toString() { + public String getName() { return "SaveDeobfMapping"; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index bbab9557a..751c9653d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -309,12 +309,10 @@ public class RootNode { } public void mergePasses(Map> customPasses) { - PassMerge.run(preDecompilePasses, - customPasses.get(JadxPreparePass.TYPE), - p -> new PreparePassWrapper((JadxPreparePass) p)); - PassMerge.run(processClasses.getPasses(), - customPasses.get(JadxDecompilePass.TYPE), - p -> new DecompilePassWrapper((JadxDecompilePass) p)); + new PassMerge(preDecompilePasses) + .merge(customPasses.get(JadxPreparePass.TYPE), p -> new PreparePassWrapper((JadxPreparePass) p)); + new PassMerge(processClasses.getPasses()) + .merge(customPasses.get(JadxDecompilePass.TYPE), p -> new DecompilePassWrapper((JadxDecompilePass) p)); } public void runPreDecompileStage() { @@ -615,6 +613,10 @@ public class RootNode { return processClasses.getPasses(); } + public List getPreDecompilePasses() { + return preDecompilePasses; + } + public void initPasses() { processClasses.initPasses(this); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/AbstractVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/AbstractVisitor.java index b2ab220c6..6f7fb96c7 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/AbstractVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/AbstractVisitor.java @@ -24,7 +24,12 @@ public abstract class AbstractVisitor implements IDexTreeVisitor { } @Override - public String toString() { + public String getName() { return this.getClass().getSimpleName(); } + + @Override + public String toString() { + return getName(); + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/IDexTreeVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/IDexTreeVisitor.java index 6499222fc..42ed86a89 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/IDexTreeVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/IDexTreeVisitor.java @@ -10,6 +10,11 @@ import jadx.core.utils.exceptions.JadxException; */ public interface IDexTreeVisitor { + /** + * Visitor short id + */ + String getName(); + /** * Called after loading dex tree, but before visitor traversal. */ diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodVisitor.java index 7c15d0afc..946ce463c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodVisitor.java @@ -2,16 +2,16 @@ package jadx.core.dex.visitors; import java.util.function.Consumer; -import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.JadxException; -public class MethodVisitor implements IDexTreeVisitor { +public class MethodVisitor extends AbstractVisitor { + private final String name; private final Consumer visitor; - public MethodVisitor(Consumer visitor) { + public MethodVisitor(String name, Consumer visitor) { + this.name = name; this.visitor = visitor; } @@ -21,11 +21,7 @@ public class MethodVisitor implements IDexTreeVisitor { } @Override - public void init(RootNode root) throws JadxException { - } - - @Override - public boolean visit(ClassNode cls) throws JadxException { - return true; + public String getName() { + return name; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java index ee439bf04..701b02ab3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java @@ -462,7 +462,7 @@ public class OverrideMethodVisitor extends AbstractVisitor { } @Override - public String toString() { + public String getName() { return "OverrideMethodVisitor"; } } 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 1872a874d..88eb5db0a 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 @@ -362,7 +362,7 @@ public class ProcessAnonymous extends AbstractVisitor { } @Override - public String toString() { + public String getName() { return "ProcessAnonymous"; } } 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 index 0851f2e3c..e7be157e5 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessMethodsForInline.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessMethodsForInline.java @@ -73,7 +73,7 @@ public class ProcessMethodsForInline extends AbstractVisitor { } @Override - public String toString() { + public String getName() { return "ProcessMethodsForInline"; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java index 5f0d6fb56..e1dcd051e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java @@ -278,7 +278,7 @@ public class SignatureProcessor extends AbstractVisitor { } @Override - public String toString() { + public String getName() { return "SignatureProcessor"; } } 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 4c373ff00..6f22c022b 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 @@ -246,7 +246,7 @@ public class RenameVisitor extends AbstractVisitor { } @Override - public String toString() { + public String getName() { return "RenameVisitor"; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java index 1afd75563..86b45b312 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java @@ -177,7 +177,7 @@ public class UsageInfoVisitor extends AbstractVisitor { } @Override - public String toString() { + public String getName() { return "UsageInfoVisitor"; } } diff --git a/jadx-core/src/main/java/jadx/core/utils/PassMerge.java b/jadx-core/src/main/java/jadx/core/utils/PassMerge.java index dbd363175..acb969d22 100644 --- a/jadx-core/src/main/java/jadx/core/utils/PassMerge.java +++ b/jadx-core/src/main/java/jadx/core/utils/PassMerge.java @@ -1,9 +1,14 @@ package jadx.core.utils; +import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Function; +import java.util.stream.Collectors; import jadx.api.plugins.pass.JadxPass; import jadx.api.plugins.pass.JadxPassInfo; @@ -12,71 +17,216 @@ import jadx.core.utils.exceptions.JadxRuntimeException; public class PassMerge { - public static void run(List passes, List customPasses, Function wrap) { + private final List visitors; + + private Set mergePassesNames; + private Map namesMap; + + public PassMerge(List visitors) { + this.visitors = visitors; + } + + public void merge(List customPasses, Function wrap) { if (Utils.isEmpty(customPasses)) { return; } - for (JadxPass customPass : customPasses) { - IDexTreeVisitor pass = wrap.apply(customPass); - int pos = searchInsertPos(passes, customPass.getInfo()); + List mergePasses = ListUtils.map(customPasses, p -> new MergePass(p, wrap.apply(p), p.getInfo())); + linkDeps(mergePasses); + mergePasses.sort(new ExtDepsComparator(visitors).thenComparing(InvertedDepsComparator.INSTANCE)); + + namesMap = new IdentityHashMap<>(); + visitors.forEach(p -> namesMap.put(p, p.getName())); + mergePasses.forEach(p -> namesMap.put(p.getVisitor(), p.getName())); + + mergePassesNames = mergePasses.stream().map(MergePass::getName).collect(Collectors.toSet()); + + for (MergePass mergePass : mergePasses) { + int pos = searchInsertPos(mergePass); if (pos == -1) { - passes.add(pass); + visitors.add(mergePass.getVisitor()); } else { - passes.add(pos, pass); + visitors.add(pos, mergePass.getVisitor()); } } } - private static int searchInsertPos(List passes, JadxPassInfo info) { - List runAfter = info.runAfter(); - List runBefore = info.runBefore(); + private int searchInsertPos(MergePass pass) { + List runAfter = pass.after(); + List runBefore = pass.before(); if (runAfter.isEmpty() && runBefore.isEmpty()) { return -1; // last } - if (ListUtils.isSingleElement(runAfter, "start")) { + if (ListUtils.isSingleElement(runAfter, JadxPassInfo.START)) { return 0; } - if (ListUtils.isSingleElement(runBefore, "end")) { + if (ListUtils.isSingleElement(runBefore, JadxPassInfo.END)) { return -1; } - Map namesMap = buildNamesMap(passes); - int after = 0; + int visitorsCount = visitors.size(); + Map namePosMap = new HashMap<>(visitorsCount); + for (int i = 0; i < visitorsCount; i++) { + namePosMap.put(namesMap.get(visitors.get(i)), i); + } + int after = -1; for (String name : runAfter) { - Integer pos = namesMap.get(name); + Integer pos = namePosMap.get(name); if (pos != null) { after = Math.max(after, pos); + } else { + if (mergePassesNames.contains(name)) { + // ignore known passes + continue; + } + throw new JadxRuntimeException("Ordering pass not found: " + name + + ", listed in 'runAfter' of pass: " + pass + + "\n all passes: " + ListUtils.map(visitors, namesMap::get)); } } int before = Integer.MAX_VALUE; for (String name : runBefore) { - Integer pos = namesMap.get(name); + Integer pos = namePosMap.get(name); if (pos != null) { before = Math.min(before, pos); + } else { + if (mergePassesNames.contains(name)) { + // ignore known passes + continue; + } + throw new JadxRuntimeException("Ordering pass not found: " + name + + ", listed in 'runBefore' of pass: " + pass + + "\n all passes: " + ListUtils.map(visitors, namesMap::get)); } } if (before <= after) { - throw new JadxRuntimeException("Conflict pass order requirements: " + info.getName() + throw new JadxRuntimeException("Conflict order requirements for pass: " + pass + "\n run after: " + runAfter + "\n run before: " + runBefore - + "\n passes: " + ListUtils.map(passes, PassMerge::getPassName)); + + "\n passes: " + ListUtils.map(visitors, namesMap::get)); } - if (after == 0) { + if (after == -1) { + if (before == Integer.MAX_VALUE) { + // not ordered, put at last + return -1; + } return before; } int pos = after + 1; - return pos >= passes.size() ? -1 : pos; + return pos >= visitorsCount ? -1 : pos; } - private static Map buildNamesMap(List passes) { - int size = passes.size(); - Map namesMap = new HashMap<>(size); - for (int i = 0; i < size; i++) { - namesMap.put(getPassName(passes.get(i)), i); + private static final class MergePass { + private final JadxPass pass; + private final IDexTreeVisitor visitor; + private final JadxPassInfo info; + // copy dep lists for future modifications + private final List before; + private final List after; + + private MergePass(JadxPass pass, IDexTreeVisitor visitor, JadxPassInfo info) { + this.pass = pass; + this.visitor = visitor; + this.info = info; + this.before = new ArrayList<>(info.runBefore()); + this.after = new ArrayList<>(info.runAfter()); + } + + public JadxPass getPass() { + return pass; + } + + public IDexTreeVisitor getVisitor() { + return visitor; + } + + public String getName() { + return info.getName(); + } + + public JadxPassInfo getInfo() { + return info; + } + + public List before() { + return before; + } + + public List after() { + return after; + } + + @Override + public String toString() { + return info.getName(); } - return namesMap; } - private static String getPassName(IDexTreeVisitor pass) { - return pass.getClass().getSimpleName(); + /** + * Make deps double linked + */ + private static void linkDeps(List mergePasses) { + Map map = mergePasses.stream().collect(Collectors.toMap(MergePass::getName, p -> p)); + for (MergePass pass : mergePasses) { + for (String after : pass.getInfo().runAfter()) { + MergePass beforePass = map.get(after); + if (beforePass != null) { + beforePass.before().add(pass.getName()); + } + } + for (String before : pass.getInfo().runBefore()) { + MergePass afterPass = map.get(before); + if (afterPass != null) { + afterPass.after().add(pass.getName()); + } + } + } + } + + /** + * Place passes with visitors dependencies before others. + */ + private static class ExtDepsComparator implements Comparator { + private final Set names; + + public ExtDepsComparator(List visitors) { + this.names = visitors.stream() + .map(IDexTreeVisitor::getName) + .collect(Collectors.toSet()); + } + + @Override + public int compare(MergePass first, MergePass second) { + boolean isFirst = containsVisitor(first.before()) || containsVisitor(first.after()); + boolean isSecond = containsVisitor(second.before()) || containsVisitor(second.after()); + return -Boolean.compare(isFirst, isSecond); + } + + private boolean containsVisitor(List deps) { + for (String dep : deps) { + if (names.contains(dep)) { + return true; + } + } + return false; + } + } + + /** + * Sort to get inverted dependencies i.e. if pass depends on another place it before. + */ + private static class InvertedDepsComparator implements Comparator { + public static final InvertedDepsComparator INSTANCE = new InvertedDepsComparator(); + + @Override + public int compare(MergePass first, MergePass second) { + if (first.before().contains(second.getName()) + || first.after().contains(second.getName())) { + return 1; + } + if (second.before().contains(first.getName()) + || second.after().contains(first.getName())) { + return -1; + } + return 0; + } } } diff --git a/jadx-core/src/test/java/jadx/core/utils/PassMergeTest.java b/jadx-core/src/test/java/jadx/core/utils/PassMergeTest.java new file mode 100644 index 000000000..e22aaf3b1 --- /dev/null +++ b/jadx-core/src/test/java/jadx/core/utils/PassMergeTest.java @@ -0,0 +1,143 @@ +package jadx.core.utils; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import jadx.api.impl.passes.DecompilePassWrapper; +import jadx.api.plugins.pass.JadxPass; +import jadx.api.plugins.pass.JadxPassInfo; +import jadx.api.plugins.pass.impl.OrderedJadxPassInfo; +import jadx.api.plugins.pass.impl.SimpleJadxPassInfo; +import jadx.api.plugins.pass.types.JadxDecompilePass; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.dex.visitors.IDexTreeVisitor; +import jadx.core.utils.exceptions.JadxRuntimeException; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +class PassMergeTest { + + @Test + public void testSimple() { + List base = asList("a", "b", "c"); + check(base, mockPass("x"), asList("a", "b", "c", "x")); + check(base, mockPass(mockInfo("x").after(JadxPassInfo.START)), asList("x", "a", "b", "c")); + check(base, mockPass(mockInfo("x").before(JadxPassInfo.END)), asList("a", "b", "c", "x")); + } + + @Test + public void testSingle() { + List base = asList("a", "b", "c"); + check(base, mockPass(mockInfo("x").after("a")), asList("a", "x", "b", "c")); + check(base, mockPass(mockInfo("x").before("c")), asList("a", "b", "x", "c")); + check(base, mockPass(mockInfo("x").before("a")), asList("x", "a", "b", "c")); + check(base, mockPass(mockInfo("x").after("c")), asList("a", "b", "c", "x")); + } + + @Test + public void testMulti() { + List base = asList("a", "b", "c"); + JadxPass x = mockPass(mockInfo("x").after("a")); + JadxPass y = mockPass(mockInfo("y").after("a")); + JadxPass z = mockPass(mockInfo("z").before("b")); + check(base, asList(x, y, z), asList("a", "y", "x", "z", "b", "c")); + } + + @Test + public void testMultiWithDeps() { + List base = asList("a", "b", "c"); + JadxPass x = mockPass(mockInfo("x").after("a")); + JadxPass y = mockPass(mockInfo("y").after("x")); + JadxPass z = mockPass(mockInfo("z").before("b").after("y")); + check(base, asList(x, y, z), asList("a", "x", "y", "z", "b", "c")); + } + + @Test + public void testMultiWithDeps2() { + List base = asList("a", "b", "c"); + JadxPass x = mockPass(mockInfo("x").before("y")); + JadxPass y = mockPass(mockInfo("y").before("b")); + JadxPass z = mockPass(mockInfo("z").after("y")); + check(base, asList(x, y, z), asList("a", "x", "y", "z", "b", "c")); + } + + @Test + public void testMultiWithDeps3() { + List base = asList("a", "b", "c"); + JadxPass x = mockPass(mockInfo("x")); + JadxPass y = mockPass(mockInfo("y").after("x").before("b")); + check(base, asList(x, y), asList("a", "x", "y", "b", "c")); + } + + @Test + public void testLoop() { + List base = asList("a", "b", "c"); + JadxPass x = mockPass(mockInfo("x").before("y")); + JadxPass y = mockPass(mockInfo("y").before("x")); + Throwable thrown = catchThrowable(() -> check(base, asList(x, y), emptyList())); + assertThat(thrown).isInstanceOf(JadxRuntimeException.class); + } + + private void check(List visitorNames, JadxPass pass, List result) { + check(visitorNames, singletonList(pass), result); + } + + private void check(List visitorNames, List passes, List result) { + List visitors = ListUtils.map(visitorNames, PassMergeTest::mockVisitor); + new PassMerge(visitors).merge(passes, p -> new DecompilePassWrapper((JadxDecompilePass) p)); + List resultVisitors = ListUtils.map(visitors, IDexTreeVisitor::getName); + assertThat(resultVisitors).isEqualTo(result); + } + + private static IDexTreeVisitor mockVisitor(String name) { + return new AbstractVisitor() { + @Override + public String getName() { + return name; + } + }; + } + + private JadxPass mockPass(String name) { + return mockPass(new SimpleJadxPassInfo(name)); + } + + private OrderedJadxPassInfo mockInfo(String name) { + return new OrderedJadxPassInfo(name, name); + } + + private JadxPass mockPass(JadxPassInfo info) { + return new JadxDecompilePass() { + @Override + public void init(RootNode root) { + } + + @Override + public boolean visit(ClassNode cls) { + return false; + } + + @Override + public void visit(MethodNode mth) { + } + + @Override + public JadxPassInfo getInfo() { + return info; + } + + @Override + public String toString() { + return info.getName(); + } + }; + } +} diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Debug.kt b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Debug.kt index 9b063153a..497854359 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Debug.kt +++ b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Debug.kt @@ -15,4 +15,12 @@ class Debug(private val jadx: JadxScriptInstance) { fun saveCFG(mth: MethodNode, file: File = File("dump-mth-raw")) { DotGraphVisitor.dumpRaw().save(file, mth) } + + fun printPreparePasses() { + jadx.internalDecompiler.root.preDecompilePasses.forEach { jadx.log.info { it.name } } + } + + fun printPasses() { + jadx.internalDecompiler.root.passes.forEach { jadx.log.info { it.name } } + } } diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Decompile.kt b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Decompile.kt index 323cce283..ce63fd8d6 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Decompile.kt +++ b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Decompile.kt @@ -13,8 +13,7 @@ class Decompile(private val jadx: JadxScriptInstance) { fun allThreaded(threadsCount: Int = JadxArgs.DEFAULT_THREADS_COUNT) { val executor = Executors.newFixedThreadPool(threadsCount) - val dec = jadx.internalDecompiler - val batches = dec.decompileScheduler.buildBatches(jadx.classes) + val batches = jadx.internalDecompiler.decompileScheduler.buildBatches(jadx.classes) for (batch in batches) { executor.submit { batch.forEach(JavaClass::decompile)