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)