diff --git a/.gitignore b/.gitignore
index 34d09155e..9d20058a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,7 +27,6 @@ jadx-output/
*-tmp/
**/tmp/
-*.dex
*.class
*.dump
*.log
diff --git a/build.gradle b/build.gradle
index 9b32030fe..c8fa053e2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -34,6 +34,7 @@ allprojects {
dependencies {
compile 'org.slf4j:slf4j-api:1.7.30'
+ compileOnly 'org.jetbrains:annotations:19.0.0'
testCompile 'ch.qos.logback:logback-classic:1.2.3'
testCompile 'org.hamcrest:hamcrest-library:2.2'
@@ -44,6 +45,7 @@ allprojects {
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2'
testCompile 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
+ testCompileOnly 'org.jetbrains:annotations:19.0.0'
}
test {
@@ -86,6 +88,7 @@ spotless {
include 'jadx-cli/src/**/java/**/*.java'
include 'jadx-core/src/**/java/**/*.java'
include 'jadx-gui/src/**/java/**/*.java'
+ include 'jadx-plugins/**/java/**/*.java'
}
importOrderFile 'config/code-formatter/eclipse.importorder'
diff --git a/jadx-cli/build.gradle b/jadx-cli/build.gradle
index 7fec42de9..25eeed8ec 100644
--- a/jadx-cli/build.gradle
+++ b/jadx-cli/build.gradle
@@ -4,6 +4,10 @@ plugins {
dependencies {
compile(project(':jadx-core'))
+
+ runtime(project(':jadx-plugins:jadx-dex-input'))
+ runtime(project(':jadx-plugins:jadx-java-convert'))
+
compile 'com.beust:jcommander:1.78'
compile 'ch.qos.logback:logback-classic:1.2.3'
}
diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLI.java b/jadx-cli/src/main/java/jadx/cli/JadxCLI.java
index 4691991a7..ef876cc37 100644
--- a/jadx-cli/src/main/java/jadx/cli/JadxCLI.java
+++ b/jadx-cli/src/main/java/jadx/cli/JadxCLI.java
@@ -17,8 +17,11 @@ public class JadxCLI {
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) {
- result = processAndSave(jadxArgs);
+ result = processAndSave(jadxArgs.toJadxArgs());
}
+ } catch (JadxArgsValidateException e) {
+ LOG.error("Incorrect arguments: {}", e.getMessage());
+ result = 1;
} catch (Exception e) {
LOG.error("jadx error: {}", e.getMessage(), e);
result = 1;
@@ -28,23 +31,18 @@ public class JadxCLI {
}
}
- static int processAndSave(JadxCLIArgs inputArgs) {
- JadxArgs args = inputArgs.toJadxArgs();
- args.setCodeCache(new NoOpCodeCache());
- JadxDecompiler jadx = new JadxDecompiler(args);
- try {
+ static int processAndSave(JadxArgs jadxArgs) {
+ jadxArgs.setCodeCache(new NoOpCodeCache());
+ try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load();
- } catch (JadxArgsValidateException e) {
- LOG.error("Incorrect arguments: {}", e.getMessage());
- return 1;
- }
- jadx.save();
- int errorsCount = jadx.getErrorsCount();
- if (errorsCount != 0) {
- jadx.printErrorsReport();
- LOG.error("finished with errors, count: {}", errorsCount);
- } else {
- LOG.info("done");
+ jadx.save();
+ int errorsCount = jadx.getErrorsCount();
+ if (errorsCount != 0) {
+ jadx.printErrorsReport();
+ LOG.error("finished with errors, count: {}", errorsCount);
+ } else {
+ LOG.info("done");
+ }
}
return 0;
}
diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle
index 44ffadae7..8bc7c595b 100644
--- a/jadx-core/build.gradle
+++ b/jadx-core/build.gradle
@@ -1,11 +1,13 @@
+plugins {
+ id 'java-library'
+}
+
dependencies {
runtime files('clsp-data/android-29-clst.jar')
runtime files('clsp-data/android-29-res.jar')
- compile files('lib/dx-1.16.jar') // TODO: dx don't support java version > 9 (53)
+ api(project(':jadx-plugins:jadx-plugins-api'))
- compile 'org.ow2.asm:asm:8.0.1'
- compile 'org.jetbrains:annotations:19.0.0'
compile 'com.google.code.gson:gson:2.8.6'
compile 'org.smali:baksmali:2.4.0'
@@ -15,6 +17,9 @@ dependencies {
compile 'com.google.guava:guava:29.0-jre'
testCompile 'org.apache.commons:commons-lang3:3.9'
+
+ testRuntime(project(':jadx-plugins:jadx-dex-input'))
+ testRuntime(project(':jadx-plugins:jadx-java-convert'))
}
test {
diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java
index ad37fd9ed..65e1c8bd9 100644
--- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java
+++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java
@@ -1,6 +1,8 @@
package jadx.api;
+import java.io.Closeable;
import java.io.File;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -17,6 +19,10 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import jadx.api.plugins.JadxPlugin;
+import jadx.api.plugins.JadxPluginManager;
+import jadx.api.plugins.input.JadxInputPlugin;
+import jadx.api.plugins.input.data.ILoadResult;
import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
@@ -26,8 +32,8 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject;
+import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
-import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
@@ -39,10 +45,10 @@ import jadx.core.xmlgen.ResourcesSaver;
* JadxArgs args = new JadxArgs();
* args.getInputFiles().add(new File("test.apk"));
* args.setOutDir(new File("jadx-test-output"));
- *
- * JadxDecompiler jadx = new JadxDecompiler(args);
- * jadx.load();
- * jadx.save();
+ * try (JadxDecompiler jadx = new JadxDecompiler(args)) {
+ * jadx.load();
+ * jadx.save();
+ * }
*
*
*
@@ -56,11 +62,12 @@ import jadx.core.xmlgen.ResourcesSaver;
*
*
*/
-public final class JadxDecompiler {
+public final class JadxDecompiler implements Closeable {
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
private JadxArgs args;
- private List inputFiles;
+ private JadxPluginManager pluginManager = new JadxPluginManager();
+ private List loadedInputs = new ArrayList<>();
private RootNode root;
private List classes;
@@ -68,9 +75,9 @@ public final class JadxDecompiler {
private BinaryXMLParser xmlParser;
- private Map classesMap = new ConcurrentHashMap<>();
- private Map methodsMap = new ConcurrentHashMap<>();
- private Map fieldsMap = new ConcurrentHashMap<>();
+ private final Map classesMap = new ConcurrentHashMap<>();
+ private final Map methodsMap = new ConcurrentHashMap<>();
+ private final Map fieldsMap = new ConcurrentHashMap<>();
public JadxDecompiler() {
this(new JadxArgs());
@@ -84,16 +91,23 @@ public final class JadxDecompiler {
reset();
JadxArgsValidator.validate(args);
LOG.info("loading ...");
-
- inputFiles = loadFiles(args.getInputFiles());
+ loadInputFiles();
root = new RootNode(args);
- root.load(inputFiles);
+ root.loadClasses(loadedInputs);
root.initClassPath();
root.loadResources(getResources());
root.initPasses();
}
+ private void loadInputFiles() {
+ loadedInputs.clear();
+ List inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
+ for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
+ loadedInputs.add(inputPlugin.loadFiles(inputPaths));
+ }
+ }
+
private void reset() {
root = null;
classes = null;
@@ -103,27 +117,34 @@ public final class JadxDecompiler {
classesMap.clear();
methodsMap.clear();
fieldsMap.clear();
+
+ closeInputs();
+ }
+
+ private void closeInputs() {
+ loadedInputs.forEach(load -> {
+ try {
+ load.close();
+ } catch (Exception e) {
+ LOG.error("Failed to close input", e);
+ }
+ });
+ loadedInputs.clear();
+ }
+
+ @Override
+ public void close() {
+ reset();
+ }
+
+ public void registerPlugin(JadxPlugin plugin) {
+ pluginManager.register(plugin);
}
public static String getVersion() {
return Jadx.getVersion();
}
- private List loadFiles(List files) {
- if (files.isEmpty()) {
- throw new JadxRuntimeException("Empty file list");
- }
- List filesList = new ArrayList<>();
- for (File file : files) {
- try {
- InputFile.addFilesFrom(file, filesList, args.isSkipSources());
- } catch (Exception e) {
- throw new JadxRuntimeException("Error load file: " + file, e);
- }
- }
- return filesList;
- }
-
public void save() {
save(!args.isSkipSources(), !args.isSkipResources());
}
@@ -232,7 +253,7 @@ public final class JadxDecompiler {
if (root == null) {
return Collections.emptyList();
}
- resources = new ResourcesLoader(this).load(inputFiles);
+ resources = new ResourcesLoader(this).load();
}
return resources;
}
@@ -432,4 +453,5 @@ public final class JadxDecompiler {
public String toString() {
return "jadx decompiler " + getVersion();
}
+
}
diff --git a/jadx-core/src/main/java/jadx/api/JavaField.java b/jadx-core/src/main/java/jadx/api/JavaField.java
index cfe0dd6d4..582c66bb4 100644
--- a/jadx-core/src/main/java/jadx/api/JavaField.java
+++ b/jadx-core/src/main/java/jadx/api/JavaField.java
@@ -39,7 +39,7 @@ public final class JavaField implements JavaNode {
}
public ArgType getType() {
- return ArgType.tryToResolveClassAlias(field.dex(), field.getType());
+ return ArgType.tryToResolveClassAlias(field.root(), field.getType());
}
@Override
diff --git a/jadx-core/src/main/java/jadx/api/JavaMethod.java b/jadx-core/src/main/java/jadx/api/JavaMethod.java
index 82b5ac492..13e863fe8 100644
--- a/jadx-core/src/main/java/jadx/api/JavaMethod.java
+++ b/jadx-core/src/main/java/jadx/api/JavaMethod.java
@@ -48,12 +48,12 @@ public final class JavaMethod implements JavaNode {
}
List arguments = mth.getArgTypes();
return Utils.collectionMap(arguments,
- type -> ArgType.tryToResolveClassAlias(mth.dex(), type));
+ type -> ArgType.tryToResolveClassAlias(mth.root(), type));
}
public ArgType getReturnType() {
ArgType retType = mth.getReturnType();
- return ArgType.tryToResolveClassAlias(mth.dex(), retType);
+ return ArgType.tryToResolveClassAlias(mth.root(), retType);
}
public boolean isConstructor() {
diff --git a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java
index 152ed4dfd..22a6293b0 100644
--- a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java
+++ b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java
@@ -21,7 +21,6 @@ import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException;
-import jadx.core.utils.files.InputFile;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResTableParser;
@@ -39,10 +38,11 @@ public final class ResourcesLoader {
this.jadxRef = jadxRef;
}
- List load(List inputFiles) {
+ List load() {
+ List inputFiles = jadxRef.getArgs().getInputFiles();
List list = new ArrayList<>(inputFiles.size());
- for (InputFile file : inputFiles) {
- loadFile(list, file.getFile());
+ for (File file : inputFiles) {
+ loadFile(list, file);
}
return list;
}
diff --git a/jadx-core/src/main/java/jadx/core/Consts.java b/jadx-core/src/main/java/jadx/core/Consts.java
index ab35e8a2e..efe038512 100644
--- a/jadx-core/src/main/java/jadx/core/Consts.java
+++ b/jadx-core/src/main/java/jadx/core/Consts.java
@@ -11,11 +11,11 @@ public class Consts {
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
- public static final String DALVIK_ANNOTATION_PKG = "dalvik.annotation.";
- public static final String DALVIK_SIGNATURE = "dalvik.annotation.Signature";
- public static final String DALVIK_INNER_CLASS = "dalvik.annotation.InnerClass";
- public static final String DALVIK_THROWS = "dalvik.annotation.Throws";
- public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.annotation.AnnotationDefault";
+ public static final String DALVIK_ANNOTATION_PKG = "Ldalvik/annotation/";
+ public static final String DALVIK_SIGNATURE = "Ldalvik/annotation/Signature;";
+ public static final String DALVIK_INNER_CLASS = "Ldalvik/annotation/InnerClass;";
+ public static final String DALVIK_THROWS = "Ldalvik/annotation/Throws;";
+ public static final String DALVIK_ANNOTATION_DEFAULT = "Ldalvik/annotation/AnnotationDefault;";
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java
index 84dd65be2..51846927a 100644
--- a/jadx-core/src/main/java/jadx/core/Jadx.java
+++ b/jadx-core/src/main/java/jadx/core/Jadx.java
@@ -11,13 +11,40 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
-import jadx.core.dex.visitors.*;
+import jadx.core.dex.visitors.AttachMethodDetails;
+import jadx.core.dex.visitors.AttachTryCatchVisitor;
+import jadx.core.dex.visitors.ClassModifier;
+import jadx.core.dex.visitors.ConstInlineVisitor;
+import jadx.core.dex.visitors.ConstructorVisitor;
+import jadx.core.dex.visitors.DeboxingVisitor;
+import jadx.core.dex.visitors.DependencyCollector;
+import jadx.core.dex.visitors.DotGraphVisitor;
+import jadx.core.dex.visitors.EnumVisitor;
+import jadx.core.dex.visitors.ExtractFieldInit;
+import jadx.core.dex.visitors.FallbackModeVisitor;
+import jadx.core.dex.visitors.FixAccessModifiers;
+import jadx.core.dex.visitors.GenericTypesVisitor;
+import jadx.core.dex.visitors.IDexTreeVisitor;
+import jadx.core.dex.visitors.InitCodeVariables;
+import jadx.core.dex.visitors.MarkFinallyVisitor;
+import jadx.core.dex.visitors.MethodInlineVisitor;
+import jadx.core.dex.visitors.MethodInvokeVisitor;
+import jadx.core.dex.visitors.ModVisitor;
+import jadx.core.dex.visitors.MoveInlineVisitor;
+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.ReSugarCode;
+import jadx.core.dex.visitors.RenameVisitor;
+import jadx.core.dex.visitors.ShadowFieldVisitor;
+import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
-import jadx.core.dex.visitors.debuginfo.DebugInfoParseVisitor;
+import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
@@ -41,73 +68,88 @@ public class Jadx {
}
}
+ public static List getFallbackPassesList() {
+ List passes = new ArrayList<>(3);
+ passes.add(new AttachTryCatchVisitor());
+ passes.add(new ProcessInstructionsVisitor());
+ passes.add(new FallbackModeVisitor());
+ return passes;
+ }
+
public static List getPassesList(JadxArgs args) {
- List passes = new ArrayList<>();
if (args.isFallbackMode()) {
- passes.add(new FallbackModeVisitor());
- } else {
- if (args.isDebugInfo()) {
- passes.add(new DebugInfoParseVisitor());
- }
- passes.add(new BlockSplitter());
- if (args.isRawCFGOutput()) {
- passes.add(DotGraphVisitor.dumpRaw());
- }
- passes.add(new BlockProcessor());
- passes.add(new BlockExceptionHandler());
- passes.add(new BlockFinish());
-
- passes.add(new AttachMethodDetails());
- passes.add(new OverrideMethodVisitor());
-
- passes.add(new SSATransform());
- passes.add(new MoveInlineVisitor());
- passes.add(new ConstructorVisitor());
- passes.add(new InitCodeVariables());
- passes.add(new MarkFinallyVisitor());
- passes.add(new ConstInlineVisitor());
- passes.add(new TypeInferenceVisitor());
- if (args.isDebugInfo()) {
- passes.add(new DebugInfoApplyVisitor());
- }
-
- passes.add(new GenericTypesVisitor());
- passes.add(new ShadowFieldVisitor());
- passes.add(new DeboxingVisitor());
- passes.add(new ModVisitor());
- passes.add(new CodeShrinkVisitor());
- passes.add(new ReSugarCode());
- if (args.isCfgOutput()) {
- passes.add(DotGraphVisitor.dump());
- }
-
- passes.add(new RegionMakerVisitor());
- passes.add(new IfRegionVisitor());
- passes.add(new ReturnVisitor());
- passes.add(new CleanRegions());
-
- passes.add(new CodeShrinkVisitor());
- passes.add(new MethodInvokeVisitor());
- passes.add(new SimplifyVisitor());
- passes.add(new CheckRegions());
-
- passes.add(new EnumVisitor());
- passes.add(new ExtractFieldInit());
- passes.add(new FixAccessModifiers());
- passes.add(new ProcessAnonymous());
- passes.add(new ClassModifier());
- passes.add(new MethodInlineVisitor());
- passes.add(new LoopRegionVisitor());
-
- passes.add(new ProcessVariables());
- passes.add(new PrepareForCodeGen());
- if (args.isCfgOutput()) {
- passes.add(DotGraphVisitor.dumpRegions());
- }
-
- passes.add(new DependencyCollector());
- passes.add(new RenameVisitor());
+ return getFallbackPassesList();
}
+
+ List passes = new ArrayList<>();
+ if (args.isDebugInfo()) {
+ passes.add(new DebugInfoAttachVisitor());
+ }
+ passes.add(new AttachTryCatchVisitor());
+ passes.add(new ProcessInstructionsVisitor());
+
+ passes.add(new BlockSplitter());
+ if (args.isRawCFGOutput()) {
+ passes.add(DotGraphVisitor.dumpRaw());
+ }
+ passes.add(new BlockProcessor());
+ passes.add(new BlockExceptionHandler());
+ passes.add(new BlockFinish());
+
+ passes.add(new AttachMethodDetails());
+ passes.add(new OverrideMethodVisitor());
+
+ passes.add(new SSATransform());
+ passes.add(new MoveInlineVisitor());
+ passes.add(new ConstructorVisitor());
+ passes.add(new InitCodeVariables());
+ passes.add(new MarkFinallyVisitor());
+ passes.add(new ConstInlineVisitor());
+ passes.add(new TypeInferenceVisitor());
+ if (args.isRawCFGOutput()) {
+ passes.add(DotGraphVisitor.dumpRaw());
+ }
+ if (args.isDebugInfo()) {
+ passes.add(new DebugInfoApplyVisitor());
+ }
+
+ passes.add(new GenericTypesVisitor());
+ passes.add(new ShadowFieldVisitor());
+ passes.add(new DeboxingVisitor());
+ passes.add(new ModVisitor());
+ passes.add(new CodeShrinkVisitor());
+ passes.add(new ReSugarCode());
+ if (args.isCfgOutput()) {
+ passes.add(DotGraphVisitor.dump());
+ }
+
+ passes.add(new RegionMakerVisitor());
+ passes.add(new IfRegionVisitor());
+ passes.add(new ReturnVisitor());
+ passes.add(new CleanRegions());
+
+ passes.add(new CodeShrinkVisitor());
+ passes.add(new MethodInvokeVisitor());
+ passes.add(new SimplifyVisitor());
+ passes.add(new CheckRegions());
+
+ passes.add(new EnumVisitor());
+ passes.add(new ExtractFieldInit());
+ passes.add(new FixAccessModifiers());
+ passes.add(new ProcessAnonymous());
+ passes.add(new ClassModifier());
+ passes.add(new MethodInlineVisitor());
+ passes.add(new LoopRegionVisitor());
+
+ passes.add(new ProcessVariables());
+ passes.add(new PrepareForCodeGen());
+ if (args.isCfgOutput()) {
+ passes.add(DotGraphVisitor.dumpRegions());
+ }
+
+ passes.add(new DependencyCollector());
+ passes.add(new RenameVisitor());
+
return passes;
}
diff --git a/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java b/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java
index 95ae11ac2..a799d0619 100644
--- a/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java
+++ b/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java
@@ -1,20 +1,22 @@
package jadx.core.clsp;
-import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
+import jadx.api.plugins.JadxPluginManager;
+import jadx.api.plugins.input.JadxInputPlugin;
+import jadx.api.plugins.input.data.ILoadResult;
import jadx.core.dex.nodes.RootNode;
-import jadx.core.utils.exceptions.DecodeException;
-import jadx.core.utils.files.InputFile;
/**
* Utility class for convert dex or jar to jadx classes set (.jcst)
@@ -26,30 +28,24 @@ public class ConvertToClsSet {
LOG.info("