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(" "); } - public static void main(String[] args) throws IOException, DecodeException { + public static void main(String[] args) throws IOException { if (args.length < 2) { usage(); System.exit(1); } - Path output = Paths.get(args[0]); + List inputPaths = Stream.of(args).map(s -> Paths.get(s)).collect(Collectors.toList()); + Path output = inputPaths.remove(0); - List inputFiles = new ArrayList<>(args.length - 1); - for (int i = 1; i < args.length; i++) { - File f = new File(args[i]); - if (f.isDirectory()) { - addFilesFromDirectory(f, inputFiles); - } else { - InputFile.addFilesFrom(f, inputFiles, false); - } - } - for (InputFile inputFile : inputFiles) { - LOG.info("Loaded: {}", inputFile.getFile()); + JadxPluginManager pluginManager = new JadxPluginManager(); + List loadedInputs = new ArrayList<>(); + for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) { + loadedInputs.add(inputPlugin.loadFiles(inputPaths)); } JadxArgs jadxArgs = new JadxArgs(); jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class)); RootNode root = new RootNode(jadxArgs); - root.load(inputFiles); + root.loadClasses(loadedInputs); ClsSet set = new ClsSet(root); set.loadFrom(root); @@ -57,22 +53,4 @@ public class ConvertToClsSet { LOG.info("Output: {}, file size: {}B", output, output.toFile().length()); LOG.info("done"); } - - private static void addFilesFromDirectory(File dir, List inputFiles) { - File[] files = dir.listFiles(); - if (files == null) { - return; - } - for (File file : files) { - if (file.isDirectory()) { - addFilesFromDirectory(file, inputFiles); - } else { - try { - InputFile.addFilesFrom(file, inputFiles, false); - } catch (Exception e) { - LOG.warn("Skip file: {}, load error: {}", file, e.getMessage()); - } - } - } - } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java b/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java index 01f4f7889..2ec79bb09 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java @@ -7,10 +7,12 @@ import java.util.Map.Entry; import org.jetbrains.annotations.Nullable; +import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.data.annotations.IAnnotation; import jadx.core.Consts; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.IAttributeNode; -import jadx.core.dex.attributes.annotations.Annotation; import jadx.core.dex.attributes.annotations.AnnotationsList; import jadx.core.dex.attributes.annotations.MethodParameters; import jadx.core.dex.info.FieldInfo; @@ -18,6 +20,7 @@ import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; import jadx.core.utils.StringUtils; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -52,7 +55,7 @@ public class AnnotationGen { if (aList == null || aList.isEmpty()) { return; } - for (Annotation a : aList.getAll()) { + for (IAnnotation a : aList.getAll()) { formatAnnotation(code, a); code.add(' '); } @@ -63,7 +66,7 @@ public class AnnotationGen { if (aList == null || aList.isEmpty()) { return; } - for (Annotation a : aList.getAll()) { + for (IAnnotation a : aList.getAll()) { String aCls = a.getAnnotationClass(); if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) { code.startLine(); @@ -72,20 +75,20 @@ public class AnnotationGen { } } - private void formatAnnotation(CodeWriter code, Annotation a) { + private void formatAnnotation(CodeWriter code, IAnnotation a) { code.add('@'); - ClassNode annCls = cls.dex().resolveClass(a.getType()); + ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass()); if (annCls != null) { classGen.useClass(code, annCls); } else { - classGen.useType(code, a.getType()); + classGen.useClass(code, a.getAnnotationClass()); } - Map vl = a.getValues(); + Map vl = a.getValues(); if (!vl.isEmpty()) { code.add('('); - for (Iterator> it = vl.entrySet().iterator(); it.hasNext();) { - Entry e = it.next(); + for (Iterator> it = vl.entrySet().iterator(); it.hasNext();) { + Entry e = it.next(); String paramName = getParamName(annCls, e.getKey()); if (paramName.equals("value") && vl.size() == 1) { // don't add "value = " if no other parameters @@ -93,7 +96,7 @@ public class AnnotationGen { code.add(paramName); code.add(" = "); } - encodeValue(code, e.getValue()); + encodeValue(cls.root(), code, e.getValue()); if (it.hasNext()) { code.add(", "); } @@ -127,66 +130,94 @@ public class AnnotationGen { } } - public Object getAnnotationDefaultValue(String name) { - Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT); + public EncodedValue getAnnotationDefaultValue(String name) { + IAnnotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT); if (an != null) { - Annotation defAnnotation = (Annotation) an.getDefaultValue(); + IAnnotation defAnnotation = (IAnnotation) an.getDefaultValue().getValue(); return defAnnotation.getValues().get(name); } return null; } // TODO: refactor this boilerplate code - public void encodeValue(CodeWriter code, Object val) { - if (val == null) { + public void encodeValue(RootNode root, CodeWriter code, EncodedValue encodedValue) { + if (encodedValue == null) { code.add("null"); return; } - if (val instanceof String) { - code.add(getStringUtils().unescapeString((String) val)); - } else if (val instanceof Integer) { - code.add(TypeGen.formatInteger((Integer) val, false)); - } else if (val instanceof Character) { - code.add(getStringUtils().unescapeChar((Character) val)); - } else if (val instanceof Boolean) { - code.add(Boolean.TRUE.equals(val) ? "true" : "false"); - } else if (val instanceof Float) { - code.add(TypeGen.formatFloat((Float) val)); - } else if (val instanceof Double) { - code.add(TypeGen.formatDouble((Double) val)); - } else if (val instanceof Long) { - code.add(TypeGen.formatLong((Long) val, false)); - } else if (val instanceof Short) { - code.add(TypeGen.formatShort((Short) val, false)); - } else if (val instanceof Byte) { - code.add(TypeGen.formatByte((Byte) val, false)); - } else if (val instanceof ArgType) { - classGen.useType(code, (ArgType) val); - code.add(".class"); - } else if (val instanceof FieldInfo) { - // must be a static field - FieldInfo field = (FieldInfo) val; - InsnGen.makeStaticFieldAccess(code, field, classGen); - } else if (val instanceof Iterable) { - code.add('{'); - Iterator it = ((Iterable) val).iterator(); - while (it.hasNext()) { - Object obj = it.next(); - encodeValue(code, obj); - if (it.hasNext()) { - code.add(", "); + Object value = encodedValue.getValue(); + switch (encodedValue.getType()) { + case ENCODED_NULL: + code.add("null"); + break; + case ENCODED_BOOLEAN: + code.add(Boolean.TRUE.equals(value) ? "true" : "false"); + break; + case ENCODED_BYTE: + code.add(TypeGen.formatByte((Byte) value, false)); + break; + case ENCODED_SHORT: + code.add(TypeGen.formatShort((Short) value, false)); + break; + case ENCODED_CHAR: + code.add(getStringUtils().unescapeChar((Character) value)); + break; + case ENCODED_INT: + code.add(TypeGen.formatInteger((Integer) value, false)); + break; + case ENCODED_LONG: + code.add(TypeGen.formatLong((Long) value, false)); + break; + case ENCODED_FLOAT: + code.add(TypeGen.formatFloat((Float) value)); + break; + case ENCODED_DOUBLE: + code.add(TypeGen.formatDouble((Double) value)); + break; + case ENCODED_STRING: + code.add(getStringUtils().unescapeString((String) value)); + break; + case ENCODED_TYPE: + classGen.useType(code, ArgType.parse((String) value)); + code.add(".class"); + break; + case ENCODED_ENUM: + case ENCODED_FIELD: + // must be a static field + if (value instanceof IFieldData) { + FieldInfo field = FieldInfo.fromData(root, (IFieldData) value); + InsnGen.makeStaticFieldAccess(code, field, classGen); + } else if (value instanceof FieldInfo) { + InsnGen.makeStaticFieldAccess(code, (FieldInfo) value, classGen); + } else { + throw new JadxRuntimeException("Unexpected field type class: " + value.getClass()); } - } - code.add('}'); - } else if (val instanceof Annotation) { - formatAnnotation(code, (Annotation) val); - } else { - // TODO: also can be method values - throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ')'); + break; + case ENCODED_METHOD: + // TODO + break; + case ENCODED_ARRAY: + code.add('{'); + Iterator it = ((Iterable) value).iterator(); + while (it.hasNext()) { + EncodedValue v = (EncodedValue) it.next(); + encodeValue(cls.root(), code, v); + if (it.hasNext()) { + code.add(", "); + } + } + code.add('}'); + break; + case ENCODED_ANNOTATION: + formatAnnotation(code, (IAnnotation) value); + break; + + default: + throw new JadxRuntimeException("Can't decode value: " + encodedValue.getType() + " (" + encodedValue + ')'); } } private StringUtils getStringUtils() { - return cls.dex().root().getStringUtils(); + return cls.root().getStringUtils(); } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index 1e1bcb100..f8f6f5b59 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -13,13 +13,16 @@ import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; -import com.android.dx.rop.code.AccessFlags; - import jadx.api.ICodeInfo; import jadx.api.JadxArgs; +import jadx.api.plugins.input.data.AccessFlags; +import jadx.api.plugins.input.data.annotations.EncodedType; +import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AttrNode; +import jadx.core.dex.attributes.FieldInitAttr; +import jadx.core.dex.attributes.FieldInitAttr.InitType; import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField; import jadx.core.dex.attributes.nodes.JadxError; @@ -31,13 +34,11 @@ import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.GenericTypeParameter; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.parser.FieldInitAttr; -import jadx.core.dex.nodes.parser.FieldInitAttr.InitType; +import jadx.core.dex.nodes.RootNode; import jadx.core.utils.CodeGenUtils; import jadx.core.utils.ErrorsCounter; import jadx.core.utils.Utils; @@ -122,17 +123,17 @@ public class ClassGen { public void addClassDeclaration(CodeWriter clsCode) { AccessInfo af = cls.getAccessFlags(); if (af.isInterface()) { - af = af.remove(AccessFlags.ACC_ABSTRACT) - .remove(AccessFlags.ACC_STATIC); + af = af.remove(AccessFlags.ABSTRACT) + .remove(AccessFlags.STATIC); } else if (af.isEnum()) { - af = af.remove(AccessFlags.ACC_FINAL) - .remove(AccessFlags.ACC_ABSTRACT) - .remove(AccessFlags.ACC_STATIC); + af = af.remove(AccessFlags.FINAL) + .remove(AccessFlags.ABSTRACT) + .remove(AccessFlags.STATIC); } // 'static' and 'private' modifier not allowed for top classes (not inner) if (!cls.getClassInfo().isInner()) { - af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE); + af = af.remove(AccessFlags.STATIC).remove(AccessFlags.PRIVATE); } annotationGen.addForClass(clsCode); @@ -392,15 +393,16 @@ public class ClassGen { FieldInitAttr fv = f.get(AType.FIELD_INIT); if (fv != null) { code.add(" = "); - if (fv.getValue() == null) { - code.add(TypeGen.literalToString(0, f.getType(), cls, fallback)); - } else { - if (fv.getValueType() == InitType.CONST) { - annotationGen.encodeValue(code, fv.getValue()); - } else if (fv.getValueType() == InitType.INSN) { - InsnGen insnGen = makeInsnGen(fv.getInsnMth()); - addInsnBody(insnGen, code, fv.getInsn()); + if (fv.getValueType() == InitType.CONST) { + EncodedValue encodedValue = fv.getEncodedValue(); + if (encodedValue.getType() == EncodedType.ENCODED_NULL) { + code.add(TypeGen.literalToString(0, f.getType(), cls, fallback)); + } else { + annotationGen.encodeValue(cls.root(), code, encodedValue); } + } else if (fv.getValueType() == InitType.INSN) { + InsnGen insnGen = makeInsnGen(fv.getInsnMth()); + addInsnBody(insnGen, code, fv.getInsn()); } } code.add(';'); @@ -425,7 +427,7 @@ public class ClassGen { EnumField f = it.next(); code.startLine(f.getField().getAlias()); ConstructorInsn constrInsn = f.getConstrInsn(); - MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth()); + MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth()); int skipCount = getEnumCtrSkipArgsCount(callMth); if (constrInsn.getArgsCount() > skipCount) { if (igen == null) { @@ -493,6 +495,10 @@ public class ClassGen { } } + public void useClass(CodeWriter code, String rawCls) { + useClass(code, ArgType.object(rawCls)); + } + public void useClass(CodeWriter code, ArgType type) { ArgType outerType = type.getOuterType(); if (outerType != null) { @@ -528,7 +534,7 @@ public class ClassGen { } public void useClass(CodeWriter code, ClassInfo classInfo) { - ClassNode classNode = cls.dex().resolveClass(classInfo); + ClassNode classNode = cls.root().resolveClass(classInfo); if (classNode != null) { useClass(code, classNode); } else { @@ -569,11 +575,11 @@ public class ClassGen { return shortName; } // don't add import if class not public (must be accessed using inheritance) - ClassNode classNode = cls.dex().resolveClass(extClsInfo); + ClassNode classNode = cls.root().resolveClass(extClsInfo); if (classNode != null && !classNode.getAccessFlags().isPublic()) { return shortName; } - if (searchCollision(cls.dex(), useCls, extClsInfo)) { + if (searchCollision(cls.root(), useCls, extClsInfo)) { return fullName; } // ignore classes from default package @@ -652,7 +658,7 @@ public class ClassGen { return false; } - private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) { + private static boolean searchCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) { if (useCls == null) { return false; } @@ -660,7 +666,7 @@ public class ClassGen { if (useCls.getAliasShortName().equals(shortName)) { return true; } - ClassNode classNode = dex.resolveClass(useCls); + ClassNode classNode = root.resolveClass(useCls); if (classNode != null) { for (ClassNode inner : classNode.getInnerClasses()) { if (inner.getShortName().equals(shortName) @@ -669,7 +675,7 @@ public class ClassGen { } } } - return searchCollision(dex, useCls.getParentClass(), searchCls); + return searchCollision(root, useCls.getParentClass(), searchCls); } private void insertRenameInfo(CodeWriter code, ClassNode cls) { diff --git a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java index 2a5b3012a..654f9ace5 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -27,7 +27,7 @@ import jadx.core.dex.instructions.ArithOp; import jadx.core.dex.instructions.BaseInvokeNode; import jadx.core.dex.instructions.ConstClassNode; import jadx.core.dex.instructions.ConstStringNode; -import jadx.core.dex.instructions.FillArrayNode; +import jadx.core.dex.instructions.FillArrayInsn; import jadx.core.dex.instructions.FilledNewArrayNode; import jadx.core.dex.instructions.GotoNode; import jadx.core.dex.instructions.IfNode; @@ -36,7 +36,7 @@ import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.InvokeType; import jadx.core.dex.instructions.NewArrayNode; -import jadx.core.dex.instructions.SwitchNode; +import jadx.core.dex.instructions.SwitchInsn; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.CodeVar; import jadx.core.dex.instructions.args.InsnArg; @@ -152,7 +152,7 @@ public class InsnGen { private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException { ClassNode pCls = mth.getParentClass(); - FieldNode fieldNode = pCls.dex().root().deepResolveField(field); + FieldNode fieldNode = pCls.root().deepResolveField(field); if (fieldNode != null) { FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE); if (replace != null) { @@ -190,7 +190,7 @@ public class InsnGen { } code.add('.'); } - FieldNode fieldNode = clsGen.getClassNode().dex().root().deepResolveField(field); + FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field); if (fieldNode != null) { code.attachAnnotation(fieldNode); } @@ -264,7 +264,7 @@ public class InsnGen { switch (insn.getType()) { case CONST_STR: String str = ((ConstStringNode) insn).getString(); - code.add(mth.dex().root().getStringUtils().unescapeString(str)); + code.add(mth.root().getStringUtils().unescapeString(str)); break; case CONST_CLASS: @@ -395,7 +395,7 @@ public class InsnGen { break; case FILL_ARRAY: - FillArrayNode arrayNode = (FillArrayNode) insn; + FillArrayInsn arrayNode = (FillArrayInsn) insn; if (fallback) { String arrStr = arrayNode.dataToString(); addArg(code, insn.getArg(0)); @@ -509,15 +509,16 @@ public class InsnGen { case SWITCH: fallbackOnlyInsn(insn); - SwitchNode sw = (SwitchNode) insn; + SwitchInsn sw = (SwitchInsn) insn; code.add("switch("); addArg(code, insn.getArg(0)); code.add(") {"); code.incIndent(); - for (int i = 0; i < sw.getCasesCount(); i++) { - String key = sw.getKeys()[i].toString(); - code.startLine("case ").add(key).add(": goto "); - code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';'); + int[] keys = sw.getKeys(); + int[] targets = sw.getTargets(); + for (int i = 0; i < keys.length; i++) { + code.startLine("case ").add(Integer.toString(keys[i])).add(": goto "); + code.add(MethodGen.getLabelName(targets[i])).add(';'); } code.startLine("default: goto "); code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';'); @@ -541,6 +542,21 @@ public class InsnGen { code.add(')'); break; + case MOVE_RESULT: + fallbackOnlyInsn(insn); + code.add("move-result"); + break; + + case FILL_ARRAY_DATA: + fallbackOnlyInsn(insn); + code.add("fill-array " + insn.toString()); + break; + + case SWITCH_DATA: + fallbackOnlyInsn(insn); + code.add(insn.toString()); + break; + default: throw new CodegenException(mth, "Unknown instruction: " + insn.getType()); } @@ -550,7 +566,7 @@ public class InsnGen { * In most cases must be combined with new array instructions. * Use one by one array fill (can be replaced with System.arrayCopy) */ - private void fillArray(CodeWriter code, FillArrayNode arrayNode) throws CodegenException { + private void fillArray(CodeWriter code, FillArrayInsn arrayNode) throws CodegenException { code.add("// fill-array-data instruction"); code.startLine(); List args = arrayNode.getLiteralArgs(arrayNode.getElementType()); @@ -605,7 +621,7 @@ public class InsnGen { } private void makeConstructor(ConstructorInsn insn, CodeWriter code) throws CodegenException { - ClassNode cls = mth.dex().resolveClass(insn.getClassType()); + ClassNode cls = mth.root().resolveClass(insn.getClassType()); if (cls != null && cls.isAnonymous() && !fallback) { cls.ensureProcessed(); inlineAnonymousConstructor(code, cls, insn); @@ -639,7 +655,7 @@ public class InsnGen { code.add('>'); } } - MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth()); + MethodNode callMth = mth.root().resolveMethod(insn.getCallMth()); generateMethodArguments(code, insn, 0, callMth); } @@ -673,7 +689,7 @@ public class InsnGen { } else { useClass(code, parent); } - MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth()); + MethodNode callMth = mth.root().resolveMethod(insn.getCallMth()); generateMethodArguments(code, insn, 0, callMth); code.add(' '); new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code, true); diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index 5840b40c5..fd7456754 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -7,9 +7,10 @@ import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; +import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.core.Consts; +import jadx.core.Jadx; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.annotations.MethodParameters; @@ -28,7 +29,7 @@ import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.visitors.DepthTraversal; -import jadx.core.dex.visitors.FallbackModeVisitor; +import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.utils.CodeGenUtils; import jadx.core.utils.InsnUtils; import jadx.core.utils.Utils; @@ -86,12 +87,12 @@ public class MethodGen { AccessInfo ai = mth.getAccessFlags(); // don't add 'abstract' and 'public' to methods in interface if (clsAccFlags.isInterface()) { - ai = ai.remove(AccessFlags.ACC_ABSTRACT); - ai = ai.remove(AccessFlags.ACC_PUBLIC); + ai = ai.remove(AccessFlags.ABSTRACT); + ai = ai.remove(AccessFlags.PUBLIC); } // don't add 'public' for annotations if (clsAccFlags.isAnnotation()) { - ai = ai.remove(AccessFlags.ACC_PUBLIC); + ai = ai.remove(AccessFlags.PUBLIC); } if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) { @@ -145,10 +146,10 @@ public class MethodGen { // add default value if in annotation class if (mth.getParentClass().getAccessFlags().isAnnotation()) { - Object def = annotationGen.getAnnotationDefaultValue(mth.getName()); + EncodedValue def = annotationGen.getAnnotationDefaultValue(mth.getName()); if (def != null) { code.add(" default "); - annotationGen.encodeValue(code, def); + annotationGen.encodeValue(mth.root(), code, def); } } return true; @@ -269,17 +270,17 @@ public class MethodGen { } public void addFallbackMethodCode(CodeWriter code, FallbackOption fallbackOption) { - if (mth.getInstructions() == null) { - // load original instructions - try { - mth.unload(); - mth.load(); - DepthTraversal.visit(new FallbackModeVisitor(), mth); - } catch (DecodeException e) { - LOG.error("Error reload instructions in fallback mode:", e); - code.startLine("// Can't load method instructions: " + e.getMessage()); - return; + // load original instructions + try { + mth.unload(); + mth.load(); + for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) { + DepthTraversal.visit(visitor, mth); } + } catch (DecodeException e) { + LOG.error("Error reload instructions in fallback mode:", e); + code.startLine("// Can't load method instructions: " + e.getMessage()); + return; } InsnNode[] insnArr = mth.getInstructions(); if (insnArr == null) { diff --git a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java index f04002e3d..bb394fc0d 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java @@ -10,11 +10,12 @@ import org.slf4j.LoggerFactory; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.FieldInitAttr; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.ForceReturnAttr; import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.info.ClassInfo; -import jadx.core.dex.instructions.SwitchNode; +import jadx.core.dex.instructions.SwitchInsn; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.CodeVar; import jadx.core.dex.instructions.args.InsnArg; @@ -26,7 +27,6 @@ import jadx.core.dex.nodes.IBlock; import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.InsnNode; -import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.dex.regions.Region; import jadx.core.dex.regions.SwitchRegion; import jadx.core.dex.regions.SwitchRegion.CaseInfo; @@ -251,7 +251,7 @@ public class RegionGen extends InsnGen { } private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException { - SwitchNode insn = (SwitchNode) BlockUtils.getLastInsn(sw.getHeader()); + SwitchInsn insn = (SwitchInsn) BlockUtils.getLastInsn(sw.getHeader()); Objects.requireNonNull(insn, "Switch insn not found in header"); InsnArg arg = insn.getArg(0); code.startLine("switch ("); @@ -288,7 +288,7 @@ public class RegionGen extends InsnGen { // print original value, sometimes replaced with incorrect field FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT); if (valueAttr != null) { - Object value = valueAttr.getValue(); + Object value = valueAttr.getEncodedValue(); if (value != null && valueAttr.getValueType() == FieldInitAttr.InitType.CONST) { code.add(" /*").add(value.toString()).add("*/"); } diff --git a/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java b/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java index 47c04be57..67ccab5b8 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java @@ -68,7 +68,7 @@ public class JsonCodeGen { JsonClass jsonCls = new JsonClass(); jsonCls.setPkg(classInfo.getAliasPkg()); - jsonCls.setDex(cls.dex().getDexFile().getName()); + jsonCls.setDex(cls.getInputPath().toString()); jsonCls.setName(classInfo.getFullName()); if (classInfo.hasAlias()) { jsonCls.setAlias(classInfo.getAliasFullName()); diff --git a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java index 54fc89373..4dcf48988 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java +++ b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java @@ -12,7 +12,6 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,9 +25,9 @@ import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; public class Deobfuscator { private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class); @@ -42,8 +41,7 @@ public class Deobfuscator { public static final String KOTLIN_METADATA_CLASSNAME_REGEX = "(L.*;)"; private final JadxArgs args; - @NotNull - private final List dexNodes; + private final RootNode root; private final DeobfPresets deobfPresets; private final Map clsMap = new LinkedHashMap<>(); @@ -67,9 +65,9 @@ public class Deobfuscator { private int fldIndex = 0; private int mthIndex = 0; - public Deobfuscator(JadxArgs args, @NotNull List dexNodes, Path deobfMapFile) { + public Deobfuscator(JadxArgs args, RootNode root, Path deobfMapFile) { this.args = args; - this.dexNodes = dexNodes; + this.root = root; this.minLength = args.getDeobfuscationMinLength(); this.maxLength = args.getDeobfuscationMaxLength(); @@ -109,15 +107,11 @@ public class Deobfuscator { } private void preProcess() { - for (DexNode dexNode : dexNodes) { - for (ClassNode cls : dexNode.getClasses()) { - Collections.addAll(reservedClsNames, cls.getPackage().split("\\.")); - } + for (ClassNode cls : root.getClasses()) { + Collections.addAll(reservedClsNames, cls.getPackage().split("\\.")); } - for (DexNode dexNode : dexNodes) { - for (ClassNode cls : dexNode.getClasses()) { - preProcessClass(cls); - } + for (ClassNode cls : root.getClasses()) { + preProcessClass(cls); } } @@ -126,10 +120,8 @@ public class Deobfuscator { if (DEBUG) { dumpAlias(); } - for (DexNode dexNode : dexNodes) { - for (ClassNode cls : dexNode.getClasses()) { - processClass(cls); - } + for (ClassNode cls : root.getClasses()) { + processClass(cls); } postProcess(); } @@ -212,14 +204,14 @@ public class Deobfuscator { if (added) { ArgType superClass = cls.getSuperClass(); if (superClass != null) { - ClassNode superNode = cls.dex().resolveClass(superClass); + ClassNode superNode = cls.root().resolveClass(superClass); if (superNode != null) { collectClassHierarchy(superNode, collected); } } for (ArgType argType : cls.getInterfaces()) { - ClassNode interfaceNode = cls.dex().resolveClass(argType); + ClassNode interfaceNode = cls.root().resolveClass(argType); if (interfaceNode != null) { collectClassHierarchy(interfaceNode, collected); } @@ -473,7 +465,7 @@ public class Deobfuscator { return null; } } - ClassNode otherCls = cls.root().searchClassByName(cls.getPackage() + '.' + name); + ClassNode otherCls = cls.root().resolveClass(cls.getPackage() + '.' + name); if (otherCls != null) { return null; } @@ -584,10 +576,8 @@ public class Deobfuscator { } private void dumpAlias() { - for (DexNode dexNode : dexNodes) { - for (ClassNode cls : dexNode.getClasses()) { - dumpClassAlias(cls); - } + for (ClassNode cls : root.getClasses()) { + dumpClassAlias(cls); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java index a6e96428d..e9be49206 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java @@ -27,7 +27,6 @@ import jadx.core.dex.attributes.nodes.RenameReasonAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.attributes.nodes.SourceFileAttr; import jadx.core.dex.nodes.IMethodDetails; -import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.SplitterBlockAttr; diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java index 26dc41b71..c1b28275f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java @@ -2,7 +2,7 @@ package jadx.core.dex.attributes; import java.util.List; -import jadx.core.dex.attributes.annotations.Annotation; +import jadx.api.plugins.input.data.annotations.IAnnotation; public abstract class AttrNode implements IAttributeNode { @@ -64,7 +64,7 @@ public abstract class AttrNode implements IAttributeNode { } @Override - public Annotation getAnnotation(String cls) { + public IAnnotation getAnnotation(String cls) { return storage.getAnnotation(cls); } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java index 2d342b9d1..adcf6c631 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java @@ -8,7 +8,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import jadx.core.dex.attributes.annotations.Annotation; +import jadx.api.plugins.input.data.annotations.IAnnotation; import jadx.core.dex.attributes.annotations.AnnotationsList; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -70,7 +70,7 @@ public class AttributeStorage { return (T) attributes.get(type); } - public Annotation getAnnotation(String cls) { + public IAnnotation getAnnotation(String cls) { AnnotationsList aList = get(AType.ANNOTATION_LIST); return aList == null ? null : aList.get(cls); } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/EmptyAttrStorage.java b/jadx-core/src/main/java/jadx/core/dex/attributes/EmptyAttrStorage.java index 3b0c06e22..e449bb498 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/EmptyAttrStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/EmptyAttrStorage.java @@ -3,7 +3,7 @@ package jadx.core.dex.attributes; import java.util.Collections; import java.util.List; -import jadx.core.dex.attributes.annotations.Annotation; +import jadx.api.plugins.input.data.annotations.IAnnotation; public final class EmptyAttrStorage extends AttributeStorage { @@ -23,7 +23,7 @@ public final class EmptyAttrStorage extends AttributeStorage { } @Override - public Annotation getAnnotation(String cls) { + public IAnnotation getAnnotation(String cls) { return null; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/FieldInitAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/FieldInitAttr.java similarity index 75% rename from jadx-core/src/main/java/jadx/core/dex/nodes/parser/FieldInitAttr.java rename to jadx-core/src/main/java/jadx/core/dex/attributes/FieldInitAttr.java index da8d6b543..7522511de 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/FieldInitAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/FieldInitAttr.java @@ -1,18 +1,16 @@ -package jadx.core.dex.nodes.parser; +package jadx.core.dex.attributes; -import jadx.core.dex.attributes.AType; -import jadx.core.dex.attributes.IAttribute; +import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; public class FieldInitAttr implements IAttribute { - public static final FieldInitAttr NULL_VALUE = constValue(null); + public static final FieldInitAttr NULL_VALUE = constValue(EncodedValue.NULL); public enum InitType { CONST, INSN - } private final Object value; @@ -25,7 +23,7 @@ public class FieldInitAttr implements IAttribute { this.insnMth = insnMth; } - public static FieldInitAttr constValue(Object value) { + public static FieldInitAttr constValue(EncodedValue value) { return new FieldInitAttr(InitType.CONST, value, null); } @@ -33,8 +31,8 @@ public class FieldInitAttr implements IAttribute { return new FieldInitAttr(InitType.INSN, insn, mth); } - public Object getValue() { - return value; + public EncodedValue getEncodedValue() { + return (EncodedValue) value; } public InsnNode getInsn() { diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/IAttributeNode.java b/jadx-core/src/main/java/jadx/core/dex/attributes/IAttributeNode.java index b6435d5b1..f227fa372 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/IAttributeNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/IAttributeNode.java @@ -2,7 +2,7 @@ package jadx.core.dex.attributes; import java.util.List; -import jadx.core.dex.attributes.annotations.Annotation; +import jadx.api.plugins.input.data.annotations.IAnnotation; public interface IAttributeNode { @@ -20,7 +20,7 @@ public interface IAttributeNode { T get(AType type); - Annotation getAnnotation(String cls); + IAnnotation getAnnotation(String cls); List getAll(AType> type); diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/Annotation.java b/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/Annotation.java deleted file mode 100644 index 99ad42917..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/Annotation.java +++ /dev/null @@ -1,47 +0,0 @@ -package jadx.core.dex.attributes.annotations; - -import java.util.Map; - -import jadx.core.dex.instructions.args.ArgType; - -public class Annotation { - - public enum Visibility { - BUILD, RUNTIME, SYSTEM - } - - private final Visibility visibility; - private final ArgType atype; - private final Map values; - - public Annotation(Visibility visibility, ArgType type, Map values) { - this.visibility = visibility; - this.atype = type; - this.values = values; - } - - public Visibility getVisibility() { - return visibility; - } - - public ArgType getType() { - return atype; - } - - public String getAnnotationClass() { - return atype.getObject(); - } - - public Map getValues() { - return values; - } - - public Object getDefaultValue() { - return values.get("value"); - } - - @Override - public String toString() { - return "Annotation[" + visibility + ", " + atype + ", " + values + ']'; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/AnnotationsList.java b/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/AnnotationsList.java index 3278ca538..3562d2d30 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/AnnotationsList.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/AnnotationsList.java @@ -1,33 +1,50 @@ package jadx.core.dex.attributes.annotations; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.annotations.IAnnotation; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.IAttribute; +import jadx.core.dex.nodes.ICodeNode; import jadx.core.utils.Utils; public class AnnotationsList implements IAttribute { - public static final AnnotationsList EMPTY = new AnnotationsList(Collections.emptyList()); - - private final Map map; - - public AnnotationsList(List anList) { - map = new HashMap<>(anList.size()); - for (Annotation a : anList) { - map.put(a.getAnnotationClass(), a); + public static void attach(ICodeNode node, List annotationList) { + AnnotationsList attrList = pack(annotationList); + if (attrList != null) { + node.addAttr(attrList); } } - public Annotation get(String className) { + @Nullable + public static AnnotationsList pack(List annotationList) { + if (annotationList.isEmpty()) { + return null; + } + Map annMap = new HashMap<>(annotationList.size()); + for (IAnnotation ann : annotationList) { + annMap.put(ann.getAnnotationClass(), ann); + } + return new AnnotationsList(annMap); + } + + private final Map map; + + public AnnotationsList(Map map) { + this.map = map; + } + + public IAnnotation get(String className) { return map.get(className); } - public Collection getAll() { + public Collection getAll() { return map.values(); } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/MethodParameters.java b/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/MethodParameters.java index 2f6529128..42d7e84fc 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/MethodParameters.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/MethodParameters.java @@ -3,16 +3,29 @@ package jadx.core.dex.attributes.annotations; import java.util.ArrayList; import java.util.List; +import jadx.api.plugins.input.data.annotations.IAnnotation; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.IAttribute; +import jadx.core.dex.nodes.ICodeNode; import jadx.core.utils.Utils; public class MethodParameters implements IAttribute { + public static void attach(ICodeNode node, List> annotationRefList) { + if (annotationRefList.isEmpty()) { + return; + } + List list = new ArrayList<>(annotationRefList.size()); + for (List annList : annotationRefList) { + list.add(AnnotationsList.pack(annList)); + } + node.addAttr(new MethodParameters(list)); + } + private final List paramList; - public MethodParameters(int paramCount) { - paramList = new ArrayList<>(paramCount); + public MethodParameters(List paramsList) { + this.paramList = paramsList; } public List getParamList() { diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LocalVarsDebugInfoAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LocalVarsDebugInfoAttr.java index ad2966e84..a62dbcad4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LocalVarsDebugInfoAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LocalVarsDebugInfoAttr.java @@ -2,21 +2,21 @@ package jadx.core.dex.attributes.nodes; import java.util.List; +import jadx.api.plugins.input.data.ILocalVar; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.IAttribute; -import jadx.core.dex.visitors.debuginfo.LocalVar; import jadx.core.utils.Utils; import static jadx.core.codegen.CodeWriter.NL; public class LocalVarsDebugInfoAttr implements IAttribute { - private final List localVars; + private final List localVars; - public LocalVarsDebugInfoAttr(List localVars) { + public LocalVarsDebugInfoAttr(List localVars) { this.localVars = localVars; } - public List getLocalVars() { + public List getLocalVars() { return localVars; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RegDebugInfoAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RegDebugInfoAttr.java index 531794775..3a3d8fe68 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RegDebugInfoAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RegDebugInfoAttr.java @@ -5,17 +5,12 @@ import java.util.Objects; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.IAttribute; import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.visitors.debuginfo.LocalVar; public class RegDebugInfoAttr implements IAttribute { private final ArgType type; private final String name; - public RegDebugInfoAttr(LocalVar var) { - this(var.getType(), var.getName()); - } - public RegDebugInfoAttr(ArgType type, String name) { this.type = type; this.name = name; diff --git a/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java index 7a7571be7..bb3f82efe 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java @@ -1,13 +1,12 @@ package jadx.core.dex.info; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; import jadx.core.Consts; import jadx.core.utils.exceptions.JadxRuntimeException; public class AccessInfo { - public static final int VISIBILITY_FLAGS = AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED | AccessFlags.ACC_PRIVATE; + public static final int VISIBILITY_FLAGS = AccessFlags.PUBLIC | AccessFlags.PROTECTED | AccessFlags.PRIVATE; private final int accFlags; public enum AFType { @@ -53,15 +52,15 @@ public class AccessInfo { } public boolean isPublic() { - return (accFlags & AccessFlags.ACC_PUBLIC) != 0; + return (accFlags & AccessFlags.PUBLIC) != 0; } public boolean isProtected() { - return (accFlags & AccessFlags.ACC_PROTECTED) != 0; + return (accFlags & AccessFlags.PROTECTED) != 0; } public boolean isPrivate() { - return (accFlags & AccessFlags.ACC_PRIVATE) != 0; + return (accFlags & AccessFlags.PRIVATE) != 0; } public boolean isPackagePrivate() { @@ -69,59 +68,59 @@ public class AccessInfo { } public boolean isAbstract() { - return (accFlags & AccessFlags.ACC_ABSTRACT) != 0; + return (accFlags & AccessFlags.ABSTRACT) != 0; } public boolean isInterface() { - return (accFlags & AccessFlags.ACC_INTERFACE) != 0; + return (accFlags & AccessFlags.INTERFACE) != 0; } public boolean isAnnotation() { - return (accFlags & AccessFlags.ACC_ANNOTATION) != 0; + return (accFlags & AccessFlags.ANNOTATION) != 0; } public boolean isNative() { - return (accFlags & AccessFlags.ACC_NATIVE) != 0; + return (accFlags & AccessFlags.NATIVE) != 0; } public boolean isStatic() { - return (accFlags & AccessFlags.ACC_STATIC) != 0; + return (accFlags & AccessFlags.STATIC) != 0; } public boolean isFinal() { - return (accFlags & AccessFlags.ACC_FINAL) != 0; + return (accFlags & AccessFlags.FINAL) != 0; } public boolean isConstructor() { - return (accFlags & AccessFlags.ACC_CONSTRUCTOR) != 0; + return (accFlags & AccessFlags.CONSTRUCTOR) != 0; } public boolean isEnum() { - return (accFlags & AccessFlags.ACC_ENUM) != 0; + return (accFlags & AccessFlags.ENUM) != 0; } public boolean isSynthetic() { - return (accFlags & AccessFlags.ACC_SYNTHETIC) != 0; + return (accFlags & AccessFlags.SYNTHETIC) != 0; } public boolean isBridge() { - return (accFlags & AccessFlags.ACC_BRIDGE) != 0; + return (accFlags & AccessFlags.BRIDGE) != 0; } public boolean isVarArgs() { - return (accFlags & AccessFlags.ACC_VARARGS) != 0; + return (accFlags & AccessFlags.VARARGS) != 0; } public boolean isSynchronized() { - return (accFlags & (AccessFlags.ACC_SYNCHRONIZED | AccessFlags.ACC_DECLARED_SYNCHRONIZED)) != 0; + return (accFlags & (AccessFlags.SYNCHRONIZED | AccessFlags.DECLARED_SYNCHRONIZED)) != 0; } public boolean isTransient() { - return (accFlags & AccessFlags.ACC_TRANSIENT) != 0; + return (accFlags & AccessFlags.TRANSIENT) != 0; } public boolean isVolatile() { - return (accFlags & AccessFlags.ACC_VOLATILE) != 0; + return (accFlags & AccessFlags.VOLATILE) != 0; } public AFType getType() { @@ -174,14 +173,14 @@ public class AccessInfo { break; case CLASS: - if ((accFlags & AccessFlags.ACC_STRICT) != 0) { + if ((accFlags & AccessFlags.STRICT) != 0) { code.append("strict "); } if (Consts.DEBUG) { - if ((accFlags & AccessFlags.ACC_SUPER) != 0) { + if ((accFlags & AccessFlags.SUPER) != 0) { code.append("/* super */ "); } - if ((accFlags & AccessFlags.ACC_ENUM) != 0) { + if ((accFlags & AccessFlags.ENUM) != 0) { code.append("/* enum */ "); } } @@ -209,25 +208,12 @@ public class AccessInfo { throw new JadxRuntimeException("Unknown visibility flags: " + getVisibility()); } - public String rawString() { - switch (type) { - case CLASS: - return AccessFlags.classString(accFlags); - case FIELD: - return AccessFlags.fieldString(accFlags); - case METHOD: - return AccessFlags.methodString(accFlags); - default: - return "?"; - } - } - public int rawValue() { return accFlags; } @Override public String toString() { - return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ')'; + return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + makeString() + ')'; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index e58e3b33c..a2b2f6e25 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -7,7 +7,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -37,13 +36,6 @@ public final class ClassInfo implements Comparable { return root.getInfoStorage().putCls(newClsInfo); } - public static ClassInfo fromDex(DexNode dex, int clsIndex) { - if (clsIndex == DexNode.NO_INDEX) { - throw new JadxRuntimeException("NO_INDEX for class"); - } - return fromType(dex.root(), dex.getType(clsIndex)); - } - public static ClassInfo fromName(RootNode root, String clsName) { return fromType(root, ArgType.object(clsName)); } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java index d71095fdd..a605c4d44 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java @@ -10,12 +10,12 @@ import org.jetbrains.annotations.Nullable; import jadx.api.JadxArgs; import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.FieldInitAttr; import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; -import jadx.core.dex.nodes.parser.FieldInitAttr; +import jadx.core.dex.nodes.RootNode; public class ConstStorage { @@ -72,10 +72,10 @@ public class ConstStorage { if (accFlags.isStatic() && accFlags.isFinal()) { FieldInitAttr fv = f.get(AType.FIELD_INIT); if (fv != null - && fv.getValue() != null + && fv.getEncodedValue() != null && fv.getValueType() == FieldInitAttr.InitType.CONST && fv != FieldInitAttr.NULL_VALUE) { - addConstField(cls, f, fv.getValue(), accFlags.isPublic()); + addConstField(cls, f, fv.getEncodedValue().getValue(), accFlags.isPublic()); } } } @@ -98,9 +98,9 @@ public class ConstStorage { if (!replaceEnabled) { return null; } - DexNode dex = cls.dex(); + RootNode root = cls.root(); if (value instanceof Integer) { - FieldNode rField = getResourceField((Integer) value, dex); + FieldNode rField = getResourceField((Integer) value, root); if (rField != null) { return rField; } @@ -125,7 +125,7 @@ public class ConstStorage { if (parentClass == null) { break; } - current = dex.resolveClass(parentClass); + current = root.resolveClass(parentClass); } if (searchGlobal) { return globalValues.get(value); @@ -134,12 +134,12 @@ public class ConstStorage { } @Nullable - private FieldNode getResourceField(Integer value, DexNode dex) { + private FieldNode getResourceField(Integer value, RootNode root) { String str = resourcesNames.get(value); if (str == null) { return null; } - ClassNode appResClass = dex.root().getAppResClass(); + ClassNode appResClass = root.getAppResClass(); if (appResClass == null) { return null; } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/FieldInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/FieldInfo.java index ba9a2e782..8aec5c2d2 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/FieldInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/FieldInfo.java @@ -2,11 +2,10 @@ package jadx.core.dex.info; import java.util.Objects; -import com.android.dex.FieldId; - +import jadx.api.plugins.input.data.IFieldData; import jadx.core.codegen.TypeGen; import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.DexNode; +import jadx.core.dex.nodes.RootNode; public final class FieldInfo { @@ -22,17 +21,15 @@ public final class FieldInfo { this.alias = name; } - public static FieldInfo from(DexNode dex, ClassInfo declClass, String name, ArgType type) { + public static FieldInfo from(RootNode root, ClassInfo declClass, String name, ArgType type) { FieldInfo field = new FieldInfo(declClass, name, type); - return dex.root().getInfoStorage().getField(field); + return root.getInfoStorage().getField(field); } - public static FieldInfo fromDex(DexNode dex, int index) { - FieldId field = dex.getFieldId(index); - return from(dex, - ClassInfo.fromDex(dex, field.getDeclaringClassIndex()), - dex.getString(field.getNameIndex()), - dex.getType(field.getTypeIndex())); + public static FieldInfo fromData(RootNode root, IFieldData fieldData) { + ClassInfo declClass = ClassInfo.fromName(root, fieldData.getParentClassType()); + FieldInfo field = new FieldInfo(declClass, fieldData.getName(), ArgType.parse(fieldData.getType())); + return root.getInfoStorage().getField(field); } public String getName() { diff --git a/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java index b0a79a5a2..9daff3731 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java @@ -4,14 +4,11 @@ import java.util.HashMap; import java.util.Map; import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.DexNode; -import jadx.core.utils.exceptions.JadxRuntimeException; public class InfoStorage { private final Map classes = new HashMap<>(); private final Map fields = new HashMap<>(); - private final Map methods = new HashMap<>(); // use only one MethodInfo instance private final Map uniqueMethods = new HashMap<>(); @@ -26,27 +23,6 @@ public class InfoStorage { } } - private static int generateMethodLookupId(DexNode dex, int mthId) { - return dex.getDexId() << 16 | mthId; - } - - public MethodInfo getMethod(DexNode dex, int mtdId) { - synchronized (methods) { - return methods.get(generateMethodLookupId(dex, mtdId)); - } - } - - public MethodInfo putMethod(DexNode dex, int mthId, MethodInfo methodInfo) { - synchronized (methods) { - MethodInfo uniqueMethodInfo = putMethod(methodInfo); - MethodInfo prev = methods.put(generateMethodLookupId(dex, mthId), uniqueMethodInfo); - if (prev != null && prev != uniqueMethodInfo) { - throw new JadxRuntimeException("Method lookup id collision: " + methodInfo + ", " + prev + ", " + uniqueMethodInfo); - } - return uniqueMethodInfo; - } - } - public MethodInfo putMethod(MethodInfo newMth) { synchronized (uniqueMethods) { MethodInfo prev = uniqueMethods.get(newMth); diff --git a/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java index f8058cb0f..a6b24ba26 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java @@ -5,12 +5,9 @@ import java.util.Objects; import org.jetbrains.annotations.Nullable; -import com.android.dex.MethodId; -import com.android.dex.ProtoId; - +import jadx.api.plugins.input.data.IMethodData; import jadx.core.codegen.TypeGen; import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.Utils; @@ -34,20 +31,13 @@ public final class MethodInfo implements Comparable { this.shortId = makeShortId(name, argTypes, retType); } - public static MethodInfo fromDex(DexNode dex, int mthIndex) { - MethodInfo storageMth = dex.root().getInfoStorage().getMethod(dex, mthIndex); - if (storageMth != null) { - return storageMth; - } - MethodId mthId = dex.getMethodId(mthIndex); - String mthName = dex.getString(mthId.getNameIndex()); - ClassInfo parentClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex()); - - ProtoId proto = dex.getProtoId(mthId.getProtoIndex()); - ArgType returnType = dex.getType(proto.getReturnTypeIndex()); - List args = dex.readParamList(proto.getParametersOffset()); - MethodInfo newMth = new MethodInfo(parentClass, mthName, args, returnType); - return dex.root().getInfoStorage().putMethod(dex, mthIndex, newMth); + public static MethodInfo fromData(RootNode root, IMethodData methodData) { + ArgType parentClsType = ArgType.parse(methodData.getParentClassType()); + ClassInfo parentClass = ClassInfo.fromType(root, parentClsType); + ArgType returnType = ArgType.parse(methodData.getReturnType()); + List args = Utils.collectionMap(methodData.getArgTypes(), ArgType::parse); + MethodInfo newMth = new MethodInfo(parentClass, methodData.getName(), args, returnType); + return root.getInfoStorage().putMethod(newMth); } public static MethodInfo fromDetails(RootNode rootNode, ClassInfo declClass, String name, List args, ArgType retType) { diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java index 6346acc99..a360a6678 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java @@ -1,7 +1,6 @@ package jadx.core.dex.instructions; -import com.android.dx.io.instructions.DecodedInstruction; - +import jadx.api.plugins.input.insns.InsnData; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; @@ -14,12 +13,12 @@ public class ArithNode extends InsnNode { private final ArithOp op; - public ArithNode(DecodedInstruction insn, ArithOp op, ArgType type, boolean literal) { + public ArithNode(InsnData insn, ArithOp op, ArgType type, boolean literal) { super(InsnType.ARITH, 2); this.op = op; setResult(InsnArg.reg(insn, 0, type)); - int rc = insn.getRegisterCount(); + int rc = insn.getRegsCount(); if (literal) { if (rc == 1) { // self diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayData.java similarity index 54% rename from jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayNode.java rename to jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayData.java index 3cc11f566..f24a5066b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayData.java @@ -4,8 +4,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction; - +import jadx.api.plugins.input.insns.custom.IArrayPayload; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.LiteralArg; @@ -13,7 +12,7 @@ import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.nodes.InsnNode; import jadx.core.utils.exceptions.JadxRuntimeException; -public final class FillArrayNode extends InsnNode { +public final class FillArrayData extends InsnNode { private static final ArgType ONE_BYTE_TYPE = ArgType.unknown(PrimitiveType.BOOLEAN, PrimitiveType.BYTE); private static final ArgType TWO_BYTES_TYPE = ArgType.unknown(PrimitiveType.SHORT, PrimitiveType.CHAR); @@ -22,21 +21,22 @@ public final class FillArrayNode extends InsnNode { private final Object data; private final int size; + private final int elemSize; private ArgType elemType; - public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) { - this(payload.getData(), payload.getSize(), getElementType(payload.getElementWidthUnit())); - addArg(InsnArg.reg(resReg, ArgType.array(elemType))); + public FillArrayData(IArrayPayload payload) { + this(payload.getData(), payload.getSize(), payload.getElementSize()); } - private FillArrayNode(Object data, int size, ArgType elemType) { - super(InsnType.FILL_ARRAY, 1); + private FillArrayData(Object data, int size, int elemSize) { + super(InsnType.FILL_ARRAY_DATA, 0); this.data = data; this.size = size; - this.elemType = elemType; + this.elemSize = elemSize; + this.elemType = getElementType(elemSize); } - private static ArgType getElementType(short elementWidthUnit) { + private static ArgType getElementType(int elementWidthUnit) { switch (elementWidthUnit) { case 1: return ONE_BYTE_TYPE; @@ -66,24 +66,29 @@ public final class FillArrayNode extends InsnNode { public List getLiteralArgs(ArgType type) { List list = new ArrayList<>(size); Object array = data; - if (array instanceof int[]) { - for (int b : (int[]) array) { - list.add(InsnArg.lit(b, type)); - } - } else if (array instanceof byte[]) { - for (byte b : (byte[]) array) { - list.add(InsnArg.lit(b, type)); - } - } else if (array instanceof short[]) { - for (short b : (short[]) array) { - list.add(InsnArg.lit(b, type)); - } - } else if (array instanceof long[]) { - for (long b : (long[]) array) { - list.add(InsnArg.lit(b, type)); - } - } else { - throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + type); + switch (elemSize) { + case 1: + for (byte b : (byte[]) array) { + list.add(InsnArg.lit(b, type)); + } + break; + case 2: + for (short b : (short[]) array) { + list.add(InsnArg.lit(b, type)); + } + break; + case 4: + for (int b : (int[]) array) { + list.add(InsnArg.lit(b, type)); + } + break; + case 8: + for (long b : (long[]) array) { + list.add(InsnArg.lit(b, type)); + } + break; + default: + throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + type); } return list; } @@ -93,32 +98,33 @@ public final class FillArrayNode extends InsnNode { if (this == obj) { return true; } - if (!(obj instanceof FillArrayNode) || !super.isSame(obj)) { + if (!(obj instanceof FillArrayData) || !super.isSame(obj)) { return false; } - FillArrayNode other = (FillArrayNode) obj; + FillArrayData other = (FillArrayData) obj; return elemType.equals(other.elemType) && data == other.data; } @Override public InsnNode copy() { - return copyCommonParams(new FillArrayNode(data, size, elemType)); + FillArrayData copy = new FillArrayData(data, size, elemSize); + copy.elemType = this.elemType; + return copyCommonParams(copy); } public String dataToString() { - if (data instanceof int[]) { - return Arrays.toString((int[]) data); + switch (elemSize) { + case 1: + return Arrays.toString((byte[]) data); + case 2: + return Arrays.toString((short[]) data); + case 4: + return Arrays.toString((int[]) data); + case 8: + return Arrays.toString((long[]) data); + default: + return "?"; } - if (data instanceof short[]) { - return Arrays.toString((short[]) data); - } - if (data instanceof byte[]) { - return Arrays.toString((byte[]) data); - } - if (data instanceof long[]) { - return Arrays.toString((long[]) data); - } - return "?"; } @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayInsn.java b/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayInsn.java new file mode 100644 index 000000000..b256f7e24 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayInsn.java @@ -0,0 +1,67 @@ +package jadx.core.dex.instructions; + +import java.util.List; +import java.util.Objects; + +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.instructions.args.LiteralArg; +import jadx.core.dex.nodes.InsnNode; + +public final class FillArrayInsn extends InsnNode { + private final int target; + private FillArrayData arrayData; + + public FillArrayInsn(InsnArg arg, int target) { + super(InsnType.FILL_ARRAY, 1); + this.target = target; + addArg(arg); + } + + public int getTarget() { + return target; + } + + public void setArrayData(FillArrayData arrayData) { + this.arrayData = arrayData; + } + + @Override + public boolean isSame(InsnNode obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof FillArrayInsn) || !super.isSame(obj)) { + return false; + } + FillArrayInsn other = (FillArrayInsn) obj; + return Objects.equals(arrayData, other.arrayData); + } + + @Override + public InsnNode copy() { + FillArrayInsn copy = new FillArrayInsn(getArg(0), target); + return copyCommonParams(copy); + } + + @Override + public String toString() { + return super.toString() + ", data: " + arrayData; + } + + public int getSize() { + return arrayData.getSize(); + } + + public ArgType getElementType() { + return arrayData.getElementType(); + } + + public List getLiteralArgs(ArgType elType) { + return arrayData.getLiteralArgs(elType); + } + + public String dataToString() { + return Objects.toString(arrayData); + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java index d137b913a..091b2816d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java @@ -2,8 +2,7 @@ package jadx.core.dex.instructions; import java.util.List; -import com.android.dx.io.instructions.DecodedInstruction; - +import jadx.api.plugins.input.insns.InsnData; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.PrimitiveType; @@ -21,12 +20,12 @@ public class IfNode extends GotoNode { private BlockNode thenBlock; private BlockNode elseBlock; - public IfNode(DecodedInstruction insn, IfOp op) { + public IfNode(InsnData insn, IfOp op) { super(InsnType.IF, insn.getTarget(), 2); this.op = op; ArgType argType = narrowTypeByOp(op); addArg(InsnArg.reg(insn, 0, argType)); - if (insn.getRegisterCount() == 1) { + if (insn.getRegsCount() == 1) { addArg(InsnArg.lit(0, argType)); } else { addArg(InsnArg.reg(insn, 1, argType)); diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java index 67b318a6a..d403c9cbc 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java @@ -1,21 +1,13 @@ package jadx.core.dex.instructions; -import java.io.EOFException; -import java.util.function.Predicate; - import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.android.dex.Code; -import com.android.dx.io.OpcodeInfo; -import com.android.dx.io.Opcodes; -import com.android.dx.io.instructions.DecodedInstruction; -import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction; -import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction; -import com.android.dx.io.instructions.ShortArrayCodeInput; -import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction; - +import jadx.api.plugins.input.data.ICodeReader; +import jadx.api.plugins.input.insns.InsnData; +import jadx.api.plugins.input.insns.custom.IArrayPayload; +import jadx.api.plugins.input.insns.custom.ISwitchPayload; import jadx.core.Consts; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; @@ -23,642 +15,497 @@ import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.RegisterArg; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.utils.InsnUtils; +import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.DecodeException; public class InsnDecoder { private static final Logger LOG = LoggerFactory.getLogger(InsnDecoder.class); private final MethodNode method; - private final DexNode dex; - private DecodedInstruction[] insnArr; + private final RootNode root; public InsnDecoder(MethodNode mthNode) { this.method = mthNode; - this.dex = method.dex(); + this.root = method.root(); } - public void decodeInsns(Code mthCode) throws DecodeException { - short[] encodedInstructions = mthCode.getInstructions(); - int size = encodedInstructions.length; - DecodedInstruction[] decoded = new DecodedInstruction[size]; - ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions); - try { - while (in.hasMore()) { - decoded[in.cursor()] = decodeRawInsn(in); + public InsnNode[] process(ICodeReader codeReader) { + InsnNode[] instructions = new InsnNode[codeReader.getInsnsCount()]; + codeReader.visitInstructions(rawInsn -> { + int offset = rawInsn.getOffset(); + InsnNode insn; + try { + rawInsn.decode(); + insn = decode(rawInsn, offset); + insn.setOffset(offset); + } catch (Exception e) { + LOG.error("Failed to decode insn: " + rawInsn + ", method: " + method, e); + insn = new InsnNode(InsnType.NOP, 0); } - } catch (Exception e) { - throw new DecodeException(method, e.getMessage(), e); - } - insnArr = decoded; - } - - private DecodedInstruction decodeRawInsn(ShortArrayCodeInput in) throws EOFException { - int opcodeUnit = in.read(); - int opcode = Opcodes.extractOpcodeFromUnit(opcodeUnit); - OpcodeInfo.Info opcodeInfo; - try { - opcodeInfo = OpcodeInfo.get(opcode); - } catch (IllegalArgumentException e) { - LOG.warn("Ignore decode error: '{}', replace with NOP instruction", e.getMessage()); - opcodeInfo = OpcodeInfo.NOP; - } - return opcodeInfo.getFormat().decode(opcodeUnit, in); - } - - public InsnNode[] process() throws DecodeException { - InsnNode[] instructions = new InsnNode[insnArr.length]; - for (int i = 0; i < insnArr.length; i++) { - DecodedInstruction rawInsn = insnArr[i]; - if (rawInsn != null) { - InsnNode insn = decode(rawInsn, i); - insn.setOffset(i); - instructions[i] = insn; - } else { - instructions[i] = null; - } - } - insnArr = null; + instructions[offset] = insn; + }); return instructions; } @NotNull - private InsnNode decode(DecodedInstruction insn, int offset) throws DecodeException { + private InsnNode decode(InsnData insn, int offset) throws DecodeException { switch (insn.getOpcode()) { - case Opcodes.NOP: - case Opcodes.PACKED_SWITCH_PAYLOAD: - case Opcodes.SPARSE_SWITCH_PAYLOAD: - case Opcodes.FILL_ARRAY_DATA_PAYLOAD: + case NOP: return new InsnNode(InsnType.NOP, 0); // move-result will be process in invoke and filled-new-array instructions - case Opcodes.MOVE_RESULT: - case Opcodes.MOVE_RESULT_WIDE: - case Opcodes.MOVE_RESULT_OBJECT: - return new InsnNode(InsnType.NOP, 0); + case MOVE_RESULT: + return insn(InsnType.MOVE_RESULT, InsnArg.reg(insn, 0, ArgType.UNKNOWN)); - case Opcodes.CONST: - case Opcodes.CONST_4: - case Opcodes.CONST_16: - case Opcodes.CONST_HIGH16: + case CONST: LiteralArg narrowLitArg = InsnArg.lit(insn, ArgType.NARROW); return insn(InsnType.CONST, InsnArg.reg(insn, 0, narrowLitArg.getType()), narrowLitArg); - case Opcodes.CONST_WIDE: - case Opcodes.CONST_WIDE_16: - case Opcodes.CONST_WIDE_32: - case Opcodes.CONST_WIDE_HIGH16: + case CONST_WIDE: LiteralArg wideLitArg = InsnArg.lit(insn, ArgType.WIDE); return insn(InsnType.CONST, InsnArg.reg(insn, 0, wideLitArg.getType()), wideLitArg); - case Opcodes.CONST_STRING: - case Opcodes.CONST_STRING_JUMBO: - InsnNode constStrInsn = new ConstStringNode(dex.getString(insn.getIndex())); + case CONST_STRING: + InsnNode constStrInsn = new ConstStringNode(insn.getIndexAsString()); constStrInsn.setResult(InsnArg.reg(insn, 0, ArgType.STRING)); return constStrInsn; - case Opcodes.CONST_CLASS: { - ArgType clsType = dex.getType(insn.getIndex()); + case CONST_CLASS: { + ArgType clsType = ArgType.parse(insn.getIndexAsType()); InsnNode constClsInsn = new ConstClassNode(clsType); - constClsInsn.setResult( - InsnArg.reg(insn, 0, ArgType.generic(Consts.CLASS_CLASS, clsType))); + constClsInsn.setResult(InsnArg.reg(insn, 0, ArgType.generic(Consts.CLASS_CLASS, clsType))); return constClsInsn; } - case Opcodes.MOVE: - case Opcodes.MOVE_16: - case Opcodes.MOVE_FROM16: + case MOVE: return insn(InsnType.MOVE, InsnArg.reg(insn, 0, ArgType.NARROW), InsnArg.reg(insn, 1, ArgType.NARROW)); - case Opcodes.MOVE_WIDE: - case Opcodes.MOVE_WIDE_16: - case Opcodes.MOVE_WIDE_FROM16: + case MOVE_WIDE: return insn(InsnType.MOVE, InsnArg.reg(insn, 0, ArgType.WIDE), InsnArg.reg(insn, 1, ArgType.WIDE)); - case Opcodes.MOVE_OBJECT: - case Opcodes.MOVE_OBJECT_16: - case Opcodes.MOVE_OBJECT_FROM16: + case MOVE_OBJECT: return insn(InsnType.MOVE, InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT), InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT)); - case Opcodes.ADD_INT: - case Opcodes.ADD_INT_2ADDR: + case ADD_INT: return arith(insn, ArithOp.ADD, ArgType.INT); - case Opcodes.ADD_DOUBLE: - case Opcodes.ADD_DOUBLE_2ADDR: + case ADD_DOUBLE: return arith(insn, ArithOp.ADD, ArgType.DOUBLE); - case Opcodes.ADD_FLOAT: - case Opcodes.ADD_FLOAT_2ADDR: + case ADD_FLOAT: return arith(insn, ArithOp.ADD, ArgType.FLOAT); - case Opcodes.ADD_LONG: - case Opcodes.ADD_LONG_2ADDR: + case ADD_LONG: return arith(insn, ArithOp.ADD, ArgType.LONG); - case Opcodes.ADD_INT_LIT8: - case Opcodes.ADD_INT_LIT16: + case ADD_INT_LIT: return arithLit(insn, ArithOp.ADD, ArgType.INT); - case Opcodes.SUB_INT: - case Opcodes.SUB_INT_2ADDR: + case SUB_INT: return arith(insn, ArithOp.SUB, ArgType.INT); - case Opcodes.RSUB_INT_LIT8: - case Opcodes.RSUB_INT: // LIT16 + case RSUB_INT: return new ArithNode(ArithOp.SUB, InsnArg.reg(insn, 0, ArgType.INT), InsnArg.lit(insn, ArgType.INT), InsnArg.reg(insn, 1, ArgType.INT)); - case Opcodes.SUB_LONG: - case Opcodes.SUB_LONG_2ADDR: + case SUB_LONG: return arith(insn, ArithOp.SUB, ArgType.LONG); - case Opcodes.SUB_FLOAT: - case Opcodes.SUB_FLOAT_2ADDR: + case SUB_FLOAT: return arith(insn, ArithOp.SUB, ArgType.FLOAT); - case Opcodes.SUB_DOUBLE: - case Opcodes.SUB_DOUBLE_2ADDR: + case SUB_DOUBLE: return arith(insn, ArithOp.SUB, ArgType.DOUBLE); - case Opcodes.MUL_INT: - case Opcodes.MUL_INT_2ADDR: + case MUL_INT: return arith(insn, ArithOp.MUL, ArgType.INT); - case Opcodes.MUL_DOUBLE: - case Opcodes.MUL_DOUBLE_2ADDR: + case MUL_DOUBLE: return arith(insn, ArithOp.MUL, ArgType.DOUBLE); - case Opcodes.MUL_FLOAT: - case Opcodes.MUL_FLOAT_2ADDR: + case MUL_FLOAT: return arith(insn, ArithOp.MUL, ArgType.FLOAT); - case Opcodes.MUL_LONG: - case Opcodes.MUL_LONG_2ADDR: + case MUL_LONG: return arith(insn, ArithOp.MUL, ArgType.LONG); - case Opcodes.MUL_INT_LIT8: - case Opcodes.MUL_INT_LIT16: + case MUL_INT_LIT: return arithLit(insn, ArithOp.MUL, ArgType.INT); - case Opcodes.DIV_INT: - case Opcodes.DIV_INT_2ADDR: + case DIV_INT: return arith(insn, ArithOp.DIV, ArgType.INT); - case Opcodes.REM_INT: - case Opcodes.REM_INT_2ADDR: + case REM_INT: return arith(insn, ArithOp.REM, ArgType.INT); - case Opcodes.REM_LONG: - case Opcodes.REM_LONG_2ADDR: + case REM_LONG: return arith(insn, ArithOp.REM, ArgType.LONG); - case Opcodes.REM_FLOAT: - case Opcodes.REM_FLOAT_2ADDR: + case REM_FLOAT: return arith(insn, ArithOp.REM, ArgType.FLOAT); - case Opcodes.REM_DOUBLE: - case Opcodes.REM_DOUBLE_2ADDR: + case REM_DOUBLE: return arith(insn, ArithOp.REM, ArgType.DOUBLE); - case Opcodes.DIV_DOUBLE: - case Opcodes.DIV_DOUBLE_2ADDR: + case DIV_DOUBLE: return arith(insn, ArithOp.DIV, ArgType.DOUBLE); - case Opcodes.DIV_FLOAT: - case Opcodes.DIV_FLOAT_2ADDR: + case DIV_FLOAT: return arith(insn, ArithOp.DIV, ArgType.FLOAT); - case Opcodes.DIV_LONG: - case Opcodes.DIV_LONG_2ADDR: + case DIV_LONG: return arith(insn, ArithOp.DIV, ArgType.LONG); - case Opcodes.DIV_INT_LIT8: - case Opcodes.DIV_INT_LIT16: + case DIV_INT_LIT: return arithLit(insn, ArithOp.DIV, ArgType.INT); - case Opcodes.REM_INT_LIT8: - case Opcodes.REM_INT_LIT16: + case REM_INT_LIT: return arithLit(insn, ArithOp.REM, ArgType.INT); - case Opcodes.AND_INT: - case Opcodes.AND_INT_2ADDR: + case AND_INT: return arith(insn, ArithOp.AND, ArgType.INT); - case Opcodes.AND_INT_LIT8: - case Opcodes.AND_INT_LIT16: + case AND_INT_LIT: return arithLit(insn, ArithOp.AND, ArgType.INT); - case Opcodes.XOR_INT_LIT8: - case Opcodes.XOR_INT_LIT16: + case XOR_INT_LIT: return arithLit(insn, ArithOp.XOR, ArgType.INT); - case Opcodes.AND_LONG: - case Opcodes.AND_LONG_2ADDR: + case AND_LONG: return arith(insn, ArithOp.AND, ArgType.LONG); - case Opcodes.OR_INT: - case Opcodes.OR_INT_2ADDR: + case OR_INT: return arith(insn, ArithOp.OR, ArgType.INT); - case Opcodes.OR_INT_LIT8: - case Opcodes.OR_INT_LIT16: + case OR_INT_LIT: return arithLit(insn, ArithOp.OR, ArgType.INT); - case Opcodes.XOR_INT: - case Opcodes.XOR_INT_2ADDR: + case XOR_INT: return arith(insn, ArithOp.XOR, ArgType.INT); - case Opcodes.OR_LONG: - case Opcodes.OR_LONG_2ADDR: + case OR_LONG: return arith(insn, ArithOp.OR, ArgType.LONG); - case Opcodes.XOR_LONG: - case Opcodes.XOR_LONG_2ADDR: + case XOR_LONG: return arith(insn, ArithOp.XOR, ArgType.LONG); - case Opcodes.USHR_INT: - case Opcodes.USHR_INT_2ADDR: + case USHR_INT: return arith(insn, ArithOp.USHR, ArgType.INT); - case Opcodes.USHR_LONG: - case Opcodes.USHR_LONG_2ADDR: + case USHR_LONG: return arith(insn, ArithOp.USHR, ArgType.LONG); - case Opcodes.SHL_INT: - case Opcodes.SHL_INT_2ADDR: + case SHL_INT: return arith(insn, ArithOp.SHL, ArgType.INT); - case Opcodes.SHL_LONG: - case Opcodes.SHL_LONG_2ADDR: + case SHL_LONG: return arith(insn, ArithOp.SHL, ArgType.LONG); - case Opcodes.SHR_INT: - case Opcodes.SHR_INT_2ADDR: + case SHR_INT: return arith(insn, ArithOp.SHR, ArgType.INT); - case Opcodes.SHR_LONG: - case Opcodes.SHR_LONG_2ADDR: + case SHR_LONG: return arith(insn, ArithOp.SHR, ArgType.LONG); - case Opcodes.SHL_INT_LIT8: + case SHL_INT_LIT: return arithLit(insn, ArithOp.SHL, ArgType.INT); - case Opcodes.SHR_INT_LIT8: + case SHR_INT_LIT: return arithLit(insn, ArithOp.SHR, ArgType.INT); - case Opcodes.USHR_INT_LIT8: + case USHR_INT_LIT: return arithLit(insn, ArithOp.USHR, ArgType.INT); - case Opcodes.NEG_INT: + case NEG_INT: return neg(insn, ArgType.INT); - case Opcodes.NEG_LONG: + case NEG_LONG: return neg(insn, ArgType.LONG); - case Opcodes.NEG_FLOAT: + case NEG_FLOAT: return neg(insn, ArgType.FLOAT); - case Opcodes.NEG_DOUBLE: + case NEG_DOUBLE: return neg(insn, ArgType.DOUBLE); - case Opcodes.NOT_INT: + case NOT_INT: return not(insn, ArgType.INT); - case Opcodes.NOT_LONG: + case NOT_LONG: return not(insn, ArgType.LONG); - case Opcodes.INT_TO_BYTE: + case INT_TO_BYTE: return cast(insn, ArgType.INT, ArgType.BYTE); - case Opcodes.INT_TO_CHAR: + case INT_TO_CHAR: return cast(insn, ArgType.INT, ArgType.CHAR); - case Opcodes.INT_TO_SHORT: + case INT_TO_SHORT: return cast(insn, ArgType.INT, ArgType.SHORT); - case Opcodes.INT_TO_FLOAT: + case INT_TO_FLOAT: return cast(insn, ArgType.INT, ArgType.FLOAT); - case Opcodes.INT_TO_DOUBLE: + case INT_TO_DOUBLE: return cast(insn, ArgType.INT, ArgType.DOUBLE); - case Opcodes.INT_TO_LONG: + case INT_TO_LONG: return cast(insn, ArgType.INT, ArgType.LONG); - case Opcodes.FLOAT_TO_INT: + case FLOAT_TO_INT: return cast(insn, ArgType.FLOAT, ArgType.INT); - case Opcodes.FLOAT_TO_DOUBLE: + case FLOAT_TO_DOUBLE: return cast(insn, ArgType.FLOAT, ArgType.DOUBLE); - case Opcodes.FLOAT_TO_LONG: + case FLOAT_TO_LONG: return cast(insn, ArgType.FLOAT, ArgType.LONG); - case Opcodes.DOUBLE_TO_INT: + case DOUBLE_TO_INT: return cast(insn, ArgType.DOUBLE, ArgType.INT); - case Opcodes.DOUBLE_TO_FLOAT: + case DOUBLE_TO_FLOAT: return cast(insn, ArgType.DOUBLE, ArgType.FLOAT); - case Opcodes.DOUBLE_TO_LONG: + case DOUBLE_TO_LONG: return cast(insn, ArgType.DOUBLE, ArgType.LONG); - case Opcodes.LONG_TO_INT: + case LONG_TO_INT: return cast(insn, ArgType.LONG, ArgType.INT); - case Opcodes.LONG_TO_FLOAT: + case LONG_TO_FLOAT: return cast(insn, ArgType.LONG, ArgType.FLOAT); - case Opcodes.LONG_TO_DOUBLE: + case LONG_TO_DOUBLE: return cast(insn, ArgType.LONG, ArgType.DOUBLE); - case Opcodes.IF_EQ: - case Opcodes.IF_EQZ: + case IF_EQ: + case IF_EQZ: return new IfNode(insn, IfOp.EQ); - case Opcodes.IF_NE: - case Opcodes.IF_NEZ: + case IF_NE: + case IF_NEZ: return new IfNode(insn, IfOp.NE); - case Opcodes.IF_GT: - case Opcodes.IF_GTZ: + case IF_GT: + case IF_GTZ: return new IfNode(insn, IfOp.GT); - case Opcodes.IF_GE: - case Opcodes.IF_GEZ: + case IF_GE: + case IF_GEZ: return new IfNode(insn, IfOp.GE); - case Opcodes.IF_LT: - case Opcodes.IF_LTZ: + case IF_LT: + case IF_LTZ: return new IfNode(insn, IfOp.LT); - case Opcodes.IF_LE: - case Opcodes.IF_LEZ: + case IF_LE: + case IF_LEZ: return new IfNode(insn, IfOp.LE); - case Opcodes.CMP_LONG: + case CMP_LONG: return cmp(insn, InsnType.CMP_L, ArgType.LONG); - case Opcodes.CMPL_FLOAT: + case CMPL_FLOAT: return cmp(insn, InsnType.CMP_L, ArgType.FLOAT); - case Opcodes.CMPL_DOUBLE: + case CMPL_DOUBLE: return cmp(insn, InsnType.CMP_L, ArgType.DOUBLE); - case Opcodes.CMPG_FLOAT: + case CMPG_FLOAT: return cmp(insn, InsnType.CMP_G, ArgType.FLOAT); - case Opcodes.CMPG_DOUBLE: + case CMPG_DOUBLE: return cmp(insn, InsnType.CMP_G, ArgType.DOUBLE); - case Opcodes.GOTO: - case Opcodes.GOTO_16: - case Opcodes.GOTO_32: + case GOTO: return new GotoNode(insn.getTarget()); - case Opcodes.THROW: + case THROW: return insn(InsnType.THROW, null, InsnArg.reg(insn, 0, ArgType.THROWABLE)); - case Opcodes.MOVE_EXCEPTION: + case MOVE_EXCEPTION: return insn(InsnType.MOVE_EXCEPTION, InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT_NO_ARRAY)); - case Opcodes.RETURN_VOID: + case RETURN_VOID: return new InsnNode(InsnType.RETURN, 0); - case Opcodes.RETURN: - case Opcodes.RETURN_WIDE: - case Opcodes.RETURN_OBJECT: + case RETURN: return insn(InsnType.RETURN, null, InsnArg.reg(insn, 0, method.getReturnType())); - case Opcodes.INSTANCE_OF: - InsnNode instInsn = new IndexInsnNode(InsnType.INSTANCE_OF, dex.getType(insn.getIndex()), 1); + case INSTANCE_OF: + InsnNode instInsn = new IndexInsnNode(InsnType.INSTANCE_OF, ArgType.parse(insn.getIndexAsType()), 1); instInsn.setResult(InsnArg.reg(insn, 0, ArgType.BOOLEAN)); instInsn.addArg(InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT)); return instInsn; - case Opcodes.CHECK_CAST: - ArgType castType = dex.getType(insn.getIndex()); + case CHECK_CAST: + ArgType castType = ArgType.parse(insn.getIndexAsType()); InsnNode checkCastInsn = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1); checkCastInsn.setResult(InsnArg.reg(insn, 0, castType)); checkCastInsn.addArg(InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT)); return checkCastInsn; - case Opcodes.IGET: - case Opcodes.IGET_BOOLEAN: - case Opcodes.IGET_BYTE: - case Opcodes.IGET_CHAR: - case Opcodes.IGET_SHORT: - case Opcodes.IGET_WIDE: - case Opcodes.IGET_OBJECT: - FieldInfo igetFld = FieldInfo.fromDex(dex, insn.getIndex()); + case IGET: + FieldInfo igetFld = FieldInfo.fromData(root, insn.getIndexAsField()); InsnNode igetInsn = new IndexInsnNode(InsnType.IGET, igetFld, 1); igetInsn.setResult(InsnArg.reg(insn, 0, tryResolveFieldType(igetFld))); igetInsn.addArg(InsnArg.reg(insn, 1, igetFld.getDeclClass().getType())); return igetInsn; - case Opcodes.IPUT: - case Opcodes.IPUT_BOOLEAN: - case Opcodes.IPUT_BYTE: - case Opcodes.IPUT_CHAR: - case Opcodes.IPUT_SHORT: - case Opcodes.IPUT_WIDE: - case Opcodes.IPUT_OBJECT: - FieldInfo iputFld = FieldInfo.fromDex(dex, insn.getIndex()); + case IPUT: + FieldInfo iputFld = FieldInfo.fromData(root, insn.getIndexAsField()); InsnNode iputInsn = new IndexInsnNode(InsnType.IPUT, iputFld, 2); iputInsn.addArg(InsnArg.reg(insn, 0, tryResolveFieldType(iputFld))); iputInsn.addArg(InsnArg.reg(insn, 1, iputFld.getDeclClass().getType())); return iputInsn; - case Opcodes.SGET: - case Opcodes.SGET_BOOLEAN: - case Opcodes.SGET_BYTE: - case Opcodes.SGET_CHAR: - case Opcodes.SGET_SHORT: - case Opcodes.SGET_WIDE: - case Opcodes.SGET_OBJECT: - FieldInfo sgetFld = FieldInfo.fromDex(dex, insn.getIndex()); + case SGET: + FieldInfo sgetFld = FieldInfo.fromData(root, insn.getIndexAsField()); InsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, sgetFld, 0); sgetInsn.setResult(InsnArg.reg(insn, 0, tryResolveFieldType(sgetFld))); return sgetInsn; - case Opcodes.SPUT: - case Opcodes.SPUT_BOOLEAN: - case Opcodes.SPUT_BYTE: - case Opcodes.SPUT_CHAR: - case Opcodes.SPUT_SHORT: - case Opcodes.SPUT_WIDE: - case Opcodes.SPUT_OBJECT: - FieldInfo sputFld = FieldInfo.fromDex(dex, insn.getIndex()); + case SPUT: + FieldInfo sputFld = FieldInfo.fromData(root, insn.getIndexAsField()); InsnNode sputInsn = new IndexInsnNode(InsnType.SPUT, sputFld, 1); sputInsn.addArg(InsnArg.reg(insn, 0, tryResolveFieldType(sputFld))); return sputInsn; - case Opcodes.ARRAY_LENGTH: + case ARRAY_LENGTH: InsnNode arrLenInsn = new InsnNode(InsnType.ARRAY_LENGTH, 1); arrLenInsn.setResult(InsnArg.reg(insn, 0, ArgType.INT)); arrLenInsn.addArg(InsnArg.reg(insn, 1, ArgType.array(ArgType.UNKNOWN))); return arrLenInsn; - case Opcodes.AGET: + case AGET: return arrayGet(insn, ArgType.INT_FLOAT); - case Opcodes.AGET_BOOLEAN: + case AGET_BOOLEAN: return arrayGet(insn, ArgType.BOOLEAN); - case Opcodes.AGET_BYTE: + case AGET_BYTE: return arrayGet(insn, ArgType.BYTE); - case Opcodes.AGET_CHAR: + case AGET_CHAR: return arrayGet(insn, ArgType.CHAR); - case Opcodes.AGET_SHORT: + case AGET_SHORT: return arrayGet(insn, ArgType.SHORT); - case Opcodes.AGET_WIDE: + case AGET_WIDE: return arrayGet(insn, ArgType.WIDE); - case Opcodes.AGET_OBJECT: + case AGET_OBJECT: return arrayGet(insn, ArgType.UNKNOWN_OBJECT); - case Opcodes.APUT: + case APUT: return arrayPut(insn, ArgType.INT_FLOAT); - case Opcodes.APUT_BOOLEAN: + case APUT_BOOLEAN: return arrayPut(insn, ArgType.BOOLEAN); - case Opcodes.APUT_BYTE: + case APUT_BYTE: return arrayPut(insn, ArgType.BYTE); - case Opcodes.APUT_CHAR: + case APUT_CHAR: return arrayPut(insn, ArgType.CHAR); - case Opcodes.APUT_SHORT: + case APUT_SHORT: return arrayPut(insn, ArgType.SHORT); - case Opcodes.APUT_WIDE: + case APUT_WIDE: return arrayPut(insn, ArgType.WIDE); - case Opcodes.APUT_OBJECT: + case APUT_OBJECT: return arrayPut(insn, ArgType.UNKNOWN_OBJECT); - case Opcodes.INVOKE_STATIC: + case INVOKE_STATIC: return invoke(insn, offset, InvokeType.STATIC, false); - case Opcodes.INVOKE_STATIC_RANGE: + case INVOKE_STATIC_RANGE: return invoke(insn, offset, InvokeType.STATIC, true); - case Opcodes.INVOKE_DIRECT: + case INVOKE_DIRECT: return invoke(insn, offset, InvokeType.DIRECT, false); - case Opcodes.INVOKE_INTERFACE: + case INVOKE_INTERFACE: return invoke(insn, offset, InvokeType.INTERFACE, false); - case Opcodes.INVOKE_SUPER: + case INVOKE_SUPER: return invoke(insn, offset, InvokeType.SUPER, false); - case Opcodes.INVOKE_VIRTUAL: + case INVOKE_VIRTUAL: return invoke(insn, offset, InvokeType.VIRTUAL, false); - case Opcodes.INVOKE_DIRECT_RANGE: + case INVOKE_DIRECT_RANGE: return invoke(insn, offset, InvokeType.DIRECT, true); - case Opcodes.INVOKE_INTERFACE_RANGE: + case INVOKE_INTERFACE_RANGE: return invoke(insn, offset, InvokeType.INTERFACE, true); - case Opcodes.INVOKE_SUPER_RANGE: + case INVOKE_SUPER_RANGE: return invoke(insn, offset, InvokeType.SUPER, true); - case Opcodes.INVOKE_VIRTUAL_RANGE: + case INVOKE_VIRTUAL_RANGE: return invoke(insn, offset, InvokeType.VIRTUAL, true); - case Opcodes.NEW_INSTANCE: - ArgType clsType = dex.getType(insn.getIndex()); + case NEW_INSTANCE: + ArgType clsType = ArgType.parse(insn.getIndexAsType()); IndexInsnNode newInstInsn = new IndexInsnNode(InsnType.NEW_INSTANCE, clsType, 0); newInstInsn.setResult(InsnArg.reg(insn, 0, clsType)); return newInstInsn; - case Opcodes.NEW_ARRAY: - ArgType arrType = dex.getType(insn.getIndex()); + case NEW_ARRAY: + ArgType arrType = ArgType.parse(insn.getIndexAsType()); return new NewArrayNode(arrType, InsnArg.reg(insn, 0, arrType), InsnArg.typeImmutableReg(insn, 1, ArgType.INT)); - case Opcodes.FILL_ARRAY_DATA: - return fillArray(insn); + case FILL_ARRAY_DATA: + return new FillArrayInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN_ARRAY), insn.getTarget()); + case FILL_ARRAY_DATA_PAYLOAD: + return new FillArrayData(((IArrayPayload) insn.getPayload())); - case Opcodes.FILLED_NEW_ARRAY: - return filledNewArray(insn, offset, false); - case Opcodes.FILLED_NEW_ARRAY_RANGE: - return filledNewArray(insn, offset, true); + case FILLED_NEW_ARRAY: + return filledNewArray(insn, false); + case FILLED_NEW_ARRAY_RANGE: + return filledNewArray(insn, true); - case Opcodes.PACKED_SWITCH: - return decodeSwitch(insn, offset, true); + case PACKED_SWITCH: + return new SwitchInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN), insn.getTarget(), true); + case SPARSE_SWITCH: + return new SwitchInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN), insn.getTarget(), false); - case Opcodes.SPARSE_SWITCH: - return decodeSwitch(insn, offset, false); + case PACKED_SWITCH_PAYLOAD: + case SPARSE_SWITCH_PAYLOAD: + return new SwitchData(((ISwitchPayload) insn.getPayload())); - case Opcodes.MONITOR_ENTER: + case MONITOR_ENTER: return insn(InsnType.MONITOR_ENTER, null, InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT)); - case Opcodes.MONITOR_EXIT: + case MONITOR_EXIT: return insn(InsnType.MONITOR_EXIT, null, InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT)); default: - throw new DecodeException("Unknown instruction: '" + OpcodeInfo.getName(insn.getOpcode()) + '\''); + throw new DecodeException("Unknown instruction: '" + insn + '\''); } } private ArgType tryResolveFieldType(FieldInfo igetFld) { - FieldNode fieldNode = dex.resolveField(igetFld); + FieldNode fieldNode = root.resolveField(igetFld); if (fieldNode != null) { return fieldNode.getType(); } return igetFld.getType(); } - private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) { - int payloadOffset = insn.getTarget(); - DecodedInstruction payload = getInsnByOffsetSkipNop(insnArr, payloadOffset); - Object[] keys; - int[] targets; - if (packed) { - PackedSwitchPayloadDecodedInstruction ps = (PackedSwitchPayloadDecodedInstruction) payload; - targets = ps.getTargets(); - keys = new Object[targets.length]; - int k = ps.getFirstKey(); - for (int i = 0; i < keys.length; i++) { - keys[i] = k++; - } - } else { - SparseSwitchPayloadDecodedInstruction ss = (SparseSwitchPayloadDecodedInstruction) payload; - targets = ss.getTargets(); - keys = new Object[targets.length]; - for (int i = 0; i < keys.length; i++) { - keys[i] = ss.getKeys()[i]; - } - } - // convert from relative to absolute offsets - for (int i = 0; i < targets.length; i++) { - targets[i] = targets[i] - payloadOffset + offset; - } - int nextOffset = getNextInsnOffset(insnArr, offset); - return new SwitchNode(InsnArg.reg(insn, 0, ArgType.NARROW), keys, targets, nextOffset, packed); - } - - private InsnNode fillArray(DecodedInstruction insn) { - DecodedInstruction payload = getInsnByOffsetSkipNop(insnArr, insn.getTarget()); - return new FillArrayNode(insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload); - } - - private InsnNode filledNewArray(DecodedInstruction insn, int offset, boolean isRange) { - int resReg = getMoveResultRegister(insnArr, offset); - ArgType arrType = dex.getType(insn.getIndex()); + private InsnNode filledNewArray(InsnData insn, boolean isRange) { + ArgType arrType = ArgType.parse(insn.getIndexAsType()); ArgType elType = arrType.getArrayElement(); boolean typeImmutable = elType.isPrimitive(); - int regsCount = insn.getRegisterCount(); + int regsCount = insn.getRegsCount(); InsnArg[] regs = new InsnArg[regsCount]; if (isRange) { - int r = insn.getA(); + int r = insn.getReg(0); for (int i = 0; i < regsCount; i++) { regs[i] = InsnArg.reg(r, elType, typeImmutable); r++; } } else { for (int i = 0; i < regsCount; i++) { - int regNum = InsnUtils.getArg(insn, i); + int regNum = insn.getReg(i); regs[i] = InsnArg.reg(regNum, elType, typeImmutable); } } InsnNode node = new FilledNewArrayNode(elType, regs.length); - node.setResult(resReg == -1 ? null : InsnArg.reg(resReg, arrType)); + // node.setResult(resReg == -1 ? null : InsnArg.reg(resReg, arrType)); for (InsnArg arg : regs) { node.addArg(arg); } return node; } - private InsnNode cmp(DecodedInstruction insn, InsnType itype, ArgType argType) { + private InsnNode cmp(InsnData insn, InsnType itype, ArgType argType) { InsnNode inode = new InsnNode(itype, 2); inode.setResult(InsnArg.reg(insn, 0, ArgType.INT)); inode.addArg(InsnArg.reg(insn, 1, argType)); @@ -666,20 +513,19 @@ public class InsnDecoder { return inode; } - private InsnNode cast(DecodedInstruction insn, ArgType from, ArgType to) { + private InsnNode cast(InsnData insn, ArgType from, ArgType to) { InsnNode inode = new IndexInsnNode(InsnType.CAST, to, 1); inode.setResult(InsnArg.reg(insn, 0, to)); inode.addArg(InsnArg.reg(insn, 1, from)); return inode; } - private InsnNode invoke(DecodedInstruction insn, int offset, InvokeType type, boolean isRange) { - int resReg = getMoveResultRegister(insnArr, offset); - MethodInfo mth = MethodInfo.fromDex(dex, insn.getIndex()); - return new InvokeNode(mth, insn, type, isRange, resReg); + private InsnNode invoke(InsnData insn, int offset, InvokeType type, boolean isRange) { + MethodInfo mth = MethodInfo.fromData(root, insn.getIndexAsMethod()); + return new InvokeNode(mth, insn, type, isRange); } - private InsnNode arrayGet(DecodedInstruction insn, ArgType argType) { + private InsnNode arrayGet(InsnData insn, ArgType argType) { InsnNode inode = new InsnNode(InsnType.AGET, 2); inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, argType)); inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType))); @@ -687,7 +533,7 @@ public class InsnDecoder { return inode; } - private InsnNode arrayPut(DecodedInstruction insn, ArgType argType) { + private InsnNode arrayPut(InsnData insn, ArgType argType) { InsnNode inode = new InsnNode(InsnType.APUT, 3); inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType))); inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL)); @@ -695,11 +541,11 @@ public class InsnDecoder { return inode; } - private InsnNode arith(DecodedInstruction insn, ArithOp op, ArgType type) { + private InsnNode arith(InsnData insn, ArithOp op, ArgType type) { return new ArithNode(insn, op, fixTypeForBitOps(op, type), false); } - private InsnNode arithLit(DecodedInstruction insn, ArithOp op, ArgType type) { + private InsnNode arithLit(InsnData insn, ArithOp op, ArgType type) { return new ArithNode(insn, op, fixTypeForBitOps(op, type), true); } @@ -711,14 +557,14 @@ public class InsnDecoder { return type; } - private InsnNode neg(DecodedInstruction insn, ArgType type) { + private InsnNode neg(InsnData insn, ArgType type) { InsnNode inode = new InsnNode(InsnType.NEG, 1); inode.setResult(InsnArg.reg(insn, 0, type)); inode.addArg(InsnArg.reg(insn, 1, type)); return inode; } - private InsnNode not(DecodedInstruction insn, ArgType type) { + private InsnNode not(InsnData insn, ArgType type) { InsnNode inode = new InsnNode(InsnType.NOT, 1); inode.setResult(InsnArg.reg(insn, 0, type)); inode.addArg(InsnArg.reg(insn, 1, type)); @@ -737,54 +583,4 @@ public class InsnDecoder { node.addArg(arg); return node; } - - private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) { - int nextOffset = getNextInsnOffsetSkipNop(insnArr, offset); - if (nextOffset >= 0) { - DecodedInstruction next = insnArr[nextOffset]; - int opc = next.getOpcode(); - if (opc == Opcodes.MOVE_RESULT - || opc == Opcodes.MOVE_RESULT_WIDE - || opc == Opcodes.MOVE_RESULT_OBJECT) { - return next.getA(); - } - } - return -1; - } - - private static DecodedInstruction getInsnByOffsetSkipNop(DecodedInstruction[] insnArr, int offset) { - DecodedInstruction payload = insnArr[offset]; - if (payload.getOpcode() == Opcodes.NOP) { - return insnArr[getNextInsnOffsetSkipNop(insnArr, offset)]; - } - return payload; - } - - public static int getNextInsnOffset(DecodedInstruction[] insnArr, int offset) { - return getNextInsnOffset(insnArr, offset, null); - } - - public static int getNextInsnOffsetSkipNop(DecodedInstruction[] insnArr, int offset) { - return getNextInsnOffset(insnArr, offset, i -> i.getOpcode() == Opcodes.NOP); - } - - public static int getNextInsnOffset(InsnNode[] insnArr, int offset) { - return getNextInsnOffset(insnArr, offset, null); - } - - public static int getNextInsnOffset(T[] insnArr, int offset, Predicate skip) { - int i = offset + 1; - while (i < insnArr.length) { - T insn = insnArr[i]; - if (insn == null || (skip != null && skip.test(insn))) { - i++; - } else { - break; - } - } - if (i >= insnArr.length) { - return -1; - } - return i; - } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java index eb184a591..448ba16d8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java @@ -23,6 +23,7 @@ public enum InsnType { CMP_G, IF, SWITCH, + SWITCH_DATA, MONITOR_ENTER, MONITOR_EXIT, @@ -32,6 +33,7 @@ public enum InsnType { ARRAY_LENGTH, FILL_ARRAY, + FILL_ARRAY_DATA, FILLED_NEW_ARRAY, AGET, @@ -47,6 +49,7 @@ public enum InsnType { SPUT, INVOKE, + MOVE_RESULT, // *** Additional instructions *** @@ -67,8 +70,5 @@ public enum InsnType { PHI, // fake insn to keep arguments which will be used in regions codegen - REGION_ARG, - - // TODO: now multidimensional arrays created using Array.newInstance function - NEW_MULTIDIM_ARRAY + REGION_ARG } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java index 9b850c919..75b2f2aa1 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java @@ -2,37 +2,31 @@ package jadx.core.dex.instructions; import org.jetbrains.annotations.Nullable; -import com.android.dx.io.instructions.DecodedInstruction; - +import jadx.api.plugins.input.insns.InsnData; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.nodes.InsnNode; -import jadx.core.utils.InsnUtils; public final class InvokeNode extends BaseInvokeNode { private final InvokeType type; private final MethodInfo mth; - public InvokeNode(MethodInfo mth, DecodedInstruction insn, InvokeType type, boolean isRange, int resReg) { + public InvokeNode(MethodInfo mth, InsnData insn, InvokeType type, boolean isRange) { super(InsnType.INVOKE, mth.getArgsCount() + (type == InvokeType.STATIC ? 0 : 1)); this.mth = mth; this.type = type; - if (resReg >= 0) { - setResult(InsnArg.reg(resReg, mth.getReturnType())); - } - - int k = isRange ? insn.getA() : 0; + int k = isRange ? insn.getReg(0) : 0; if (type != InvokeType.STATIC) { - int r = isRange ? k : InsnUtils.getArg(insn, k); + int r = isRange ? k : insn.getReg(k); addReg(r, mth.getDeclClass().getType()); k++; } for (ArgType arg : mth.getArgumentsTypes()) { - addReg(isRange ? k : InsnUtils.getArg(insn, k), arg); + addReg(isRange ? k : insn.getReg(k), arg); k += arg.getRegCount(); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchData.java b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchData.java new file mode 100644 index 000000000..fab77a831 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchData.java @@ -0,0 +1,41 @@ +package jadx.core.dex.instructions; + +import jadx.api.plugins.input.insns.custom.ISwitchPayload; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.utils.InsnUtils; + +public class SwitchData extends InsnNode { + private final int size; + private final int[] keys; + private final int[] targets; + + public SwitchData(ISwitchPayload payload) { + super(InsnType.SWITCH_DATA, 0); + this.size = payload.getSize(); + this.keys = payload.getKeys(); + this.targets = payload.getTargets(); + } + + public int getSize() { + return size; + } + + public int[] getKeys() { + return keys; + } + + public int[] getTargets() { + return targets; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("switch-data {"); + for (int i = 0; i < size; i++) { + sb.append(keys[i]).append("->").append(InsnUtils.formatOffset(targets[i])).append(", "); + } + sb.append('}'); + return sb.toString(); + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchInsn.java b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchInsn.java new file mode 100644 index 000000000..86db555ef --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchInsn.java @@ -0,0 +1,193 @@ +package jadx.core.dex.instructions; + +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import jadx.core.codegen.CodeWriter; +import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.utils.InsnUtils; +import jadx.core.utils.exceptions.JadxRuntimeException; + +import static jadx.core.utils.BlockUtils.getBlockByOffset; + +public class SwitchInsn extends TargetInsnNode { + private final int dataTarget; + private final boolean packed; // type of switch insn, if true can contain filler keys + @Nullable + private SwitchData switchData; + + private int def; // next instruction + + private Object[] modifiedKeys; + private BlockNode[] targetBlocks; + private BlockNode defTargetBlock; + + public SwitchInsn(InsnArg arg, int dataTarget, boolean packed) { + super(InsnType.SWITCH, 1); + addArg(arg); + this.dataTarget = dataTarget; + this.packed = packed; + } + + public void attachSwitchData(SwitchData data, int def) { + this.switchData = data; + this.def = def; + // fix targets + int switchOffset = getOffset(); + int size = data.getSize(); + int[] targets = data.getTargets(); + for (int i = 0; i < size; i++) { + targets[i] += switchOffset; + } + } + + @Override + public void initBlocks(BlockNode curBlock) { + if (switchData == null) { + throw new JadxRuntimeException("Switch data not yet attached"); + } + List successors = curBlock.getSuccessors(); + int[] targets = switchData.getTargets(); + int len = targets.length; + targetBlocks = new BlockNode[len]; + for (int i = 0; i < len; i++) { + targetBlocks[i] = getBlockByOffset(targets[i], successors); + } + defTargetBlock = getBlockByOffset(def, successors); + } + + @Override + public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) { + if (targetBlocks == null) { + return false; + } + int count = 0; + int len = targetBlocks.length; + for (int i = 0; i < len; i++) { + if (targetBlocks[i] == origin) { + targetBlocks[i] = replace; + count++; + } + } + if (defTargetBlock == origin) { + defTargetBlock = replace; + count++; + } + return count > 0; + } + + @Override + public boolean isSame(InsnNode obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof SwitchInsn) || !super.isSame(obj)) { + return false; + } + SwitchInsn other = (SwitchInsn) obj; + return dataTarget == other.dataTarget + && packed == other.packed; + } + + @Override + public InsnNode copy() { + SwitchInsn copy = new SwitchInsn(getArg(0), dataTarget, packed); + copy.switchData = switchData; + copy.def = def; + copy.targetBlocks = targetBlocks; + copy.defTargetBlock = defTargetBlock; + return copyCommonParams(copy); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(super.toString()); + if (switchData == null) { + sb.append("no payload"); + } else { + int size = switchData.getSize(); + int[] keys = switchData.getKeys(); + if (targetBlocks != null) { + for (int i = 0; i < size; i++) { + sb.append(CodeWriter.NL); + sb.append(" case ").append(keys[i]).append(": goto ").append(targetBlocks[i]); + } + if (def != -1) { + sb.append(CodeWriter.NL).append(" default: goto ").append(defTargetBlock); + } + } else { + int[] targets = switchData.getTargets(); + for (int i = 0; i < size; i++) { + sb.append(CodeWriter.NL); + sb.append(" case ").append(keys[i]).append(": goto ").append(InsnUtils.formatOffset(targets[i])); + } + if (def != -1) { + sb.append(CodeWriter.NL); + sb.append(" default: goto ").append(InsnUtils.formatOffset(def)); + } + } + } + return sb.toString(); + } + + public int getDataTarget() { + return dataTarget; + } + + public boolean isPacked() { + return packed; + } + + public int getDefaultCaseOffset() { + return def; + } + + @NotNull + private SwitchData getSwitchData() { + if (switchData == null) { + throw new JadxRuntimeException("Switch data not yet attached"); + } + return switchData; + } + + public int[] getTargets() { + return getSwitchData().getTargets(); + } + + public int[] getKeys() { + return getSwitchData().getKeys(); + } + + public Object getKey(int i) { + if (modifiedKeys != null) { + return modifiedKeys[i]; + } + return getSwitchData().getKeys()[i]; + } + + public void modifyKey(int i, Object newKey) { + if (modifiedKeys == null) { + int[] keys = getKeys(); + int caseCount = keys.length; + Object[] newKeys = new Object[caseCount]; + for (int j = 0; j < caseCount; j++) { + newKeys[j] = keys[j]; + } + modifiedKeys = newKeys; + } + modifiedKeys[i] = newKey; + } + + public BlockNode[] getTargetBlocks() { + return targetBlocks; + } + + public BlockNode getDefTargetBlock() { + return defTargetBlock; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java deleted file mode 100644 index 374ddd412..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java +++ /dev/null @@ -1,142 +0,0 @@ -package jadx.core.dex.instructions; - -import java.util.Arrays; -import java.util.List; - -import jadx.core.codegen.CodeWriter; -import jadx.core.dex.instructions.args.InsnArg; -import jadx.core.dex.nodes.BlockNode; -import jadx.core.dex.nodes.InsnNode; -import jadx.core.utils.InsnUtils; - -import static jadx.core.utils.BlockUtils.getBlockByOffset; - -public class SwitchNode extends TargetInsnNode { - - private final Object[] keys; - private final int[] targets; - private final int def; // next instruction - private final boolean packed; // type of switch insn, if true can contain filler keys - - private BlockNode[] targetBlocks; - private BlockNode defTargetBlock; - - public SwitchNode(InsnArg arg, Object[] keys, int[] targets, int def, boolean packed) { - this(keys, targets, def, packed); - addArg(arg); - } - - private SwitchNode(Object[] keys, int[] targets, int def, boolean packed) { - super(InsnType.SWITCH, 1); - this.keys = keys; - this.targets = targets; - this.def = def; - this.packed = packed; - } - - public int getCasesCount() { - return keys.length; - } - - public Object[] getKeys() { - return keys; - } - - public int[] getTargets() { - return targets; - } - - public int getDefaultCaseOffset() { - return def; - } - - public boolean isPacked() { - return packed; - } - - public BlockNode[] getTargetBlocks() { - return targetBlocks; - } - - public BlockNode getDefTargetBlock() { - return defTargetBlock; - } - - @Override - public void initBlocks(BlockNode curBlock) { - List successors = curBlock.getSuccessors(); - int len = targets.length; - targetBlocks = new BlockNode[len]; - for (int i = 0; i < len; i++) { - targetBlocks[i] = getBlockByOffset(targets[i], successors); - } - defTargetBlock = getBlockByOffset(def, successors); - } - - @Override - public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) { - if (targetBlocks == null) { - return false; - } - int count = 0; - int len = targetBlocks.length; - for (int i = 0; i < len; i++) { - if (targetBlocks[i] == origin) { - targetBlocks[i] = replace; - count++; - } - } - if (defTargetBlock == origin) { - defTargetBlock = replace; - count++; - } - return count > 0; - } - - @Override - public boolean isSame(InsnNode obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof SwitchNode) || !super.isSame(obj)) { - return false; - } - SwitchNode other = (SwitchNode) obj; - return def == other.def - && Arrays.equals(keys, other.keys) - && Arrays.equals(targets, other.targets); - } - - @Override - public InsnNode copy() { - SwitchNode copy = new SwitchNode(keys, targets, def, packed); - copy.targetBlocks = targetBlocks; - copy.defTargetBlock = defTargetBlock; - return copyCommonParams(copy); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(super.toString()); - if (targetBlocks == null) { - for (int i = 0; i < keys.length; i++) { - sb.append(CodeWriter.NL); - sb.append(" case ").append(keys[i]).append(": goto ").append(InsnUtils.formatOffset(targets[i])); - } - if (def != -1) { - sb.append(CodeWriter.NL); - sb.append(" default: goto ").append(InsnUtils.formatOffset(def)); - } - } else { - for (int i = 0; i < keys.length; i++) { - sb.append(CodeWriter.NL); - sb.append(" case ").append(keys[i]).append(": goto ").append(targetBlocks[i]); - } - if (def != -1) { - sb.append(CodeWriter.NL).append(" default: goto ").append(defTargetBlock); - } - } - return sb.toString(); - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java index 2ffec4f7a..9237ea5cb 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java @@ -10,7 +10,6 @@ import org.jetbrains.annotations.NotNull; import jadx.core.Consts; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.typeinference.TypeCompareEnum; import jadx.core.utils.Utils; @@ -83,6 +82,8 @@ public abstract class ArgType { return STRING; case Consts.CLASS_CLASS: return CLASS; + case Consts.CLASS_THROWABLE: + return THROWABLE; default: return new ObjectType(cleanObjectName); } @@ -739,12 +740,12 @@ public abstract class ArgType { return false; } - public static ArgType tryToResolveClassAlias(DexNode dex, ArgType type) { + public static ArgType tryToResolveClassAlias(RootNode root, ArgType type) { if (!type.isObject() || type.isGenericType()) { return type; } - ClassNode cls = dex.resolveClass(type); + ClassNode cls = root.resolveClass(type); if (cls == null) { return type; } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java index 207452a82..2c563698c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java @@ -5,14 +5,12 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.android.dx.io.instructions.DecodedInstruction; - +import jadx.api.plugins.input.insns.InsnData; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.utils.InsnRemover; -import jadx.core.utils.InsnUtils; import jadx.core.utils.exceptions.JadxRuntimeException; /** @@ -30,19 +28,19 @@ public abstract class InsnArg extends Typed { return new RegisterArg(regNum, type); } - public static RegisterArg reg(DecodedInstruction insn, int argNum, ArgType type) { - return reg(InsnUtils.getArg(insn, argNum), type); + public static RegisterArg reg(InsnData insn, int argNum, ArgType type) { + return reg(insn.getReg(argNum), type); } - public static RegisterArg typeImmutableIfKnownReg(DecodedInstruction insn, int argNum, ArgType type) { + public static RegisterArg typeImmutableIfKnownReg(InsnData insn, int argNum, ArgType type) { if (type.isTypeKnown()) { - return typeImmutableReg(InsnUtils.getArg(insn, argNum), type); + return typeImmutableReg(insn.getReg(argNum), type); } - return reg(InsnUtils.getArg(insn, argNum), type); + return reg(insn.getReg(argNum), type); } - public static RegisterArg typeImmutableReg(DecodedInstruction insn, int argNum, ArgType type) { - return typeImmutableReg(InsnUtils.getArg(insn, argNum), type); + public static RegisterArg typeImmutableReg(InsnData insn, int argNum, ArgType type) { + return typeImmutableReg(insn.getReg(argNum), type); } public static RegisterArg typeImmutableReg(int regNum, ArgType type) { @@ -61,7 +59,7 @@ public abstract class InsnArg extends Typed { return new LiteralArg(literal, type); } - public static LiteralArg lit(DecodedInstruction insn, ArgType type) { + public static LiteralArg lit(InsnData insn, ArgType type) { return lit(insn.getLiteral(), type); } 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 30629806b..ca1516337 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 @@ -1,6 +1,7 @@ package jadx.core.dex.nodes; import java.io.StringWriter; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -9,23 +10,22 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.android.dex.ClassData; -import com.android.dex.ClassData.Field; -import com.android.dex.ClassData.Method; -import com.android.dex.ClassDef; -import com.android.dex.Dex; - import jadx.api.ICodeCache; import jadx.api.ICodeInfo; +import jadx.api.plugins.input.data.IClassData; +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.data.annotations.IAnnotation; import jadx.core.Consts; import jadx.core.ProcessClass; import jadx.core.dex.attributes.AFlag; -import jadx.core.dex.attributes.annotations.Annotation; +import jadx.core.dex.attributes.FieldInitAttr; +import jadx.core.dex.attributes.annotations.AnnotationsList; import jadx.core.dex.attributes.nodes.NotificationAttrNode; import jadx.core.dex.attributes.nodes.SourceFileAttr; import jadx.core.dex.info.AccessInfo; @@ -35,12 +35,9 @@ import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.LiteralArg; -import jadx.core.dex.nodes.parser.AnnotationsParser; -import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.dex.nodes.parser.SignatureParser; -import jadx.core.dex.nodes.parser.StaticValuesParser; import jadx.core.utils.SmaliUtils; -import jadx.core.utils.exceptions.DecodeException; +import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; import static jadx.core.dex.nodes.ProcessState.LOADED; @@ -49,8 +46,10 @@ import static jadx.core.dex.nodes.ProcessState.NOT_LOADED; public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode { private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); - private final DexNode dex; + private final RootNode root; private final int clsDefOffset; + private final Path inputPath; + private final ClassInfo clsInfo; private AccessInfo accessFlags; private ArgType superClass; @@ -74,63 +73,38 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN // cache maps private Map mthInfoMap = Collections.emptyMap(); - public ClassNode(DexNode dex, ClassDef cls) { - this.dex = dex; - this.clsDefOffset = cls.getOffset(); - this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex()); + public ClassNode(RootNode root, IClassData cls) { + this.root = root; + this.inputPath = cls.getInputPath(); + this.clsDefOffset = cls.getClassDefOffset(); + this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType())); try { - if (cls.getSupertypeIndex() == DexNode.NO_INDEX) { - this.superClass = null; + String superType = cls.getSuperType(); + if (superType == null) { // only java.lang.Object don't have super class if (!clsInfo.getType().getObject().equals(Consts.CLASS_OBJECT)) { throw new JadxRuntimeException("No super class in " + clsInfo.getType()); } + this.superClass = null; } else { - this.superClass = dex.getType(cls.getSupertypeIndex()); + this.superClass = ArgType.object(superType); } - this.interfaces = new ArrayList<>(cls.getInterfaces().length); - for (short interfaceIdx : cls.getInterfaces()) { - this.interfaces.add(dex.getType(interfaceIdx)); - } - if (cls.getClassDataOffset() != 0) { - ClassData clsData = dex.readClassData(cls); - int mthsCount = clsData.getDirectMethods().length + clsData.getVirtualMethods().length; - int fieldsCount = clsData.getStaticFields().length + clsData.getInstanceFields().length; + this.interfaces = Utils.collectionMap(cls.getInterfacesTypes(), ArgType::object); - methods = new ArrayList<>(mthsCount); - fields = new ArrayList<>(fieldsCount); + methods = new ArrayList<>(); + fields = new ArrayList<>(); + cls.visitFieldsAndMethods( + fld -> fields.add(FieldNode.build(this, fld)), + mth -> methods.add(MethodNode.build(this, mth))); - for (Method mth : clsData.getDirectMethods()) { - methods.add(new MethodNode(this, mth, false)); - } - for (Method mth : clsData.getVirtualMethods()) { - methods.add(new MethodNode(this, mth, true)); - } - - for (Field f : clsData.getStaticFields()) { - fields.add(new FieldNode(this, f)); - } - loadStaticValues(cls, fields); - for (Field f : clsData.getInstanceFields()) { - fields.add(new FieldNode(this, f)); - } - } else { - methods = Collections.emptyList(); - fields = Collections.emptyList(); - } - - loadAnnotations(cls); + AnnotationsList.attach(this, cls.getAnnotations()); + loadStaticValues(cls, fields); initAccessFlags(cls); parseClassSignature(); setFieldsTypesFromSignature(); methods.forEach(MethodNode::initMethodTypes); - int sfIdx = cls.getSourceFileIndex(); - if (sfIdx != DexNode.NO_INDEX) { - String fileName = dex.getString(sfIdx); - addSourceFilenameAttr(fileName); - } - + addSourceFilenameAttr(cls.getSourceFile()); buildCache(); } catch (Exception e) { throw new JadxRuntimeException("Error decode class: " + clsInfo, e); @@ -140,11 +114,11 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN /** * Restore original access flags from Dalvik annotation if present */ - private void initAccessFlags(ClassDef cls) { + private void initAccessFlags(IClassData cls) { int accFlagsValue; - Annotation a = getAnnotation(Consts.DALVIK_INNER_CLASS); + IAnnotation a = getAnnotation(Consts.DALVIK_INNER_CLASS); if (a != null) { - accFlagsValue = (Integer) a.getValues().get("accessFlags"); + accFlagsValue = (Integer) a.getValues().get("accessFlags").getValue(); } else { accFlagsValue = cls.getAccessFlags(); } @@ -152,45 +126,37 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN } // empty synthetic class - public ClassNode(DexNode dex, String name, int accessFlags) { - this.dex = dex; + public ClassNode(RootNode root, String name, int accessFlags) { + this.root = root; + this.inputPath = null; this.clsDefOffset = 0; - this.clsInfo = ClassInfo.fromName(dex.root(), name); + this.clsInfo = ClassInfo.fromName(root, name); this.interfaces = new ArrayList<>(); this.methods = new ArrayList<>(); this.fields = new ArrayList<>(); this.accessFlags = new AccessInfo(accessFlags, AFType.CLASS); this.parentClass = this; - - dex.addClassNode(this); } - private void loadAnnotations(ClassDef cls) { - int offset = cls.getAnnotationsOffset(); - if (offset != 0) { - try { - new AnnotationsParser(this).parse(offset); - } catch (Exception e) { - LOG.error("Error parsing annotations in {}", this, e); - } + private void loadStaticValues(IClassData cls, List fields) { + if (fields.isEmpty()) { + return; } - } - - private void loadStaticValues(ClassDef cls, List staticFields) throws DecodeException { + List staticFields = fields.stream().filter(FieldNode::isStatic).collect(Collectors.toList()); for (FieldNode f : staticFields) { if (f.getAccessFlags().isFinal()) { // incorrect initialization will be removed if assign found in constructor f.addAttr(FieldInitAttr.NULL_VALUE); } } - int offset = cls.getStaticValuesOffset(); - if (offset == 0) { + List values = cls.getStaticFieldInitValues(); + int count = values.size(); + if (count == 0 || count > staticFields.size()) { return; } - Dex.Section section = dex.openSection(offset); - StaticValuesParser parser = new StaticValuesParser(dex, section); - parser.processFields(staticFields); - + for (int i = 0; i < count; i++) { + staticFields.get(i).addAttr(FieldInitAttr.constValue(values.get(i))); + } // process const fields root().getConstValues().processConstFields(this, staticFields); } @@ -382,10 +348,6 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN return root().getConstValues().getConstFieldByLiteralArg(this, arg); } - public FieldNode searchFieldById(int id) { - return searchField(FieldInfo.fromDex(dex, id)); - } - public FieldNode searchField(FieldInfo field) { for (FieldNode f : fields) { if (f.getFieldInfo().equals(field)) { @@ -441,14 +403,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN return null; } - public MethodNode searchMethodById(int id) { - return searchMethodByShortId(MethodInfo.fromDex(dex, id).getShortId()); - } - public ClassNode getParentClass() { if (parentClass == null) { if (clsInfo.isInner()) { - ClassNode parent = dex().resolveClass(clsInfo.getParentClass()); + ClassNode parent = root.resolveClass(clsInfo.getParentClass()); parentClass = parent == null ? this : parent; } else { parentClass = this; @@ -545,14 +503,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN this.accessFlags = accessFlags; } - @Override - public DexNode dex() { - return dex; - } - @Override public RootNode root() { - return dex.root(); + return root; } @Override @@ -600,9 +553,14 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN } protected static boolean getSmali(ClassNode classNode, StringWriter stringWriter) { + Path inputPath = classNode.inputPath; + if (inputPath == null) { + stringWriter.append(String.format("###### Class %s is created by jadx", classNode.getFullName())); + return false; + } stringWriter.append(String.format("###### Class %s (%s)", classNode.getFullName(), classNode.getRawName())); stringWriter.append(System.lineSeparator()); - return SmaliUtils.getSmaliCode(classNode.dex, classNode.clsDefOffset, stringWriter); + return SmaliUtils.getSmaliCode(inputPath, classNode.clsDefOffset, stringWriter); } public ProcessState getState() { @@ -621,6 +579,11 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN this.dependencies = dependencies; } + @Override + public Path getInputPath() { + return inputPath; + } + @Override public int hashCode() { return clsInfo.hashCode(); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java deleted file mode 100644 index febb7a2ae..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java +++ /dev/null @@ -1,310 +0,0 @@ -package jadx.core.dex.nodes; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.android.dex.ClassData; -import com.android.dex.ClassData.Method; -import com.android.dex.ClassDef; -import com.android.dex.Code; -import com.android.dex.Dex; -import com.android.dex.Dex.Section; -import com.android.dex.FieldId; -import com.android.dex.MethodId; -import com.android.dex.ProtoId; -import com.android.dex.TypeList; - -import jadx.core.dex.info.ClassInfo; -import jadx.core.dex.info.FieldInfo; -import jadx.core.dex.info.MethodInfo; -import jadx.core.dex.instructions.args.ArgType; -import jadx.core.utils.ErrorsCounter; -import jadx.core.utils.files.DexFile; - -public class DexNode implements IDexNode { - private static final Logger LOG = LoggerFactory.getLogger(DexNode.class); - - public static final int NO_INDEX = -1; - - private final RootNode root; - private final Dex dexBuf; - private final DexFile file; - private final int dexId; - - private final List classes = new ArrayList<>(); - private final Map clsMap = new HashMap<>(); - private final ArgType[] typesCache; - - public DexNode(RootNode root, DexFile input, int dexId) { - this.root = root; - this.file = input; - this.dexBuf = input.getDexBuf(); - this.dexId = dexId; - this.typesCache = new ArgType[dexBuf.typeIds().size()]; - } - - public void loadClasses() { - for (ClassDef cls : dexBuf.classDefs()) { - try { - addClassNode(new ClassNode(this, cls)); - } catch (Exception e) { - addDummyClass(cls, e); - } - } - // sort classes by name, expect top classes before inner - classes.sort(Comparator.comparing(ClassNode::getFullName)); - } - - private void addDummyClass(ClassDef classDef, Exception exc) { - int typeIndex = classDef.getTypeIndex(); - String name = null; - try { - ClassInfo clsInfo = ClassInfo.fromDex(this, typeIndex); - if (clsInfo != null) { - name = clsInfo.getShortName(); - } - } catch (Exception e) { - LOG.error("Failed to get name for class with type {}", typeIndex, e); - } - if (name == null || name.isEmpty()) { - name = "CLASS_" + typeIndex; - } - ClassNode clsNode = new ClassNode(this, name, classDef.getAccessFlags()); - ErrorsCounter.error(clsNode, "Load error", exc); - addClassNode(clsNode); - } - - public void addClassNode(ClassNode clsNode) { - classes.add(clsNode); - clsMap.put(clsNode.getClassInfo(), clsNode); - } - - void initInnerClasses() { - // move inner classes - List inner = new ArrayList<>(); - for (ClassNode cls : classes) { - if (cls.getClassInfo().isInner()) { - inner.add(cls); - } - } - List updated = new ArrayList<>(); - for (ClassNode cls : inner) { - ClassInfo clsInfo = cls.getClassInfo(); - ClassNode parent = resolveClass(clsInfo.getParentClass()); - if (parent == null) { - clsMap.remove(clsInfo); - clsInfo.notInner(root); - clsMap.put(clsInfo, cls); - updated.add(cls); - } else { - parent.addInnerClass(cls); - } - } - // reload names for inner classes of updated parents - for (ClassNode updCls : updated) { - for (ClassNode innerCls : updCls.getInnerClasses()) { - innerCls.getClassInfo().updateNames(root); - } - } - } - - public List getClasses() { - return classes; - } - - @Nullable - ClassNode resolveClassLocal(ClassInfo clsInfo) { - return clsMap.get(clsInfo); - } - - @Nullable - public ClassNode resolveClass(ClassInfo clsInfo) { - ClassNode classNode = resolveClassLocal(clsInfo); - if (classNode != null) { - return classNode; - } - return root.resolveClass(clsInfo); - } - - @Nullable - public ClassNode resolveClass(@NotNull ArgType type) { - if (type.isGeneric()) { - type = ArgType.object(type.getObject()); - } - return resolveClass(ClassInfo.fromType(root, type)); - } - - @Nullable - public MethodNode resolveMethod(@NotNull MethodInfo mth) { - ClassNode cls = resolveClass(mth.getDeclClass()); - if (cls != null) { - return cls.searchMethod(mth); - } - return null; - } - - @Nullable - MethodNode deepResolveMethod(@NotNull ClassNode cls, String signature) { - for (MethodNode m : cls.getMethods()) { - if (m.getMethodInfo().getShortId().startsWith(signature)) { - return m; - } - } - MethodNode found; - ArgType superClass = cls.getSuperClass(); - if (superClass != null) { - ClassNode superNode = resolveClass(superClass); - if (superNode != null) { - found = deepResolveMethod(superNode, signature); - if (found != null) { - return found; - } - } - } - for (ArgType iFaceType : cls.getInterfaces()) { - ClassNode iFaceNode = resolveClass(iFaceType); - if (iFaceNode != null) { - found = deepResolveMethod(iFaceNode, signature); - if (found != null) { - return found; - } - } - } - return null; - } - - @Nullable - public FieldNode resolveField(FieldInfo field) { - ClassNode cls = resolveClass(field.getDeclClass()); - if (cls != null) { - return cls.searchField(field); - } - return null; - } - - @Nullable - FieldNode deepResolveField(@NotNull ClassNode cls, FieldInfo fieldInfo) { - FieldNode field = cls.searchFieldByNameAndType(fieldInfo); - if (field != null) { - return field; - } - ArgType superClass = cls.getSuperClass(); - if (superClass != null) { - ClassNode superNode = resolveClass(superClass); - if (superNode != null) { - FieldNode found = deepResolveField(superNode, fieldInfo); - if (found != null) { - return found; - } - } - } - for (ArgType iFaceType : cls.getInterfaces()) { - ClassNode iFaceNode = resolveClass(iFaceType); - if (iFaceNode != null) { - FieldNode found = deepResolveField(iFaceNode, fieldInfo); - if (found != null) { - return found; - } - } - } - return null; - } - - public DexFile getDexFile() { - return file; - } - - // DexBuffer wrappers - - public String getString(int index) { - if (index == DexNode.NO_INDEX) { - return null; - } - return dexBuf.strings().get(index); - } - - public ArgType getType(int index) { - if (index == DexNode.NO_INDEX) { - return null; - } - ArgType type = typesCache[index]; - if (type != null) { - return type; - } - // no synchronization because exactly one ArgType instance not needed, just reduce instances count - // note: same types but different instances will exist in other dex nodes - ArgType parsedType = ArgType.parse(getString(dexBuf.typeIds().get(index))); - typesCache[index] = parsedType; - return parsedType; - } - - public MethodId getMethodId(int mthIndex) { - return dexBuf.methodIds().get(mthIndex); - } - - public FieldId getFieldId(int fieldIndex) { - return dexBuf.fieldIds().get(fieldIndex); - } - - public ProtoId getProtoId(int protoIndex) { - return dexBuf.protoIds().get(protoIndex); - } - - public ClassData readClassData(ClassDef cls) { - return dexBuf.readClassData(cls); - } - - public List readParamList(int parametersOffset) { - TypeList paramList = dexBuf.readTypeList(parametersOffset); - List args = new ArrayList<>(paramList.getTypes().length); - for (short t : paramList.getTypes()) { - args.add(getType(t)); - } - return Collections.unmodifiableList(args); - } - - public Code readCode(Method mth) { - return dexBuf.readCode(mth); - } - - public Section openSection(int offset) { - return dexBuf.open(offset); - } - - public boolean checkOffset(int dataOffset) { - return dataOffset >= 0 && dataOffset < dexBuf.getLength(); - } - - @Override - public RootNode root() { - return root; - } - - @Override - public DexNode dex() { - return this; - } - - @Override - public String typeName() { - return "dex"; - } - - public int getDexId() { - return dexId; - } - - @Override - public String toString() { - return "DEX: " + file; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java index fa1d142b2..305a38e54 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java @@ -1,7 +1,9 @@ package jadx.core.dex.nodes; -import com.android.dex.ClassData.Field; +import java.nio.file.Path; +import jadx.api.plugins.input.data.IFieldData; +import jadx.core.dex.attributes.annotations.AnnotationsList; import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo.AFType; @@ -10,19 +12,21 @@ import jadx.core.dex.instructions.args.ArgType; public class FieldNode extends LineAttrNode implements ICodeNode { - private final ClassNode parent; + private final ClassNode parentClass; private final FieldInfo fieldInfo; private AccessInfo accFlags; private ArgType type; - public FieldNode(ClassNode cls, Field field) { - this(cls, FieldInfo.fromDex(cls.dex(), field.getFieldIndex()), - field.getAccessFlags()); + public static FieldNode build(ClassNode cls, IFieldData fieldData) { + FieldInfo fieldInfo = FieldInfo.fromData(cls.root(), fieldData); + FieldNode fieldNode = new FieldNode(cls, fieldInfo, fieldData.getAccessFlags()); + AnnotationsList.attach(fieldNode, fieldData.getAnnotations()); + return fieldNode; } public FieldNode(ClassNode cls, FieldInfo fieldInfo, int accessFlags) { - this.parent = cls; + this.parentClass = cls; this.fieldInfo = fieldInfo; this.type = fieldInfo.getType(); this.accFlags = new AccessInfo(accessFlags, AFType.FIELD); @@ -42,6 +46,10 @@ public class FieldNode extends LineAttrNode implements ICodeNode { this.accFlags = accFlags; } + public boolean isStatic() { + return accFlags.isStatic(); + } + public String getName() { return fieldInfo.getName(); } @@ -59,7 +67,7 @@ public class FieldNode extends LineAttrNode implements ICodeNode { } public ClassNode getParentClass() { - return parent; + return parentClass; } @Override @@ -68,13 +76,13 @@ public class FieldNode extends LineAttrNode implements ICodeNode { } @Override - public DexNode dex() { - return parent.dex(); + public Path getInputPath() { + return parentClass.getInputPath(); } @Override public RootNode root() { - return parent.root(); + return parentClass.root(); } @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java index 7e7dee5a5..c8ec7da4a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java @@ -1,10 +1,12 @@ package jadx.core.dex.nodes; +import java.nio.file.Path; + public interface IDexNode { String typeName(); - DexNode dex(); - RootNode root(); + + Path getInputPath(); } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java index bd579e737..e81b7cd55 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java @@ -8,8 +8,7 @@ import java.util.Objects; import org.jetbrains.annotations.Nullable; -import com.android.dx.io.instructions.DecodedInstruction; - +import jadx.api.plugins.input.insns.InsnData; import jadx.core.codegen.CodeWriter; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.nodes.LineAttrNode; @@ -171,7 +170,7 @@ public class InsnNode extends LineAttrNode { return -1; } - protected void addReg(DecodedInstruction insn, int i, ArgType type) { + protected void addReg(InsnData insn, int i, ArgType type) { addArg(InsnArg.reg(insn, i, type)); } @@ -183,7 +182,7 @@ public class InsnNode extends LineAttrNode { addArg(InsnArg.lit(literal, type)); } - protected void addLit(DecodedInstruction insn, ArgType type) { + protected void addLit(InsnData insn, ArgType type) { addArg(InsnArg.lit(insn, type)); } @@ -259,7 +258,6 @@ public class InsnNode extends LineAttrNode { case FILL_ARRAY: case FILLED_NEW_ARRAY: case NEW_ARRAY: - case NEW_MULTIDIM_ARRAY: case STR_CONCAT: return true; @@ -470,6 +468,9 @@ public class InsnNode extends LineAttrNode { } protected void appendArgs(StringBuilder sb) { + if (arguments.isEmpty()) { + return; + } String argsStr = Utils.listToString(arguments); if (argsStr.length() < 120) { sb.append(argsStr); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index 957fa0d89..7b3e2bffb 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -1,47 +1,39 @@ package jadx.core.dex.nodes; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Objects; -import java.util.Set; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.android.dex.ClassData.Method; -import com.android.dex.Code; -import com.android.dex.Code.CatchHandler; -import com.android.dex.Code.Try; - +import jadx.api.plugins.input.data.ICodeReader; +import jadx.api.plugins.input.data.IDebugInfo; +import jadx.api.plugins.input.data.IMethodData; +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.data.annotations.IAnnotation; import jadx.core.Consts; import jadx.core.dex.attributes.AFlag; -import jadx.core.dex.attributes.AType; -import jadx.core.dex.attributes.annotations.Annotation; -import jadx.core.dex.attributes.nodes.JumpInfo; +import jadx.core.dex.attributes.annotations.AnnotationsList; +import jadx.core.dex.attributes.annotations.MethodParameters; import jadx.core.dex.attributes.nodes.LoopInfo; import jadx.core.dex.attributes.nodes.NotificationAttrNode; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo.AFType; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.MethodInfo; -import jadx.core.dex.instructions.GotoNode; -import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.InsnDecoder; -import jadx.core.dex.instructions.InsnType; -import jadx.core.dex.instructions.SwitchNode; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.nodes.parser.SignatureParser; import jadx.core.dex.regions.Region; -import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExceptionHandler; -import jadx.core.dex.trycatch.TryCatchBlock; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -55,13 +47,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, private final ClassNode parentClass; private AccessInfo accFlags; - private final Method methodData; + private final ICodeReader codeReader; private final boolean methodIsVirtual; private boolean noCode; private int regsCount; - private int codeSize; - private int debugInfoOffset; private boolean loaded; @@ -82,13 +72,20 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, private List loops; private Region region; - public MethodNode(ClassNode classNode, Method mthData, boolean isVirtual) { - this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex()); + public static MethodNode build(ClassNode classNode, IMethodData methodData) { + MethodNode methodNode = new MethodNode(classNode, methodData); + AnnotationsList.attach(methodNode, methodData.getAnnotations()); + MethodParameters.attach(methodNode, methodData.getParamsAnnotations()); + return methodNode; + } + + public MethodNode(ClassNode classNode, IMethodData mthData) { + this.mthInfo = MethodInfo.fromData(classNode.root(), mthData); this.parentClass = classNode; this.accFlags = new AccessInfo(mthData.getAccessFlags(), AFType.METHOD); - this.noCode = mthData.getCodeOffset() == 0; - this.methodData = noCode ? null : mthData; - this.methodIsVirtual = isVirtual; + this.noCode = mthData.getCodeReader() == null; + this.codeReader = noCode ? null : mthData.getCodeReader().copy(); + this.methodIsVirtual = !mthData.isDirect(); unload(); } @@ -122,26 +119,15 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, loaded = true; if (noCode) { regsCount = 0; - codeSize = 0; // TODO: registers not needed without code initArguments(this.argTypes); return; } - DexNode dex = parentClass.dex(); - Code mthCode = dex.readCode(methodData); - this.regsCount = mthCode.getRegistersSize(); + this.regsCount = codeReader.getRegistersCount(); initArguments(this.argTypes); - InsnDecoder decoder = new InsnDecoder(this); - decoder.decodeInsns(mthCode); - this.instructions = decoder.process(); - this.codeSize = instructions.length; - - initTryCatches(this, mthCode, instructions); - initJumps(instructions); - - this.debugInfoOffset = mthCode.getDebugInfoOffset(); + this.instructions = decoder.process(codeReader); } catch (Exception e) { if (!noCode) { unload(); @@ -331,130 +317,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return typeParameters; } - private static void initTryCatches(MethodNode mth, Code mthCode, InsnNode[] insnByOffset) { - CatchHandler[] catchBlocks = mthCode.getCatchHandlers(); - Try[] tries = mthCode.getTries(); - if (catchBlocks.length == 0 && tries.length == 0) { - return; - } - - int handlersCount = 0; - Set addrs = new HashSet<>(); - List catches = new ArrayList<>(catchBlocks.length); - - for (CatchHandler handler : catchBlocks) { - TryCatchBlock tcBlock = new TryCatchBlock(); - catches.add(tcBlock); - int[] handlerAddrArr = handler.getAddresses(); - for (int i = 0; i < handlerAddrArr.length; i++) { - int addr = handlerAddrArr[i]; - ClassInfo type = ClassInfo.fromDex(mth.dex(), handler.getTypeIndexes()[i]); - tcBlock.addHandler(mth, addr, type); - addrs.add(addr); - handlersCount++; - } - int addr = handler.getCatchAllAddress(); - if (addr >= 0) { - tcBlock.addHandler(mth, addr, null); - addrs.add(addr); - handlersCount++; - } - } - - if (handlersCount > 0 && handlersCount != addrs.size()) { - // resolve nested try blocks: - // inner block contains all handlers from outer block => remove these handlers from inner block - // each handler must be only in one try/catch block - for (TryCatchBlock outerTry : catches) { - for (TryCatchBlock innerTry : catches) { - if (outerTry != innerTry - && innerTry.containsAllHandlers(outerTry)) { - innerTry.removeSameHandlers(outerTry); - } - } - } - } - - // attach EXC_HANDLER attributes to instructions - addrs.clear(); - for (TryCatchBlock ct : catches) { - for (ExceptionHandler eh : ct.getHandlers()) { - int addr = eh.getHandleOffset(); - ExcHandlerAttr ehAttr = new ExcHandlerAttr(ct, eh); - // TODO: don't override existing attribute - insnByOffset[addr].addAttr(ehAttr); - } - } - - // attach TRY_ENTER, TRY_LEAVE attributes to instructions - for (Try aTry : tries) { - int catchNum = aTry.getCatchHandlerIndex(); - TryCatchBlock catchBlock = catches.get(catchNum); - int offset = aTry.getStartAddress(); - int end = offset + aTry.getInstructionCount() - 1; - - boolean tryBlockStarted = false; - InsnNode insn = null; - while (offset <= end && offset >= 0) { - insn = insnByOffset[offset]; - if (insn != null && insn.getType() != InsnType.NOP) { - if (tryBlockStarted) { - catchBlock.addInsn(insn); - } else if (insn.canThrowException()) { - insn.add(AFlag.TRY_ENTER); - catchBlock.addInsn(insn); - tryBlockStarted = true; - } - } - offset = InsnDecoder.getNextInsnOffset(insnByOffset, offset); - } - if (tryBlockStarted && insn != null) { - insn.add(AFlag.TRY_LEAVE); - } - } - } - - private static void initJumps(InsnNode[] insnByOffset) { - for (int offset = 0; offset < insnByOffset.length; offset++) { - InsnNode insn = insnByOffset[offset]; - if (insn == null) { - continue; - } - switch (insn.getType()) { - case SWITCH: - SwitchNode sw = (SwitchNode) insn; - for (int target : sw.getTargets()) { - addJump(insnByOffset, offset, target); - } - // default case - int nextInsnOffset = InsnDecoder.getNextInsnOffset(insnByOffset, offset); - if (nextInsnOffset != -1) { - addJump(insnByOffset, offset, nextInsnOffset); - } - break; - - case IF: - int next = InsnDecoder.getNextInsnOffset(insnByOffset, offset); - if (next != -1) { - addJump(insnByOffset, offset, next); - } - addJump(insnByOffset, offset, ((IfNode) insn).getTarget()); - break; - - case GOTO: - addJump(insnByOffset, offset, ((GotoNode) insn).getTarget()); - break; - - default: - break; - } - } - } - - private static void addJump(InsnNode[] insnByOffset, int offset, int target) { - insnByOffset[target].addAttr(AType.JUMP, new JumpInfo(offset, target)); - } - public String getName() { return mthInfo.getName(); } @@ -471,10 +333,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return noCode; } - public int getCodeSize() { - return codeSize; - } - public InsnNode[] getInstructions() { return instructions; } @@ -601,11 +459,12 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, @Override @SuppressWarnings("unchecked") public List getThrows() { - Annotation an = getAnnotation(Consts.DALVIK_THROWS); + IAnnotation an = getAnnotation(Consts.DALVIK_THROWS); if (an == null) { return Collections.emptyList(); } - return (List) an.getDefaultValue(); + List types = (List) an.getDefaultValue().getValue(); + return Utils.collectionMap(types, ev -> ArgType.object((String) ev.getValue())); } /** @@ -654,10 +513,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return regsCount; } - public int getDebugInfoOffset() { - return debugInfoOffset; - } - public SSAVar makeNewSVar(@NotNull RegisterArg assignArg) { int regNum = assignArg.getRegNum(); return makeNewSVar(regNum, getNextSVarVersion(regNum), assignArg); @@ -709,14 +564,9 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, this.region = region; } - @Override - public DexNode dex() { - return parentClass.dex(); - } - @Override public RootNode root() { - return dex().root(); + return parentClass.root(); } @Override @@ -724,13 +574,23 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return "method"; } + @Override + public Path getInputPath() { + return parentClass.getInputPath(); + } + @Override public MethodInfo getMethodInfo() { return mthInfo; } public long getMethodCodeOffset() { - return noCode ? 0 : methodData.getCodeOffset(); + return noCode ? 0 : codeReader.getCodeOffset(); + } + + @Nullable + public IDebugInfo getDebugInfo() { + return noCode ? null : codeReader.getDebugInfo(); } /** @@ -756,6 +616,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return loaded; } + public ICodeReader getCodeReader() { + return codeReader; + } + @Override public int hashCode() { return mthInfo.hashCode(); 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 49c54759b..10ddbdac1 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 @@ -1,8 +1,10 @@ package jadx.core.dex.nodes; import java.util.ArrayList; -import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -14,6 +16,8 @@ import jadx.api.JadxArgs; import jadx.api.ResourceFile; import jadx.api.ResourceType; import jadx.api.ResourcesLoader; +import jadx.api.plugins.input.data.IClassData; +import jadx.api.plugins.input.data.ILoadResult; import jadx.core.Jadx; import jadx.core.clsp.ClspGraph; import jadx.core.dex.info.ClassInfo; @@ -31,8 +35,6 @@ import jadx.core.utils.ErrorsCounter; import jadx.core.utils.StringUtils; import jadx.core.utils.android.AndroidResourcesUtils; import jadx.core.utils.exceptions.JadxRuntimeException; -import jadx.core.utils.files.DexFile; -import jadx.core.utils.files.InputFile; import jadx.core.xmlgen.ResTableParser; import jadx.core.xmlgen.ResourceStorage; @@ -53,8 +55,10 @@ public class RootNode { private final ICodeCache codeCache; + private final List classes = new ArrayList<>(); + private final Map clsMap = new HashMap<>(); + private ClspGraph clsp; - private List dexNodes; @Nullable private String appPackage; @Nullable @@ -69,29 +73,47 @@ public class RootNode { this.codeCache = args.getCodeCache(); this.methodUtils = new MethodUtils(this); this.typeUtils = new TypeUtils(this); - - this.dexNodes = Collections.emptyList(); } - public void load(List inputFiles) { - dexNodes = new ArrayList<>(); - for (InputFile input : inputFiles) { - for (DexFile dexFile : input.getDexFiles()) { + public void loadClasses(List loadedInputs) { + for (ILoadResult loadedInput : loadedInputs) { + loadedInput.visitClasses(cls -> { try { - LOG.debug("Load: {}", dexFile); - DexNode dexNode = new DexNode(this, dexFile, dexNodes.size()); - dexNodes.add(dexNode); + addClassNode(new ClassNode(RootNode.this, cls)); } catch (Exception e) { - throw new JadxRuntimeException("Error decode file: " + dexFile, e); + addDummyClass(cls, e); } - } - } - for (DexNode dexNode : dexNodes) { - dexNode.loadClasses(); + }); } + // sort classes by name, expect top classes before inner + classes.sort(Comparator.comparing(ClassNode::getFullName)); initInnerClasses(); } + private void addDummyClass(IClassData classData, Exception exc) { + String typeStr = classData.getType(); + String name = null; + try { + ClassInfo clsInfo = ClassInfo.fromName(this, typeStr); + if (clsInfo != null) { + name = clsInfo.getShortName(); + } + } catch (Exception e) { + LOG.error("Failed to get name for class with type {}", typeStr, e); + } + if (name == null || name.isEmpty()) { + name = "CLASS_" + typeStr; + } + ClassNode clsNode = new ClassNode(this, name, classData.getAccessFlags()); + ErrorsCounter.error(clsNode, "Load error", exc); + addClassNode(clsNode); + } + + public void addClassNode(ClassNode clsNode) { + classes.add(clsNode); + clsMap.put(clsNode.getClassInfo(), clsNode); + } + public void loadResources(List resources) { ResourceFile arsc = null; for (ResourceFile rf : resources) { @@ -110,7 +132,9 @@ public class RootNode { parser.decode(is); return parser.getResStorage(); }); - processResources(resStorage); + if (resStorage != null) { + processResources(resStorage); + } } catch (Exception e) { LOG.error("Failed to parse '.arsc' file", e); } @@ -127,11 +151,6 @@ public class RootNode { if (this.clsp == null) { ClspGraph newClsp = new ClspGraph(this); newClsp.load(); - - List classes = new ArrayList<>(); - for (DexNode dexNode : dexNodes) { - classes.addAll(dexNode.getClasses()); - } newClsp.addApp(classes); this.clsp = newClsp; @@ -142,36 +161,54 @@ public class RootNode { } private void initInnerClasses() { - for (DexNode dexNode : dexNodes) { - dexNode.initInnerClasses(); + // move inner classes + List inner = new ArrayList<>(); + for (ClassNode cls : classes) { + if (cls.getClassInfo().isInner()) { + inner.add(cls); + } + } + List updated = new ArrayList<>(); + for (ClassNode cls : inner) { + ClassInfo clsInfo = cls.getClassInfo(); + ClassNode parent = resolveClass(clsInfo.getParentClass()); + if (parent == null) { + clsMap.remove(clsInfo); + clsInfo.notInner(this); + clsMap.put(clsInfo, cls); + updated.add(cls); + } else { + parent.addInnerClass(cls); + } + } + // reload names for inner classes of updated parents + for (ClassNode updCls : updated) { + for (ClassNode innerCls : updCls.getInnerClasses()) { + innerCls.getClassInfo().updateNames(this); + } } } + public List getClasses() { + return classes; + } + public List getClasses(boolean includeInner) { - List classes = new ArrayList<>(); - for (DexNode dex : dexNodes) { - if (includeInner) { - classes.addAll(dex.getClasses()); - } else { - for (ClassNode cls : dex.getClasses()) { - if (!cls.getClassInfo().isInner()) { - classes.add(cls); - } - } + if (includeInner) { + return classes; + } + List notInnerClasses = new ArrayList<>(); + for (ClassNode cls : classes) { + if (!cls.getClassInfo().isInner()) { + notInnerClasses.add(cls); } } - return classes; + return notInnerClasses; } @Nullable public ClassNode resolveClass(ClassInfo clsInfo) { - for (DexNode dexNode : dexNodes) { - ClassNode cls = dexNode.resolveClassLocal(clsInfo); - if (cls != null) { - return cls; - } - } - return null; + return clsMap.get(clsInfo); } @Nullable @@ -185,30 +222,22 @@ public class RootNode { if (clsType.isGeneric()) { clsType = ArgType.object(clsType.getObject()); } - for (DexNode dexNode : dexNodes) { - ClassNode cls = dexNode.resolveClass(clsType); - if (cls != null) { - return cls; - } - } - return null; + return resolveClass(ClassInfo.fromType(this, clsType)); } @Nullable - public ClassNode searchClassByName(String fullName) { + public ClassNode resolveClass(String fullName) { ClassInfo clsInfo = ClassInfo.fromName(this, fullName); return resolveClass(clsInfo); } @Nullable public ClassNode searchClassByFullAlias(String fullName) { - for (DexNode dexNode : dexNodes) { - for (ClassNode cls : dexNode.getClasses()) { - ClassInfo classInfo = cls.getClassInfo(); - if (classInfo.getFullName().equals(fullName) - || classInfo.getAliasFullName().equals(fullName)) { - return cls; - } + for (ClassNode cls : classes) { + ClassInfo classInfo = cls.getClassInfo(); + if (classInfo.getFullName().equals(fullName) + || classInfo.getAliasFullName().equals(fullName)) { + return cls; } } return null; @@ -216,16 +245,23 @@ public class RootNode { public List searchClassByShortName(String shortName) { List list = new ArrayList<>(); - for (DexNode dexNode : dexNodes) { - for (ClassNode cls : dexNode.getClasses()) { - if (cls.getClassInfo().getShortName().equals(shortName)) { - list.add(cls); - } + for (ClassNode cls : classes) { + if (cls.getClassInfo().getShortName().equals(shortName)) { + list.add(cls); } } return list; } + @Nullable + public MethodNode resolveMethod(@NotNull MethodInfo mth) { + ClassNode cls = resolveClass(mth.getDeclClass()); + if (cls != null) { + return cls.searchMethod(mth); + } + return null; + } + @Nullable public MethodNode deepResolveMethod(@NotNull MethodInfo mth) { ClassNode cls = resolveClass(mth.getDeclClass()); @@ -236,7 +272,46 @@ public class RootNode { if (methodNode != null) { return methodNode; } - return cls.dex().deepResolveMethod(cls, mth.makeSignature(false)); + return deepResolveMethod(cls, mth.makeSignature(false)); + } + + @Nullable + private MethodNode deepResolveMethod(@NotNull ClassNode cls, String signature) { + for (MethodNode m : cls.getMethods()) { + if (m.getMethodInfo().getShortId().startsWith(signature)) { + return m; + } + } + MethodNode found; + ArgType superClass = cls.getSuperClass(); + if (superClass != null) { + ClassNode superNode = resolveClass(superClass); + if (superNode != null) { + found = deepResolveMethod(superNode, signature); + if (found != null) { + return found; + } + } + } + for (ArgType iFaceType : cls.getInterfaces()) { + ClassNode iFaceNode = resolveClass(iFaceType); + if (iFaceNode != null) { + found = deepResolveMethod(iFaceNode, signature); + if (found != null) { + return found; + } + } + } + return null; + } + + @Nullable + public FieldNode resolveField(FieldInfo field) { + ClassNode cls = resolveClass(field.getDeclClass()); + if (cls != null) { + return cls.searchField(field); + } + return null; } @Nullable @@ -245,7 +320,35 @@ public class RootNode { if (cls == null) { return null; } - return cls.dex().deepResolveField(cls, field); + return deepResolveField(cls, field); + } + + @Nullable + private FieldNode deepResolveField(@NotNull ClassNode cls, FieldInfo fieldInfo) { + FieldNode field = cls.searchFieldByNameAndType(fieldInfo); + if (field != null) { + return field; + } + ArgType superClass = cls.getSuperClass(); + if (superClass != null) { + ClassNode superNode = resolveClass(superClass); + if (superNode != null) { + FieldNode found = deepResolveField(superNode, fieldInfo); + if (found != null) { + return found; + } + } + } + for (ArgType iFaceType : cls.getInterfaces()) { + ClassNode iFaceNode = resolveClass(iFaceType); + if (iFaceNode != null) { + FieldNode found = deepResolveField(iFaceNode, fieldInfo); + if (found != null) { + return found; + } + } + } + return null; } public List getPasses() { @@ -262,10 +365,6 @@ public class RootNode { } } - public List getDexNodes() { - return dexNodes; - } - public ClspGraph getClsp() { return clsp; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/AnnotationsParser.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/AnnotationsParser.java deleted file mode 100644 index e2b34e8bc..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/AnnotationsParser.java +++ /dev/null @@ -1,112 +0,0 @@ -package jadx.core.dex.nodes.parser; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import com.android.dex.Dex.Section; - -import jadx.core.dex.attributes.annotations.Annotation; -import jadx.core.dex.attributes.annotations.Annotation.Visibility; -import jadx.core.dex.attributes.annotations.AnnotationsList; -import jadx.core.dex.attributes.annotations.MethodParameters; -import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; -import jadx.core.dex.nodes.FieldNode; -import jadx.core.dex.nodes.MethodNode; -import jadx.core.utils.exceptions.DecodeException; - -public class AnnotationsParser { - - private static final Visibility[] VISIBILITIES = { - Visibility.BUILD, - Visibility.RUNTIME, - Visibility.SYSTEM - }; - - private final DexNode dex; - private final ClassNode cls; - - public AnnotationsParser(ClassNode cls) { - this.cls = cls; - this.dex = cls.dex(); - } - - public void parse(int offset) throws DecodeException { - Section section = dex.openSection(offset); - - // TODO read as unsigned int - int classAnnotationsOffset = section.readInt(); - int fieldsCount = section.readInt(); - int annotatedMethodsCount = section.readInt(); - int annotatedParametersCount = section.readInt(); - - if (classAnnotationsOffset != 0) { - cls.addAttr(readAnnotationSet(classAnnotationsOffset)); - } - - for (int i = 0; i < fieldsCount; i++) { - FieldNode f = cls.searchFieldById(section.readInt()); - f.addAttr(readAnnotationSet(section.readInt())); - } - - for (int i = 0; i < annotatedMethodsCount; i++) { - MethodNode m = cls.searchMethodById(section.readInt()); - m.addAttr(readAnnotationSet(section.readInt())); - } - - for (int i = 0; i < annotatedParametersCount; i++) { - MethodNode mth = cls.searchMethodById(section.readInt()); - // read annotation ref list - Section ss = dex.openSection(section.readInt()); - int size = ss.readInt(); - MethodParameters params = new MethodParameters(size); - for (int j = 0; j < size; j++) { - params.getParamList().add(readAnnotationSet(ss.readInt())); - } - mth.addAttr(params); - } - } - - private AnnotationsList readAnnotationSet(int offset) throws DecodeException { - if (offset == 0) { - return AnnotationsList.EMPTY; - } - Section section = dex.openSection(offset); - int size = section.readInt(); - if (size == 0) { - return AnnotationsList.EMPTY; - } - List list = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - Section anSection = dex.openSection(section.readInt()); - Annotation a = readAnnotation(dex, anSection, true); - list.add(a); - } - return new AnnotationsList(list); - } - - public static Annotation readAnnotation(DexNode dex, Section s, boolean readVisibility) throws DecodeException { - EncValueParser parser = new EncValueParser(dex, s); - Visibility visibility = null; - if (readVisibility) { - byte v = s.readByte(); - visibility = VISIBILITIES[v]; - } - int typeIndex = s.readUleb128(); - int size = s.readUleb128(); - Map values = new LinkedHashMap<>(size); - for (int i = 0; i < size; i++) { - String name = dex.getString(s.readUleb128()); - values.put(name, parser.parseValue()); - } - ArgType type = dex.getType(typeIndex); - Annotation annotation = new Annotation(visibility, type, values); - if (!type.isObject()) { - throw new DecodeException("Incorrect type for annotation: " + annotation); - } - return annotation; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/EncValueParser.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/EncValueParser.java deleted file mode 100644 index f1c3ba622..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/EncValueParser.java +++ /dev/null @@ -1,132 +0,0 @@ -package jadx.core.dex.nodes.parser; - -import java.util.ArrayList; -import java.util.List; - -import com.android.dex.Dex.Section; -import com.android.dex.Leb128; - -import jadx.core.dex.info.FieldInfo; -import jadx.core.dex.info.MethodInfo; -import jadx.core.dex.nodes.DexNode; -import jadx.core.utils.exceptions.DecodeException; - -public class EncValueParser { - - private static final int ENCODED_BYTE = 0x00; - private static final int ENCODED_SHORT = 0x02; - private static final int ENCODED_CHAR = 0x03; - private static final int ENCODED_INT = 0x04; - private static final int ENCODED_LONG = 0x06; - private static final int ENCODED_FLOAT = 0x10; - private static final int ENCODED_DOUBLE = 0x11; - private static final int ENCODED_STRING = 0x17; - private static final int ENCODED_TYPE = 0x18; - private static final int ENCODED_FIELD = 0x19; - private static final int ENCODED_ENUM = 0x1b; - private static final int ENCODED_METHOD = 0x1a; - private static final int ENCODED_ARRAY = 0x1c; - private static final int ENCODED_ANNOTATION = 0x1d; - private static final int ENCODED_NULL = 0x1e; - private static final int ENCODED_BOOLEAN = 0x1f; - - protected final Section in; - - private final DexNode dex; - - public EncValueParser(DexNode dex, Section in) { - this.in = in; - this.dex = dex; - } - - public Object parseValue() throws DecodeException { - int argAndType = readByte(); - int type = argAndType & 0x1F; - int arg = (argAndType & 0xE0) >> 5; - int size = arg + 1; - - switch (type) { - case ENCODED_NULL: - return null; - - case ENCODED_BOOLEAN: - return arg == 1; - case ENCODED_BYTE: - return in.readByte(); - - case ENCODED_SHORT: - return (short) parseNumber(size, true); - case ENCODED_CHAR: - return (char) parseUnsignedInt(size); - case ENCODED_INT: - return (int) parseNumber(size, true); - case ENCODED_LONG: - return parseNumber(size, true); - - case ENCODED_FLOAT: - return Float.intBitsToFloat((int) parseNumber(size, false, 4)); - case ENCODED_DOUBLE: - return Double.longBitsToDouble(parseNumber(size, false, 8)); - - case ENCODED_STRING: - return dex.getString(parseUnsignedInt(size)); - - case ENCODED_TYPE: - return dex.getType(parseUnsignedInt(size)); - - case ENCODED_METHOD: - return MethodInfo.fromDex(dex, parseUnsignedInt(size)); - - case ENCODED_FIELD: - case ENCODED_ENUM: - return FieldInfo.fromDex(dex, parseUnsignedInt(size)); - - case ENCODED_ARRAY: - int count = Leb128.readUnsignedLeb128(in); - List values = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - values.add(parseValue()); - } - return values; - - case ENCODED_ANNOTATION: - return AnnotationsParser.readAnnotation(dex, in, false); - - default: - throw new DecodeException("Unknown encoded value type: 0x" + Integer.toHexString(type)); - } - } - - private int parseUnsignedInt(int byteCount) { - return (int) parseNumber(byteCount, false, 0); - } - - private long parseNumber(int byteCount, boolean isSignExtended) { - return parseNumber(byteCount, isSignExtended, 0); - } - - private long parseNumber(int byteCount, boolean isSignExtended, int fillOnRight) { - long result = 0; - long last = 0; - for (int i = 0; i < byteCount; i++) { - last = readByte(); - result |= last << i * 8; - } - if (fillOnRight != 0) { - for (int i = byteCount; i < fillOnRight; i++) { - result <<= 8; - } - } else { - if (isSignExtended && (last & 0x80) != 0) { - for (int i = byteCount; i < 8; i++) { - result |= (long) 0xFF << i * 8; - } - } - } - return result; - } - - private int readByte() { - return in.readByte() & 0xFF; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java index f4adb4dab..870aa32b5 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java @@ -9,11 +9,13 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.data.annotations.IAnnotation; import jadx.core.Consts; import jadx.core.dex.attributes.IAttributeNode; -import jadx.core.dex.attributes.annotations.Annotation; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.GenericTypeParameter; +import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; public class SignatureParser { @@ -45,11 +47,13 @@ public class SignatureParser { @SuppressWarnings("unchecked") @Nullable public static String getSignature(IAttributeNode node) { - Annotation a = node.getAnnotation(Consts.DALVIK_SIGNATURE); + IAnnotation a = node.getAnnotation(Consts.DALVIK_SIGNATURE); if (a == null) { return null; } - return mergeSignature((List) a.getDefaultValue()); + List values = (List) a.getDefaultValue().getValue(); + List strings = Utils.collectionMap(values, ev -> ((String) ev.getValue())); + return mergeSignature(strings); } private char next() { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/StaticValuesParser.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/StaticValuesParser.java deleted file mode 100644 index 5f10bdd29..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/StaticValuesParser.java +++ /dev/null @@ -1,28 +0,0 @@ -package jadx.core.dex.nodes.parser; - -import java.util.List; - -import com.android.dex.Dex.Section; -import com.android.dex.Leb128; - -import jadx.core.dex.nodes.DexNode; -import jadx.core.dex.nodes.FieldNode; -import jadx.core.utils.exceptions.DecodeException; - -public class StaticValuesParser extends EncValueParser { - - public StaticValuesParser(DexNode dex, Section in) { - super(dex, in); - } - - public int processFields(List fields) throws DecodeException { - int count = Leb128.readUnsignedLeb128(in); - for (int i = 0; i < count; i++) { - Object value = parseValue(); - if (i < fields.size()) { - fields.get(i).addAttr(FieldInitAttr.constValue(value)); - } - } - return count; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlock.java b/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlock.java index e39b64e09..ccb978dbc 100644 --- a/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlock.java +++ b/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlock.java @@ -2,7 +2,6 @@ package jadx.core.dex.trycatch; import java.util.ArrayList; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import org.jetbrains.annotations.Nullable; @@ -24,8 +23,8 @@ public class TryCatchBlock { private final List insns; private final CatchAttr attr; - public TryCatchBlock() { - handlers = new LinkedList<>(); + public TryCatchBlock(int handlersCount) { + handlers = new ArrayList<>(handlersCount); insns = new ArrayList<>(); attr = new CatchAttr(this); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/AttachTryCatchVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachTryCatchVisitor.java new file mode 100644 index 000000000..943d795fb --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachTryCatchVisitor.java @@ -0,0 +1,135 @@ +package jadx.core.dex.visitors; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import jadx.api.plugins.input.data.ICatch; +import jadx.api.plugins.input.data.ICodeReader; +import jadx.api.plugins.input.data.ITry; +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.info.ClassInfo; +import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.trycatch.ExcHandlerAttr; +import jadx.core.dex.trycatch.ExceptionHandler; +import jadx.core.dex.trycatch.TryCatchBlock; +import jadx.core.utils.exceptions.JadxException; + +import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset; + +@JadxVisitor( + name = "Attach Try/Catch Visitor", + desc = "Attach try/catch info to instructions", + runBefore = { + ProcessInstructionsVisitor.class + } +) +public class AttachTryCatchVisitor extends AbstractVisitor { + + @Override + public void visit(MethodNode mth) throws JadxException { + if (mth.isNoCode()) { + return; + } + initTryCatches(mth, mth.getCodeReader(), mth.getInstructions()); + } + + private static void initTryCatches(MethodNode mth, ICodeReader codeReader, InsnNode[] insnByOffset) { + List tries = codeReader.getTries(); + if (tries.isEmpty()) { + return; + } + int handlersCount = 0; + Set addrs = new HashSet<>(); + List catches = new ArrayList<>(tries.size()); + for (ITry tryData : tries) { + TryCatchBlock catchBlock = processHandlers(mth, addrs, tryData.getCatch()); + catches.add(catchBlock); + handlersCount += catchBlock.getHandlersCount(); + } + + // TODO: run modify in later passes + if (handlersCount > 0 && handlersCount != addrs.size()) { + // resolve nested try blocks: + // inner block contains all handlers from outer block => remove these handlers from inner block + // each handler must be only in one try/catch block + for (TryCatchBlock outerTry : catches) { + for (TryCatchBlock innerTry : catches) { + if (outerTry != innerTry + && innerTry.containsAllHandlers(outerTry)) { + innerTry.removeSameHandlers(outerTry); + } + } + } + } + addrs.clear(); + + for (TryCatchBlock tryCatchBlock : catches) { + if (tryCatchBlock.getHandlersCount() == 0) { + continue; + } + for (ExceptionHandler handler : tryCatchBlock.getHandlers()) { + int addr = handler.getHandleOffset(); + ExcHandlerAttr ehAttr = new ExcHandlerAttr(tryCatchBlock, handler); + // TODO: don't override existing attribute + insnByOffset[addr].addAttr(ehAttr); + } + } + + int k = 0; + for (ITry tryData : tries) { + TryCatchBlock catchBlock = catches.get(k++); + if (catchBlock.getHandlersCount() != 0) { + markTryBounds(insnByOffset, tryData, catchBlock); + } + } + + } + + private static void markTryBounds(InsnNode[] insnByOffset, ITry aTry, TryCatchBlock catchBlock) { + int offset = aTry.getStartAddress(); + int end = offset + aTry.getInstructionCount() - 1; + + boolean tryBlockStarted = false; + InsnNode insn = null; + while (offset <= end && offset >= 0) { + insn = insnByOffset[offset]; + if (insn != null && insn.getType() != InsnType.NOP) { + if (tryBlockStarted) { + catchBlock.addInsn(insn); + } else if (insn.canThrowException()) { + insn.add(AFlag.TRY_ENTER); + catchBlock.addInsn(insn); + tryBlockStarted = true; + } + } + offset = getNextInsnOffset(insnByOffset, offset); + } + if (tryBlockStarted && insn != null) { + insn.add(AFlag.TRY_LEAVE); + } + } + + private static TryCatchBlock processHandlers(MethodNode mth, Set addrs, ICatch catchBlock) { + int[] handlerAddrArr = catchBlock.getAddresses(); + String[] handlerTypes = catchBlock.getTypes(); + + int handlersCount = handlerAddrArr.length; + TryCatchBlock tcBlock = new TryCatchBlock(handlersCount); + for (int i = 0; i < handlersCount; i++) { + int addr = handlerAddrArr[i]; + ClassInfo type = ClassInfo.fromName(mth.root(), handlerTypes[i]); + tcBlock.addHandler(mth, addr, type); + addrs.add(addr); + } + int addr = catchBlock.getCatchAllAddress(); + if (addr >= 0) { + tcBlock.addHandler(mth, addr, null); + addrs.add(addr); + } + return tcBlock; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java index 508adb3e3..abdd2e357 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java @@ -4,8 +4,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; import jadx.core.Consts; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; @@ -80,7 +79,7 @@ public class ClassModifier extends AbstractVisitor { for (FieldNode field : cls.getFields()) { if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) { ClassInfo clsInfo = ClassInfo.fromType(cls.root(), field.getType()); - ClassNode fieldsCls = cls.dex().resolveClass(clsInfo); + ClassNode fieldsCls = cls.root().resolveClass(clsInfo); ClassInfo parentClass = cls.getClassInfo().getParentClass(); if (fieldsCls != null && (inline || parentClass.equals(fieldsCls.getClassInfo()))) { @@ -171,7 +170,7 @@ public class ClassModifier extends AbstractVisitor { if (!argType.isObject()) { continue; } - ClassNode argCls = cls.dex().resolveClass(argType); + ClassNode argCls = cls.root().resolveClass(argType); if (argCls == null) { // check if missing class from current top class ClassInfo argClsInfo = ClassInfo.fromType(cls.root(), argType); @@ -268,7 +267,7 @@ public class ClassModifier extends AbstractVisitor { // remove confirmed, change visibility and name if needed if (!wrappedAccFlags.isPublic()) { // must be public - FixAccessModifiers.changeVisibility(wrappedMth, AccessFlags.ACC_PUBLIC); + FixAccessModifiers.changeVisibility(wrappedMth, AccessFlags.PUBLIC); } String alias = mth.getAlias(); if (!Objects.equals(wrappedMth.getAlias(), alias)) { @@ -341,7 +340,7 @@ public class ClassModifier extends AbstractVisitor { } } else if (type == InsnType.IPUT) { FieldInfo fldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex(); - FieldNode fieldNode = mth.dex().resolveField(fldInfo); + FieldNode fieldNode = mth.root().resolveField(fldInfo); if (fieldNode != null && fieldNode.contains(AFlag.DONT_GENERATE)) { insn.add(AFlag.DONT_GENERATE); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java index 90593ca6f..388ca08a8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java @@ -114,13 +114,13 @@ public class ConstructorVisitor extends AbstractVisitor { */ @Nullable private static ConstructorInsn processConstructor(MethodNode mth, ConstructorInsn co) { - MethodNode callMth = mth.dex().resolveMethod(co.getCallMth()); + MethodNode callMth = mth.root().resolveMethod(co.getCallMth()); if (callMth == null || !callMth.getAccessFlags().isSynthetic() || !allArgsNull(co)) { return null; } - ClassNode classNode = mth.dex().resolveClass(callMth.getParentClass().getClassInfo()); + ClassNode classNode = mth.root().resolveClass(callMth.getParentClass().getClassInfo()); if (classNode == null) { return null; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java index 5f59449ab..0e4b4b4c7 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java @@ -7,33 +7,32 @@ import java.util.List; import java.util.Set; import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.FieldInitAttr; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.FieldInfo; +import jadx.core.dex.instructions.BaseInvokeNode; import jadx.core.dex.instructions.IndexInsnNode; -import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.RegisterArg; -import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.parser.FieldInitAttr; +import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.JadxException; public class DependencyCollector extends AbstractVisitor { @Override public boolean visit(ClassNode cls) throws JadxException { - DexNode dex = cls.dex(); + RootNode root = cls.root(); Set depSet = new HashSet<>(); - processClass(cls, dex, depSet); + processClass(cls, root, depSet); for (ClassNode inner : cls.getInnerClasses()) { - processClass(inner, dex, depSet); + processClass(inner, root, depSet); } depSet.remove(cls); @@ -43,18 +42,18 @@ public class DependencyCollector extends AbstractVisitor { return false; } - private static void processClass(ClassNode cls, DexNode dex, Set depList) { - addDep(dex, depList, cls.getSuperClass()); + private static void processClass(ClassNode cls, RootNode root, Set depList) { + addDep(root, depList, cls.getSuperClass()); for (ArgType iType : cls.getInterfaces()) { - addDep(dex, depList, iType); + addDep(root, depList, iType); } for (FieldNode fieldNode : cls.getFields()) { - addDep(dex, depList, fieldNode.getType()); + addDep(root, depList, fieldNode.getType()); // process instructions from field init FieldInitAttr fieldInitAttr = fieldNode.get(AType.FIELD_INIT); if (fieldInitAttr != null && fieldInitAttr.getValueType() == FieldInitAttr.InitType.INSN) { - processInsn(dex, depList, fieldInitAttr.getInsn()); + processInsn(root, depList, fieldInitAttr.getInsn()); } } // TODO: process annotations and generics @@ -62,80 +61,77 @@ public class DependencyCollector extends AbstractVisitor { if (methodNode.isNoCode() || methodNode.contains(AType.JADX_ERROR)) { continue; } - processMethod(dex, depList, methodNode); + processMethod(root, depList, methodNode); } } - private static void processMethod(DexNode dex, Set depList, MethodNode methodNode) { - addDep(dex, depList, methodNode.getParentClass()); - addDep(dex, depList, methodNode.getReturnType()); + private static void processMethod(RootNode root, Set depList, MethodNode methodNode) { + addDep(root, depList, methodNode.getParentClass()); + addDep(root, depList, methodNode.getReturnType()); for (ArgType arg : methodNode.getMethodInfo().getArgumentsTypes()) { - addDep(dex, depList, arg); + addDep(root, depList, arg); } for (BlockNode block : methodNode.getBasicBlocks()) { for (InsnNode insnNode : block.getInstructions()) { - processInsn(dex, depList, insnNode); + processInsn(root, depList, insnNode); } } } // TODO: add custom instructions processing - private static void processInsn(DexNode dex, Set depList, InsnNode insnNode) { + private static void processInsn(RootNode root, Set depList, InsnNode insnNode) { RegisterArg result = insnNode.getResult(); if (result != null) { - addDep(dex, depList, result.getType()); + addDep(root, depList, result.getType()); } for (InsnArg arg : insnNode.getArguments()) { if (arg.isInsnWrap()) { - processInsn(dex, depList, ((InsnWrapArg) arg).getWrapInsn()); + processInsn(root, depList, ((InsnWrapArg) arg).getWrapInsn()); } else { - addDep(dex, depList, arg.getType()); + addDep(root, depList, arg.getType()); } } - processCustomInsn(dex, depList, insnNode); + processCustomInsn(root, depList, insnNode); } - private static void processCustomInsn(DexNode dex, Set depList, InsnNode insn) { + private static void processCustomInsn(RootNode root, Set depList, InsnNode insn) { if (insn instanceof IndexInsnNode) { Object index = ((IndexInsnNode) insn).getIndex(); if (index instanceof FieldInfo) { - addDep(dex, depList, ((FieldInfo) index).getDeclClass()); + addDep(root, depList, ((FieldInfo) index).getDeclClass()); } else if (index instanceof ArgType) { - addDep(dex, depList, (ArgType) index); + addDep(root, depList, (ArgType) index); } - } else if (insn instanceof InvokeNode) { - ClassInfo declClass = ((InvokeNode) insn).getCallMth().getDeclClass(); - addDep(dex, depList, declClass); - } else if (insn instanceof ConstructorInsn) { - ClassInfo declClass = ((ConstructorInsn) insn).getCallMth().getDeclClass(); - addDep(dex, depList, declClass); + } else if (insn instanceof BaseInvokeNode) { + ClassInfo declClass = ((BaseInvokeNode) insn).getCallMth().getDeclClass(); + addDep(root, depList, declClass); } } - private static void addDep(DexNode dex, Set depList, ArgType type) { + private static void addDep(RootNode root, Set depList, ArgType type) { if (type != null) { if (type.isObject() && !type.isGenericType()) { - addDep(dex, depList, ClassInfo.fromType(dex.root(), type)); + addDep(root, depList, ClassInfo.fromType(root, type)); ArgType[] genericTypes = type.getGenericTypes(); if (type.isGeneric() && genericTypes != null) { for (ArgType argType : genericTypes) { - addDep(dex, depList, argType); + addDep(root, depList, argType); } } } else if (type.isArray()) { - addDep(dex, depList, type.getArrayRootElement()); + addDep(root, depList, type.getArrayRootElement()); } } } - private static void addDep(DexNode dex, Set depList, ClassInfo clsInfo) { + private static void addDep(RootNode root, Set depList, ClassInfo clsInfo) { if (clsInfo != null) { - ClassNode node = dex.resolveClass(clsInfo); - addDep(dex, depList, node); + ClassNode node = root.resolveClass(clsInfo); + addDep(root, depList, node); } } - private static void addDep(DexNode dex, Set depList, ClassNode clsNode) { + private static void addDep(RootNode root, Set depList, ClassNode clsNode) { if (clsNode != null) { // add only top classes depList.add(clsNode.getTopParentClass()); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java index a8ef3ca78..76235c25f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java @@ -1,6 +1,5 @@ package jadx.core.dex.visitors; -import jadx.core.dex.attributes.AType; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.utils.DebugChecks; @@ -19,9 +18,6 @@ public class DepthTraversal { } public static void visit(IDexTreeVisitor visitor, MethodNode mth) { - if (mth.contains(AType.JADX_ERROR)) { - return; - } try { visitor.visit(mth); if (DebugChecks.checksEnabled) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index 2a81bfa0c..7b9b2ca1f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -11,8 +11,7 @@ import java.util.stream.Collectors; import org.jetbrains.annotations.Nullable; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; import jadx.core.codegen.TypeGen; import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AFlag; @@ -36,7 +35,6 @@ import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; @@ -76,7 +74,7 @@ public class EnumVisitor extends AbstractVisitor { if (!convertToEnum(cls)) { AccessInfo accessFlags = cls.getAccessFlags(); if (accessFlags.isEnum()) { - cls.setAccessFlags(accessFlags.remove(AccessFlags.ACC_ENUM)); + cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM)); cls.addAttr(AType.COMMENTS, "JADX INFO: Failed to restore enum class, 'enum' modifier removed"); } } @@ -162,7 +160,7 @@ public class EnumVisitor extends AbstractVisitor { FieldNode fieldNode = enumField.getField(); // use string arg from the constructor as enum field name - String name = getConstString(cls.dex(), co.getArg(0)); + String name = getConstString(cls.root(), co.getArg(0)); if (name != null && !fieldNode.getAlias().equals(name) && NameMapper.isValidAndPrintable(name) @@ -196,7 +194,7 @@ public class EnumVisitor extends AbstractVisitor { if (!regs.isEmpty()) { cls.addWarnComment("Init of enum " + enumField.getField().getName() + " can be incorrect"); } - MethodNode ctrMth = cls.dex().resolveMethod(co.getCallMth()); + MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth()); if (ctrMth != null) { markArgsForSkip(ctrMth); } @@ -293,14 +291,14 @@ public class EnumVisitor extends AbstractVisitor { return null; } ClassInfo clsInfo = co.getClassType(); - ClassNode constrCls = cls.dex().resolveClass(clsInfo); + ClassNode constrCls = cls.root().resolveClass(clsInfo); if (constrCls == null) { return null; } if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum()) { return null; } - MethodNode ctrMth = cls.dex().resolveMethod(co.getCallMth()); + MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth()); if (ctrMth == null) { return null; } @@ -415,10 +413,10 @@ public class EnumVisitor extends AbstractVisitor { return null; } - private String getConstString(DexNode dex, InsnArg arg) { + private String getConstString(RootNode root, InsnArg arg) { if (arg.isInsnWrap()) { InsnNode constInsn = ((InsnWrapArg) arg).getWrapInsn(); - Object constValue = InsnUtils.getConstValueByInsn(dex, constInsn); + Object constValue = InsnUtils.getConstValueByInsn(root, constInsn); if (constValue instanceof String) { return (String) constValue; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java index d219f9979..9f518fe15 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java @@ -8,6 +8,7 @@ import java.util.Set; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.FieldInitAttr; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.instructions.IndexInsnNode; @@ -20,7 +21,6 @@ import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.utils.BlockUtils; import jadx.core.utils.InsnRemover; import jadx.core.utils.exceptions.JadxException; @@ -155,7 +155,7 @@ public class ExtractFieldInit extends AbstractVisitor { Set fields = new HashSet<>(); for (InsnNode insn : common.getPutInsns()) { FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex(); - FieldNode field = cls.dex().resolveField(fieldInfo); + FieldNode field = cls.root().resolveField(fieldInfo); if (field == null) { return; } @@ -175,7 +175,7 @@ public class ExtractFieldInit extends AbstractVisitor { } for (InsnNode insn : common.getPutInsns()) { FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex(); - FieldNode field = cls.dex().resolveField(fieldInfo); + FieldNode field = cls.root().resolveField(fieldInfo); addFieldInitAttr(common.getConstrMth(), field, insn); } } @@ -202,7 +202,7 @@ public class ExtractFieldInit extends AbstractVisitor { // exclude fields from super classes return false; } - FieldNode fieldNode = cls.dex().resolveField(fieldInfo); + FieldNode fieldNode = cls.root().resolveField(fieldInfo); if (fieldNode == null) { // exclude inherited fields (not declared in this class) return false; 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 7156857bc..322350f18 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 @@ -1,7 +1,6 @@ package jadx.core.dex.visitors; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; import jadx.core.dex.attributes.AType; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.nodes.ICodeNode; @@ -45,12 +44,12 @@ public class FixAccessModifiers extends AbstractVisitor { private static int fixVisibility(MethodNode mth) { if (mth.isVirtual()) { // make virtual methods public - return AccessFlags.ACC_PUBLIC; + return AccessFlags.PUBLIC; } else { AccessInfo accessFlags = mth.getAccessFlags(); if (accessFlags.isAbstract()) { // make abstract methods public - return AccessFlags.ACC_PUBLIC; + return AccessFlags.PUBLIC; } // enum constructor can't be public if (accessFlags.isConstructor() @@ -63,7 +62,7 @@ public class FixAccessModifiers extends AbstractVisitor { return -1; } // make other direct methods private - return AccessFlags.ACC_PRIVATE; + return AccessFlags.PRIVATE; } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/GenericTypesVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/GenericTypesVisitor.java index f58e0391e..f638bbd3d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/GenericTypesVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/GenericTypesVisitor.java @@ -49,7 +49,7 @@ public class GenericTypesVisitor extends AbstractVisitor { if (argType == null || argType.getGenericTypes() == null) { return; } - ClassNode cls = mth.dex().resolveClass(insn.getClassType()); + ClassNode cls = mth.root().resolveClass(insn.getClassType()); if (cls != null && cls.getGenericTypeParameters().isEmpty()) { return; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java index 993b65fb1..aecd0b4a8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java @@ -3,8 +3,7 @@ package jadx.core.dex.visitors; import java.util.ArrayList; import java.util.List; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; import jadx.core.Consts; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; @@ -87,7 +86,7 @@ public class MethodInlineVisitor extends AbstractVisitor { } private static boolean fixVisibilityOfInlineCode(MethodNode mth, InsnNode insn) { - int newVisFlag = AccessFlags.ACC_PUBLIC; // TODO: calculate more precisely + int newVisFlag = AccessFlags.PUBLIC; // TODO: calculate more precisely InsnType insnType = insn.getType(); if (insnType == InsnType.INVOKE) { InvokeNode invoke = (InvokeNode) insn; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java index 03b76533f..93388a8ed 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java @@ -9,10 +9,13 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.api.plugins.input.data.annotations.AnnotationVisibility; +import jadx.api.plugins.input.data.annotations.EncodedType; +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.data.annotations.IAnnotation; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AttrNode; -import jadx.core.dex.attributes.annotations.Annotation; import jadx.core.dex.attributes.annotations.AnnotationsList; import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.info.FieldInfo; @@ -20,14 +23,14 @@ import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ConstClassNode; import jadx.core.dex.instructions.ConstStringNode; -import jadx.core.dex.instructions.FillArrayNode; +import jadx.core.dex.instructions.FillArrayInsn; import jadx.core.dex.instructions.FilledNewArrayNode; import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IfOp; import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.NewArrayNode; -import jadx.core.dex.instructions.SwitchNode; +import jadx.core.dex.instructions.SwitchInsn; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.LiteralArg; @@ -108,7 +111,7 @@ public class ModVisitor extends AbstractVisitor { break; case SWITCH: - replaceConstKeys(parentClass, (SwitchNode) insn); + replaceConstKeys(parentClass, (SwitchInsn) insn); break; case NEW_ARRAY: @@ -116,7 +119,7 @@ public class ModVisitor extends AbstractVisitor { NewArrayNode newArrInsn = (NewArrayNode) insn; InsnNode nextInsn = getFirstUseSkipMove(insn.getResult()); if (nextInsn != null && nextInsn.getType() == InsnType.FILL_ARRAY) { - FillArrayNode fillArrInsn = (FillArrayNode) nextInsn; + FillArrayInsn fillArrInsn = (FillArrayInsn) nextInsn; if (checkArrSizes(mth, newArrInsn, fillArrInsn)) { InsnNode filledArr = makeFilledArrayInsn(mth, newArrInsn, fillArrInsn); replaceInsn(mth, block, i, filledArr); @@ -149,11 +152,13 @@ public class ModVisitor extends AbstractVisitor { } } - private static void replaceConstKeys(ClassNode parentClass, SwitchNode insn) { - for (int k = 0; k < insn.getCasesCount(); k++) { - FieldNode f = parentClass.getConstField(insn.getKeys()[k]); + private static void replaceConstKeys(ClassNode parentClass, SwitchInsn insn) { + int[] keys = insn.getKeys(); + int len = keys.length; + for (int k = 0; k < len; k++) { + FieldNode f = parentClass.getConstField(keys[k]); if (f != null) { - insn.getKeys()[k] = f; + insn.modifyKey(k, f); } } } @@ -194,30 +199,30 @@ public class ModVisitor extends AbstractVisitor { if (annotationsList == null) { return; } - for (Annotation annotation : annotationsList.getAll()) { - if (annotation.getVisibility() == Annotation.Visibility.SYSTEM) { + for (IAnnotation annotation : annotationsList.getAll()) { + if (annotation.getVisibility() == AnnotationVisibility.SYSTEM) { continue; } - for (Map.Entry entry : annotation.getValues().entrySet()) { + for (Map.Entry entry : annotation.getValues().entrySet()) { entry.setValue(replaceConstValue(parentCls, entry.getValue())); } } } - @SuppressWarnings({ "unchecked", "rawtypes" }) - private Object replaceConstValue(ClassNode parentCls, @Nullable Object value) { - if (value instanceof List) { - List listVal = (List) value; + @SuppressWarnings("unchecked") + private EncodedValue replaceConstValue(ClassNode parentCls, EncodedValue encodedValue) { + if (encodedValue.getType() == EncodedType.ENCODED_ARRAY) { + List listVal = (List) encodedValue.getValue(); if (!listVal.isEmpty()) { listVal.replaceAll(v -> replaceConstValue(parentCls, v)); } - return listVal; + return new EncodedValue(EncodedType.ENCODED_ARRAY, listVal); } - FieldNode constField = parentCls.getConstField(value); + FieldNode constField = parentCls.getConstField(encodedValue.getValue()); if (constField != null) { - return constField.getFieldInfo(); + return new EncodedValue(EncodedType.ENCODED_FIELD, constField.getFieldInfo()); } - return value; + return encodedValue; } private static void replaceConst(MethodNode mth, ClassNode parentClass, BlockNode block, int i, InsnNode insn) { @@ -252,10 +257,10 @@ public class ModVisitor extends AbstractVisitor { } } - private static boolean checkArrSizes(MethodNode mth, NewArrayNode newArrInsn, FillArrayNode fillArrInsn) { + private static boolean checkArrSizes(MethodNode mth, NewArrayNode newArrInsn, FillArrayInsn fillArrInsn) { int dataSize = fillArrInsn.getSize(); InsnArg arrSizeArg = newArrInsn.getArg(0); - Object value = InsnUtils.getConstValueByArg(mth.dex(), arrSizeArg); + Object value = InsnUtils.getConstValueByArg(mth.root(), arrSizeArg); if (value instanceof LiteralArg) { long literal = ((LiteralArg) value).getLiteral(); return dataSize == (int) literal; @@ -344,7 +349,7 @@ public class ModVisitor extends AbstractVisitor { private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) { MethodInfo callMth = co.getCallMth(); - MethodNode callMthNode = mth.dex().resolveMethod(callMth); + MethodNode callMthNode = mth.root().resolveMethod(callMth); if (callMthNode == null) { return; } @@ -456,7 +461,7 @@ public class ModVisitor extends AbstractVisitor { return parentInsn; } - private static InsnNode makeFilledArrayInsn(MethodNode mth, NewArrayNode newArrayNode, FillArrayNode insn) { + private static InsnNode makeFilledArrayInsn(MethodNode mth, NewArrayNode newArrayNode, FillArrayInsn insn) { ArgType insnArrayType = newArrayNode.getArrayType(); ArgType insnElementType = insnArrayType.getArrayElement(); ArgType elType = insn.getElementType(); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessInstructionsVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessInstructionsVisitor.java new file mode 100644 index 000000000..e48fa12a6 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessInstructionsVisitor.java @@ -0,0 +1,153 @@ +package jadx.core.dex.visitors; + +import org.jetbrains.annotations.Nullable; + +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.JumpInfo; +import jadx.core.dex.instructions.BaseInvokeNode; +import jadx.core.dex.instructions.FillArrayData; +import jadx.core.dex.instructions.FillArrayInsn; +import jadx.core.dex.instructions.FilledNewArrayNode; +import jadx.core.dex.instructions.GotoNode; +import jadx.core.dex.instructions.IfNode; +import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.SwitchData; +import jadx.core.dex.instructions.SwitchInsn; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.visitors.blocksmaker.BlockSplitter; +import jadx.core.utils.InsnUtils; +import jadx.core.utils.exceptions.JadxException; +import jadx.core.utils.exceptions.JadxRuntimeException; + +@JadxVisitor( + name = "Process Instructions Visitor", + desc = "Init instructions info", + runBefore = { + BlockSplitter.class + } +) +public class ProcessInstructionsVisitor extends AbstractVisitor { + + @Override + public void visit(MethodNode mth) throws JadxException { + if (mth.isNoCode()) { + return; + } + + initJumps(mth, mth.getInstructions()); + } + + private static void initJumps(MethodNode mth, InsnNode[] insnByOffset) { + for (int offset = 0; offset < insnByOffset.length; offset++) { + InsnNode insn = insnByOffset[offset]; + if (insn == null) { + continue; + } + switch (insn.getType()) { + case SWITCH: + SwitchInsn sw = (SwitchInsn) insn; + // default case + int nextInsnOffset = getNextInsnOffset(insnByOffset, offset); + if (nextInsnOffset != -1) { + addJump(mth, insnByOffset, offset, nextInsnOffset); + } + int dataTarget = sw.getDataTarget(); + InsnNode switchDataInsn = getInsnAtOffset(insnByOffset, dataTarget); + if (switchDataInsn != null && switchDataInsn.getType() == InsnType.SWITCH_DATA) { + sw.attachSwitchData((SwitchData) switchDataInsn, nextInsnOffset); + } else { + throw new JadxRuntimeException("Payload for fill-array not found at " + InsnUtils.formatOffset(dataTarget)); + } + for (int target : sw.getTargets()) { + addJump(mth, insnByOffset, offset, target); + } + break; + + case IF: + int next = getNextInsnOffset(insnByOffset, offset); + if (next != -1) { + addJump(mth, insnByOffset, offset, next); + } + addJump(mth, insnByOffset, offset, ((IfNode) insn).getTarget()); + break; + + case GOTO: + addJump(mth, insnByOffset, offset, ((GotoNode) insn).getTarget()); + break; + + case INVOKE: + ArgType retType = ((BaseInvokeNode) insn).getCallMth().getReturnType(); + mergeMoveResult(insnByOffset, offset, insn, retType); + break; + + case FILLED_NEW_ARRAY: + ArgType arrType = ((FilledNewArrayNode) insn).getArrayType(); + mergeMoveResult(insnByOffset, offset, insn, arrType); + break; + + case FILL_ARRAY: + FillArrayInsn fillArrayInsn = (FillArrayInsn) insn; + int target = fillArrayInsn.getTarget(); + InsnNode arrDataInsn = getInsnAtOffset(insnByOffset, target); + if (arrDataInsn != null && arrDataInsn.getType() == InsnType.FILL_ARRAY_DATA) { + fillArrayInsn.setArrayData((FillArrayData) arrDataInsn); + } else { + throw new JadxRuntimeException("Payload for fill-array not found at " + InsnUtils.formatOffset(target)); + } + break; + + default: + break; + } + } + } + + private static void mergeMoveResult(InsnNode[] insnByOffset, int offset, InsnNode insn, ArgType resType) { + int nextInsnOffset = getNextInsnOffset(insnByOffset, offset); + if (nextInsnOffset == -1) { + return; + } + InsnNode nextInsn = insnByOffset[nextInsnOffset]; + if (nextInsn.getType() != InsnType.MOVE_RESULT) { + return; + } + RegisterArg moveRes = nextInsn.getResult(); + insn.setResult(moveRes.duplicate(resType)); + insn.copyAttributesFrom(nextInsn); + insnByOffset[nextInsnOffset] = null; + } + + private static void addJump(MethodNode mth, InsnNode[] insnByOffset, int offset, int target) { + try { + insnByOffset[target].addAttr(AType.JUMP, new JumpInfo(offset, target)); + } catch (Exception e) { + mth.addError("Failed to set jump: " + InsnUtils.formatOffset(offset) + " -> " + InsnUtils.formatOffset(target), e); + } + } + + public static int getNextInsnOffset(InsnNode[] insnByOffset, int offset) { + int len = insnByOffset.length; + for (int i = offset + 1; i < len; i++) { + InsnNode insnNode = insnByOffset[i]; + if (insnNode != null && insnNode.getType() != InsnType.NOP) { + return i; + } + } + return -1; + } + + @Nullable + private static InsnNode getInsnAtOffset(InsnNode[] insnByOffset, int offset) { + int len = insnByOffset.length; + for (int i = offset; i < len; i++) { + InsnNode insnNode = insnByOffset[i]; + if (insnNode != null && insnNode.getType() != InsnType.NOP) { + return insnNode; + } + } + return null; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java index 1cf107ca2..961d4daf0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java @@ -17,7 +17,7 @@ import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.NewArrayNode; -import jadx.core.dex.instructions.SwitchNode; +import jadx.core.dex.instructions.SwitchInsn; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnWrapArg; @@ -26,10 +26,10 @@ import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.utils.InsnList; import jadx.core.utils.InsnRemover; @@ -80,7 +80,7 @@ public class ReSugarCode extends AbstractVisitor { break; case SWITCH: - processEnumSwitch(mth, (SwitchNode) insn); + processEnumSwitch(mth, (SwitchInsn) insn); break; default: @@ -156,7 +156,7 @@ public class ReSugarCode extends AbstractVisitor { return false; } InsnArg indexArg = insn.getArg(1); - Object value = InsnUtils.getConstValueByArg(mth.dex(), indexArg); + Object value = InsnUtils.getConstValueByArg(mth.root(), indexArg); if (value instanceof LiteralArg) { int index = (int) ((LiteralArg) value).getLiteral(); return index == putIndex; @@ -164,7 +164,7 @@ public class ReSugarCode extends AbstractVisitor { return false; } - private static void processEnumSwitch(MethodNode mth, SwitchNode insn) { + private static void processEnumSwitch(MethodNode mth, SwitchInsn insn) { InsnArg arg = insn.getArg(0); if (!arg.isInsnWrap()) { return; @@ -173,7 +173,7 @@ public class ReSugarCode extends AbstractVisitor { if (wrapInsn.getType() != InsnType.AGET) { return; } - EnumMapInfo enumMapInfo = checkEnumMapAccess(mth.dex(), wrapInsn); + EnumMapInfo enumMapInfo = checkEnumMapAccess(mth.root(), wrapInsn); if (enumMapInfo == null) { return; } @@ -184,8 +184,9 @@ public class ReSugarCode extends AbstractVisitor { if (valueMap == null) { return; } - Object[] keys = insn.getKeys(); - for (Object key : keys) { + int caseCount = insn.getKeys().length; + for (int i = 0; i < caseCount; i++) { + Object key = insn.getKey(i); Object newKey = valueMap.get(key); if (newKey == null) { return; @@ -195,8 +196,8 @@ public class ReSugarCode extends AbstractVisitor { if (!insn.replaceArg(arg, invArg)) { return; } - for (int i = 0; i < keys.length; i++) { - keys[i] = valueMap.get(keys[i]); + for (int i = 0; i < caseCount; i++) { + insn.modifyKey(i, valueMap.get(insn.getKey(i))); } enumMapField.add(AFlag.DONT_GENERATE); checkAndHideClass(enumMapField.getParentClass()); @@ -211,7 +212,7 @@ public class ReSugarCode extends AbstractVisitor { for (BlockNode block : clsInitMth.getBasicBlocks()) { for (InsnNode insn : block.getInstructions()) { if (insn.getType() == InsnType.APUT) { - addToEnumMap(enumCls.dex(), mapAttr, insn); + addToEnumMap(enumCls.root(), mapAttr, insn); } } } @@ -230,12 +231,12 @@ public class ReSugarCode extends AbstractVisitor { return mapAttr.getMap(field); } - private static void addToEnumMap(DexNode dex, EnumMapAttr mapAttr, InsnNode aputInsn) { + private static void addToEnumMap(RootNode root, EnumMapAttr mapAttr, InsnNode aputInsn) { InsnArg litArg = aputInsn.getArg(2); if (!litArg.isLiteral()) { return; } - EnumMapInfo mapInfo = checkEnumMapAccess(dex, aputInsn); + EnumMapInfo mapInfo = checkEnumMapAccess(root, aputInsn); if (mapInfo == null) { return; } @@ -252,7 +253,7 @@ public class ReSugarCode extends AbstractVisitor { if (!(index instanceof FieldInfo)) { return; } - FieldNode fieldNode = dex.resolveField((FieldInfo) index); + FieldNode fieldNode = root.resolveField((FieldInfo) index); if (fieldNode == null) { return; } @@ -260,7 +261,7 @@ public class ReSugarCode extends AbstractVisitor { mapAttr.add(field, literal, fieldNode); } - public static EnumMapInfo checkEnumMapAccess(DexNode dex, InsnNode checkInsn) { + public static EnumMapInfo checkEnumMapAccess(RootNode root, InsnNode checkInsn) { InsnArg sgetArg = checkInsn.getArg(0); InsnArg invArg = checkInsn.getArg(1); if (!sgetArg.isInsnWrap() || !invArg.isInsnWrap()) { @@ -275,7 +276,7 @@ public class ReSugarCode extends AbstractVisitor { if (!inv.getCallMth().getShortId().equals("ordinal()I")) { return null; } - ClassNode enumCls = dex.resolveClass(inv.getCallMth().getDeclClass()); + ClassNode enumCls = root.resolveClass(inv.getCallMth().getDeclClass()); if (enumCls == null || !enumCls.isEnum()) { return null; } @@ -283,7 +284,7 @@ public class ReSugarCode extends AbstractVisitor { if (!(index instanceof FieldInfo)) { return null; } - FieldNode enumMapField = dex.resolveField((FieldInfo) index); + FieldNode enumMapField = root.resolveField((FieldInfo) index); if (enumMapField == null || !enumMapField.getAccessFlags().isSynthetic()) { return null; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java index 188504636..8ca47b186 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java @@ -1,5 +1,6 @@ package jadx.core.dex.visitors; +import java.io.File; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; @@ -19,28 +20,25 @@ import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.files.FileUtils; -import jadx.core.utils.files.InputFile; public class RenameVisitor extends AbstractVisitor { @Override public void init(RootNode root) { - List dexNodes = root.getDexNodes(); - if (dexNodes.isEmpty()) { + List inputFiles = root.getArgs().getInputFiles(); + if (inputFiles.isEmpty()) { return; } - InputFile firstInputFile = dexNodes.get(0).getDexFile().getInputFile(); - Path inputFilePath = firstInputFile.getFile().getAbsoluteFile().toPath(); + Path inputFilePath = inputFiles.get(0).getAbsoluteFile().toPath(); String baseName = FileUtils.getPathBaseName(inputFilePath); Path deobfMapPath = inputFilePath.getParent().resolve(baseName + ".jobf"); JadxArgs args = root.getArgs(); - Deobfuscator deobfuscator = new Deobfuscator(args, dexNodes, deobfMapPath); + Deobfuscator deobfuscator = new Deobfuscator(args, root, deobfMapPath); if (args.isDeobfuscationOn()) { deobfuscator.execute(); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java index 561e92cd3..f893018f3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java @@ -9,6 +9,7 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.api.plugins.input.data.ILocalVar; import jadx.core.Consts; import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AFlag; @@ -111,15 +112,16 @@ public class DebugInfoApplyVisitor extends AbstractVisitor { int startOffset = getInsnOffsetByArg(ssaVar.getAssign()); int endOffset = max.get(); int regNum = ssaVar.getRegNum(); - for (LocalVar localVar : debugInfoAttr.getLocalVars()) { + for (ILocalVar localVar : debugInfoAttr.getLocalVars()) { if (localVar.getRegNum() == regNum) { - int startAddr = localVar.getStartAddr(); - int endAddr = localVar.getEndAddr(); + int startAddr = localVar.getStartOffset(); + int endAddr = localVar.getEndOffset(); if (isInside(startOffset, startAddr, endAddr) || isInside(endOffset, startAddr, endAddr)) { if (Consts.DEBUG) { LOG.debug("Apply debug info by offset for: {} to {}", ssaVar, localVar); } - applyDebugInfo(mth, ssaVar, localVar.getType(), localVar.getName()); + ArgType type = DebugInfoAttachVisitor.getVarType(localVar); + applyDebugInfo(mth, ssaVar, type, localVar.getName()); break; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java new file mode 100644 index 000000000..a04c1a4f8 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java @@ -0,0 +1,163 @@ +package jadx.core.dex.visitors.debuginfo; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.plugins.input.data.IDebugInfo; +import jadx.api.plugins.input.data.ILocalVar; +import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr; +import jadx.core.dex.attributes.nodes.RegDebugInfoAttr; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.parser.SignatureParser; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.dex.visitors.JadxVisitor; +import jadx.core.dex.visitors.blocksmaker.BlockSplitter; +import jadx.core.dex.visitors.ssa.SSATransform; +import jadx.core.utils.ErrorsCounter; +import jadx.core.utils.Utils; +import jadx.core.utils.exceptions.JadxException; + +import static jadx.core.codegen.CodeWriter.NL; + +@JadxVisitor( + name = "Debug Info Parser", + desc = "Attach debug information (variable names and types, instruction lines)", + runBefore = { + BlockSplitter.class, + SSATransform.class + } +) +public class DebugInfoAttachVisitor extends AbstractVisitor { + + private static final Logger LOG = LoggerFactory.getLogger(DebugInfoAttachVisitor.class); + + @Override + public void visit(MethodNode mth) throws JadxException { + try { + IDebugInfo debugInfo = mth.getDebugInfo(); + if (debugInfo != null) { + processDebugInfo(mth, debugInfo); + } + } catch (Exception e) { + mth.addComment("JADX WARNING: Error to parse debug info: " + + ErrorsCounter.formatMsg(mth, e.getMessage()) + + NL + Utils.getStackTrace(e)); + } + } + + private void processDebugInfo(MethodNode mth, IDebugInfo debugInfo) { + InsnNode[] insnArr = mth.getInstructions(); + attachSourceLines(debugInfo.getSourceLineMapping(), insnArr); + attachDebugInfo(mth, debugInfo.getLocalVars(), insnArr); + setMethodSourceLine(mth, insnArr); + } + + private void attachSourceLines(Map lineMapping, InsnNode[] insnArr) { + for (InsnNode insn : insnArr) { + if (insn != null) { + Integer sourceLine = lineMapping.get(insn.getOffset()); + if (sourceLine != null) { + insn.setSourceLine(sourceLine); + } + } + } + } + + private void attachDebugInfo(MethodNode mth, List localVars, InsnNode[] insnArr) { + if (localVars.isEmpty()) { + return; + } + for (ILocalVar var : localVars) { + int regNum = var.getRegNum(); + int start = var.getStartOffset(); + int end = var.getEndOffset(); + + ArgType type = getVarType(var); + RegDebugInfoAttr debugInfoAttr = new RegDebugInfoAttr(type, var.getName()); + if (start < 0) { + // attach to method arguments + RegisterArg thisArg = mth.getThisArg(); + if (thisArg != null) { + attachDebugInfo(thisArg, debugInfoAttr, regNum); + } + for (RegisterArg arg : mth.getArgRegs()) { + attachDebugInfo(arg, debugInfoAttr, regNum); + } + start = 0; + } + for (int i = start; i <= end; i++) { + InsnNode insn = insnArr[i]; + if (insn != null) { + attachDebugInfo(insn.getResult(), debugInfoAttr, regNum); + for (InsnArg arg : insn.getArguments()) { + attachDebugInfo(arg, debugInfoAttr, regNum); + } + } + } + } + + mth.addAttr(new LocalVarsDebugInfoAttr(localVars)); + } + + private void attachDebugInfo(InsnArg arg, RegDebugInfoAttr debugInfoAttr, int regNum) { + if (arg instanceof RegisterArg) { + RegisterArg reg = (RegisterArg) arg; + if (regNum == reg.getRegNum()) { + reg.addAttr(debugInfoAttr); + } + } + } + + public static ArgType getVarType(ILocalVar var) { + ArgType type = ArgType.parse(var.getType()); + String sign = var.getSignature(); + if (sign == null) { + return type; + } + try { + ArgType gType = new SignatureParser(sign).consumeType(); + if (checkSignature(type, gType)) { + return gType; + } + } catch (Exception e) { + LOG.error("Can't parse signature for local variable: {}", sign, e); + } + return type; + } + + private static boolean checkSignature(ArgType type, ArgType gType) { + boolean apply; + ArgType el = gType.getArrayRootElement(); + if (el.isGeneric()) { + if (!type.getArrayRootElement().getObject().equals(el.getObject())) { + LOG.warn("Generic type in debug info not equals: {} != {}", type, gType); + } + apply = true; + } else { + apply = el.isGenericType(); + } + return apply; + } + + /** + * Set method source line from first instruction + */ + private void setMethodSourceLine(MethodNode mth, InsnNode[] insnArr) { + for (InsnNode insn : insnArr) { + if (insn != null) { + int line = insn.getSourceLine(); + if (line != 0) { + mth.setSourceLine(line - 1); + return; + } + } + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java deleted file mode 100644 index 185ab03a3..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java +++ /dev/null @@ -1,119 +0,0 @@ -package jadx.core.dex.visitors.debuginfo; - -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jadx.core.Consts; -import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr; -import jadx.core.dex.attributes.nodes.RegDebugInfoAttr; -import jadx.core.dex.instructions.args.InsnArg; -import jadx.core.dex.instructions.args.RegisterArg; -import jadx.core.dex.nodes.InsnNode; -import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.visitors.AbstractVisitor; -import jadx.core.dex.visitors.JadxVisitor; -import jadx.core.dex.visitors.blocksmaker.BlockSplitter; -import jadx.core.dex.visitors.ssa.SSATransform; -import jadx.core.utils.ErrorsCounter; -import jadx.core.utils.Utils; -import jadx.core.utils.exceptions.JadxException; - -import static jadx.core.codegen.CodeWriter.NL; - -@JadxVisitor( - name = "Debug Info Parser", - desc = "Parse debug information (variable names and types, instruction lines)", - runBefore = { - BlockSplitter.class, - SSATransform.class - } -) -public class DebugInfoParseVisitor extends AbstractVisitor { - - private static final Logger LOG = LoggerFactory.getLogger(DebugInfoParseVisitor.class); - - @Override - public void visit(MethodNode mth) throws JadxException { - try { - int debugOffset = mth.getDebugInfoOffset(); - if (debugOffset > 0 && mth.dex().checkOffset(debugOffset)) { - processDebugInfo(mth, debugOffset); - } - } catch (Exception e) { - mth.addComment("JADX WARNING: Error to parse debug info: " - + ErrorsCounter.formatMsg(mth, e.getMessage()) - + NL + Utils.getStackTrace(e)); - } - } - - private void processDebugInfo(MethodNode mth, int debugOffset) { - InsnNode[] insnArr = mth.getInstructions(); - DebugInfoParser debugInfoParser = new DebugInfoParser(mth, debugOffset, insnArr); - List localVars = debugInfoParser.process(); - attachDebugInfo(mth, localVars, insnArr); - setMethodSourceLine(mth, insnArr); - } - - private void attachDebugInfo(MethodNode mth, List localVars, InsnNode[] insnArr) { - if (localVars.isEmpty()) { - return; - } - if (Consts.DEBUG) { - LOG.debug("Parsed debug info for {}: ", mth); - localVars.forEach(v -> LOG.debug(" {}", v)); - } - localVars.forEach(var -> { - int start = var.getStartAddr(); - int end = var.getEndAddr(); - RegDebugInfoAttr debugInfoAttr = new RegDebugInfoAttr(var); - if (start < 0) { - // attach to method arguments - RegisterArg thisArg = mth.getThisArg(); - if (thisArg != null) { - attachDebugInfo(thisArg, var, debugInfoAttr); - } - for (RegisterArg arg : mth.getArgRegs()) { - attachDebugInfo(arg, var, debugInfoAttr); - } - start = 0; - } - for (int i = start; i <= end; i++) { - InsnNode insn = insnArr[i]; - if (insn != null) { - attachDebugInfo(insn.getResult(), var, debugInfoAttr); - for (InsnArg arg : insn.getArguments()) { - attachDebugInfo(arg, var, debugInfoAttr); - } - } - } - }); - - mth.addAttr(new LocalVarsDebugInfoAttr(localVars)); - } - - private void attachDebugInfo(InsnArg arg, LocalVar var, RegDebugInfoAttr debugInfoAttr) { - if (arg instanceof RegisterArg) { - RegisterArg reg = (RegisterArg) arg; - if (var.getRegNum() == reg.getRegNum()) { - reg.addAttr(debugInfoAttr); - } - } - } - - /** - * Set method source line from first instruction - */ - private void setMethodSourceLine(MethodNode mth, InsnNode[] insnArr) { - for (InsnNode insn : insnArr) { - if (insn != null) { - int line = insn.getSourceLine(); - if (line != 0) { - mth.setSourceLine(line - 1); - return; - } - } - } - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java deleted file mode 100644 index 35a5fa122..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java +++ /dev/null @@ -1,120 +0,0 @@ -package jadx.core.dex.visitors.debuginfo; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.DexNode; -import jadx.core.dex.nodes.parser.SignatureParser; -import jadx.core.utils.InsnUtils; - -public final class LocalVar { - private static final Logger LOG = LoggerFactory.getLogger(LocalVar.class); - - private final int regNum; - private final String name; - private final ArgType type; - - private boolean isEnd; - private int startAddr; - private int endAddr; - - public LocalVar(DexNode dex, int rn, int nameId, int typeId, int signId) { - this(rn, dex.getString(nameId), dex.getType(typeId), dex.getString(signId)); - } - - public LocalVar(int regNum, String name, ArgType type) { - this(regNum, name, type, null); - } - - public LocalVar(int regNum, String name, ArgType type, String sign) { - this.regNum = regNum; - this.name = name; - if (sign != null) { - try { - ArgType gType = new SignatureParser(sign).consumeType(); - if (checkSignature(type, gType)) { - type = gType; - } - } catch (Exception e) { - LOG.error("Can't parse signature for local variable: {}", sign, e); - } - } - this.type = type; - } - - private boolean checkSignature(ArgType type, ArgType gType) { - boolean apply; - ArgType el = gType.getArrayRootElement(); - if (el.isGeneric()) { - if (!type.getArrayRootElement().getObject().equals(el.getObject())) { - LOG.warn("Generic type in debug info not equals: {} != {}", type, gType); - } - apply = true; - } else { - apply = el.isGenericType(); - } - return apply; - } - - public void start(int addr) { - this.isEnd = false; - this.startAddr = addr; - } - - /** - * Sets end address of local variable - * - * @param addr address - * @return true if local variable was active, else false - */ - public boolean end(int addr) { - if (isEnd) { - return false; - } - this.isEnd = true; - this.endAddr = addr; - return true; - } - - public int getRegNum() { - return regNum; - } - - public String getName() { - return name; - } - - public ArgType getType() { - return type; - } - - public boolean isEnd() { - return isEnd; - } - - public int getStartAddr() { - return startAddr; - } - - public int getEndAddr() { - return endAddr; - } - - @Override - public boolean equals(Object obj) { - return super.equals(obj); - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public String toString() { - return InsnUtils.formatOffset(startAddr) - + '-' + (isEnd ? InsnUtils.formatOffset(endAddr) : " ") - + ": r" + regNum + " '" + name + "' " + type; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java index 642ca5f5f..8171f5000 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java @@ -61,7 +61,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor { // for each try block search nearest dominator block for (TryCatchBlock tb : tryBlocks) { if (tb.getHandlersCount() == 0) { - mth.addWarn("No exception handlers in catch block: " + tb); + // mth.addWarn("No exception handlers in catch block: " + tb); continue; } processTryCatchBlock(mth, tb, tryBlocksMap); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java index d538ce714..ec2d88b7c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java @@ -22,7 +22,7 @@ import jadx.core.dex.attributes.nodes.LoopInfo; import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.InsnType; -import jadx.core.dex.instructions.SwitchNode; +import jadx.core.dex.instructions.SwitchInsn; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.Edge; @@ -127,7 +127,7 @@ public class RegionMaker { break; case SWITCH: - next = processSwitch(r, block, (SwitchNode) insn, stack); + next = processSwitch(r, block, (SwitchInsn) insn, stack); processed = true; break; @@ -738,15 +738,14 @@ public class RegionMaker { region.add(start); } - private BlockNode processSwitch(IRegion currentRegion, BlockNode block, SwitchNode insn, RegionStack stack) { + private BlockNode processSwitch(IRegion currentRegion, BlockNode block, SwitchInsn insn, RegionStack stack) { // map case blocks to keys int len = insn.getTargets().length; Map> blocksMap = new LinkedHashMap<>(len); - Object[] keysArr = insn.getKeys(); BlockNode[] targetBlocksArr = insn.getTargetBlocks(); for (int i = 0; i < len; i++) { List keys = blocksMap.computeIfAbsent(targetBlocksArr[i], k -> new ArrayList<>(2)); - keys.add(keysArr[i]); + keys.add(insn.getKey(i)); } BlockNode defCase = insn.getDefTargetBlock(); if (defCase != null) { @@ -899,7 +898,7 @@ public class RegionMaker { * 1. single 'default' case * 2. filler cases if switch is 'packed' and 'default' case is empty */ - private void removeEmptyCases(SwitchNode insn, SwitchRegion sw, BlockNode defCase) { + private void removeEmptyCases(SwitchInsn insn, SwitchRegion sw, BlockNode defCase) { boolean defaultCaseIsEmpty; if (defCase == null) { defaultCaseIsEmpty = true; diff --git a/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java b/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java index 8b5d4e0a6..bc8b86ab5 100644 --- a/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java +++ b/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java @@ -4,7 +4,6 @@ import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; -import java.util.List; import java.util.Set; import org.slf4j.Logger; @@ -12,7 +11,6 @@ import org.slf4j.LoggerFactory; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.FileUtils; @@ -62,14 +60,11 @@ public class ExportGradleProject { } private void skipGeneratedClasses() { - for (DexNode dexNode : root.getDexNodes()) { - List classes = dexNode.getClasses(); - for (ClassNode cls : classes) { - String shortName = cls.getClassInfo().getShortName(); - if (IGNORE_CLS_NAMES.contains(shortName)) { - cls.add(AFlag.DONT_GENERATE); - LOG.debug("Skip class: {}", cls); - } + for (ClassNode cls : root.getClasses()) { + String shortName = cls.getClassInfo().getShortName(); + if (IGNORE_CLS_NAMES.contains(shortName)) { + cls.add(AFlag.DONT_GENERATE); + LOG.debug("Skip class: {}", cls); } } } diff --git a/jadx-core/src/main/java/jadx/core/utils/AsmUtils.java b/jadx-core/src/main/java/jadx/core/utils/AsmUtils.java deleted file mode 100644 index 32c67240b..000000000 --- a/jadx-core/src/main/java/jadx/core/utils/AsmUtils.java +++ /dev/null @@ -1,22 +0,0 @@ -package jadx.core.utils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; - -import org.objectweb.asm.ClassReader; - -public class AsmUtils { - - private AsmUtils() { - } - - public static String getNameFromClassFile(File file) throws IOException { - String className; - try (FileInputStream in = new FileInputStream(file)) { - ClassReader classReader = new ClassReader(in); - className = classReader.getClassName(); - } - return className; - } -} diff --git a/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java b/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java index c886a42ef..dd06b64cf 100644 --- a/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java +++ b/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java @@ -1,5 +1,6 @@ package jadx.core.utils; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -36,7 +37,12 @@ public class ErrorsCounter { } public static String formatMsg(IDexNode node, String msg) { - return msg + " in " + node.typeName() + ": " + node + ", dex: " + node.dex().getDexFile().getName(); + return msg + " in " + node.typeName() + ": " + node + ", file: " + getNodeFile(node); + } + + private static String getNodeFile(IDexNode node) { + Path inputPath = node.getInputPath(); + return inputPath == null ? "synthetic" : inputPath.toString(); } private synchronized String addError(N node, String error, @Nullable Throwable e) { diff --git a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java index d76c86779..c18ebc50c 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java @@ -6,10 +6,9 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.android.dx.io.instructions.DecodedInstruction; - import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.FieldInitAttr; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.instructions.ConstClassNode; import jadx.core.dex.instructions.ConstStringNode; @@ -20,12 +19,10 @@ import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.nodes.BlockNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.parser.FieldInitAttr; -import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.dex.nodes.RootNode; public class InsnUtils { @@ -34,23 +31,6 @@ public class InsnUtils { private InsnUtils() { } - public static int getArg(DecodedInstruction insn, int arg) { - switch (arg) { - case 0: - return insn.getA(); - case 1: - return insn.getB(); - case 2: - return insn.getC(); - case 3: - return insn.getD(); - case 4: - return insn.getE(); - default: - throw new JadxRuntimeException("Wrong argument number: " + arg); - } - } - public static String formatOffset(int offset) { if (offset < 0) { return "?"; @@ -77,7 +57,7 @@ public class InsnUtils { * * @return LiteralArg, String, ArgType or null */ - public static Object getConstValueByArg(DexNode dex, InsnArg arg) { + public static Object getConstValueByArg(RootNode root, InsnArg arg) { if (arg.isLiteral()) { return arg; } @@ -88,13 +68,13 @@ public class InsnUtils { return null; } if (parInsn.getType() == InsnType.MOVE) { - return getConstValueByArg(dex, parInsn.getArg(0)); + return getConstValueByArg(root, parInsn.getArg(0)); } - return getConstValueByInsn(dex, parInsn); + return getConstValueByInsn(root, parInsn); } if (arg.isInsnWrap()) { InsnNode insn = ((InsnWrapArg) arg).getWrapInsn(); - return getConstValueByInsn(dex, insn); + return getConstValueByInsn(root, insn); } return null; } @@ -105,7 +85,7 @@ public class InsnUtils { * @return LiteralArg, String, ArgType or null */ @Nullable - public static Object getConstValueByInsn(DexNode dex, InsnNode insn) { + public static Object getConstValueByInsn(RootNode root, InsnNode insn) { switch (insn.getType()) { case CONST: return insn.getArg(0); @@ -115,13 +95,19 @@ public class InsnUtils { return ((ConstClassNode) insn).getClsType(); case SGET: FieldInfo f = (FieldInfo) ((IndexInsnNode) insn).getIndex(); - FieldNode fieldNode = dex.root().deepResolveField(f); + FieldNode fieldNode = root.deepResolveField(f); if (fieldNode == null) { - LOG.warn("Field {} not found in dex {}", f, dex); + LOG.warn("Field {} not found", f); return null; } FieldInitAttr attr = fieldNode.get(AType.FIELD_INIT); - return attr != null ? attr.getValue() : null; + if (attr != null) { + if (attr.getValueType() == FieldInitAttr.InitType.CONST) { + return attr.getEncodedValue().getValue(); + } + return attr.getInsn(); + } + return null; default: return null; diff --git a/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java b/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java index 6ac93d7e4..1ff8f2bdf 100644 --- a/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java @@ -1,12 +1,14 @@ package jadx.core.utils; +import java.io.BufferedInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.StringWriter; +import java.nio.file.Files; import java.nio.file.Path; import org.jf.baksmali.Adaptors.ClassDefinition; import org.jf.baksmali.BaksmaliOptions; -import org.jf.dexlib2.DexFileFactory; import org.jf.dexlib2.dexbacked.DexBackedClassDef; import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.smali.Smali; @@ -15,7 +17,6 @@ import org.jf.util.IndentingWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jadx.core.dex.nodes.DexNode; import jadx.core.utils.exceptions.JadxRuntimeException; // TODO: move smali dependencies out from jadx-core @@ -33,10 +34,12 @@ public class SmaliUtils { } } - public static boolean getSmaliCode(DexNode dex, int clsDefOffset, StringWriter stringWriter) { - try { - Path path = dex.getDexFile().getPath(); - DexBackedDexFile dexFile = DexFileFactory.loadDexFile(path.toFile(), null); + public static boolean getSmaliCode(Path path, int clsDefOffset, StringWriter stringWriter) { + if (clsDefOffset == 0) { + return false; + } + try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(path))) { + DexBackedDexFile dexFile = DexBackedDexFile.fromInputStream(null, inputStream); DexBackedClassDef dexBackedClassDef = new DexBackedClassDef(dexFile, clsDefOffset, 0); getSmaliCode(dexBackedClassDef, stringWriter); return true; diff --git a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java index 334219007..d59accfb8 100644 --- a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java @@ -10,24 +10,24 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; +import jadx.api.plugins.input.data.annotations.EncodedType; +import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.core.codegen.ClassGen; import jadx.core.codegen.CodeWriter; import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.FieldInitAttr; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ConstStorage; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.ProcessState; import jadx.core.dex.nodes.RootNode; -import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.xmlgen.ResourceStorage; import jadx.core.xmlgen.entry.ResourceEntry; @@ -44,7 +44,7 @@ public class AndroidResourcesUtils { public static ClassNode searchAppResClass(RootNode root, ResourceStorage resStorage) { String appPackage = root.getAppPackage(); String fullName = appPackage != null ? appPackage + ".R" : "R"; - ClassNode resCls = root.searchClassByName(fullName); + ClassNode resCls = root.resolveClass(fullName); if (resCls != null) { addResourceFields(resCls, resStorage, true); return resCls; @@ -82,11 +82,7 @@ public class AndroidResourcesUtils { @Nullable private static ClassNode makeClass(RootNode root, String clsName, ResourceStorage resStorage) { - List dexNodes = root.getDexNodes(); - if (dexNodes.isEmpty()) { - return null; - } - ClassNode rCls = new ClassNode(dexNodes.get(0), clsName, AccessFlags.ACC_PUBLIC | AccessFlags.ACC_FINAL); + ClassNode rCls = new ClassNode(root, clsName, AccessFlags.PUBLIC | AccessFlags.FINAL); rCls.addAttr(AType.COMMENTS, "This class is generated by JADX"); rCls.setState(ProcessState.PROCESS_COMPLETE); return rCls; @@ -113,9 +109,10 @@ public class AndroidResourcesUtils { } FieldNode rField = typeCls.searchFieldByName(resName); if (rField == null) { - FieldInfo rFieldInfo = FieldInfo.from(typeCls.dex(), typeCls.getClassInfo(), resName, ArgType.INT); - rField = new FieldNode(typeCls, rFieldInfo, AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL); - rField.addAttr(FieldInitAttr.constValue(resource.getId())); + FieldInfo rFieldInfo = FieldInfo.from(typeCls.root(), typeCls.getClassInfo(), resName, ArgType.INT); + rField = new FieldNode(typeCls, rFieldInfo, AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL); + EncodedValue value = new EncodedValue(EncodedType.ENCODED_INT, resource.getId()); + rField.addAttr(FieldInitAttr.constValue(value)); typeCls.getFields().add(rField); if (rClsExists) { rField.addAttr(AType.COMMENTS, "added by JADX"); @@ -134,8 +131,8 @@ public class AndroidResourcesUtils { @NotNull private static ClassNode addClassForResType(ClassNode resCls, boolean rClsExists, String typeName) { - ClassNode newTypeCls = new ClassNode(resCls.dex(), resCls.getFullName() + '$' + typeName, - AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL); + ClassNode newTypeCls = new ClassNode(resCls.root(), resCls.getFullName() + '$' + typeName, + AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL); resCls.addInnerClass(newTypeCls); if (rClsExists) { newTypeCls.addAttr(AType.COMMENTS, "added by JADX"); diff --git a/jadx-core/src/main/java/jadx/core/utils/files/DexFile.java b/jadx-core/src/main/java/jadx/core/utils/files/DexFile.java deleted file mode 100644 index 1b490f427..000000000 --- a/jadx-core/src/main/java/jadx/core/utils/files/DexFile.java +++ /dev/null @@ -1,40 +0,0 @@ -package jadx.core.utils.files; - -import java.nio.file.Path; - -import com.android.dex.Dex; - -public class DexFile { - private final InputFile inputFile; - private final String name; - private final Dex dexBuf; - private final Path path; - - public DexFile(InputFile inputFile, String name, Dex dexBuf, Path path) { - this.inputFile = inputFile; - this.name = name; - this.dexBuf = dexBuf; - this.path = path; - } - - public String getName() { - return name; - } - - public Dex getDexBuf() { - return dexBuf; - } - - public Path getPath() { - return path; - } - - public InputFile getInputFile() { - return inputFile; - } - - @Override - public String toString() { - return inputFile + (name.isEmpty() ? "" : ':' + name); - } -} diff --git a/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java b/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java deleted file mode 100644 index 27c3bf188..000000000 --- a/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java +++ /dev/null @@ -1,233 +0,0 @@ -package jadx.core.utils.files; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; -import java.util.jar.JarOutputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.android.dex.Dex; -import com.android.dex.DexException; - -import jadx.core.utils.AsmUtils; -import jadx.core.utils.SmaliUtils; -import jadx.core.utils.exceptions.DecodeException; -import jadx.core.utils.exceptions.JadxException; -import jadx.core.utils.exceptions.JadxRuntimeException; - -import static jadx.core.codegen.CodeWriter.NL; -import static jadx.core.utils.files.FileUtils.isApkFile; -import static jadx.core.utils.files.FileUtils.isZipDexFile; - -public class InputFile { - private static final Logger LOG = LoggerFactory.getLogger(InputFile.class); - - private final File file; - private final List dexFiles = new ArrayList<>(); - - public static void addFilesFrom(File file, List list, boolean skipSources) throws IOException, DecodeException { - InputFile inputFile = new InputFile(file); - inputFile.searchDexFiles(skipSources); - list.add(inputFile); - } - - private InputFile(File file) throws IOException { - if (!file.exists()) { - throw new IOException("File not found: " + file.getAbsolutePath()); - } - this.file = file; - } - - private void searchDexFiles(boolean skipSources) throws IOException, DecodeException { - String fileName = file.getName(); - - if (fileName.endsWith(".dex")) { - addDexFile(fileName, file.toPath()); - return; - } - if (fileName.endsWith(".smali")) { - Path output = FileUtils.createTempFile(".dex"); - SmaliUtils.assembleDex(output.toAbsolutePath().toString(), file.getAbsolutePath()); - addDexFile(fileName, output); - return; - } - if (fileName.endsWith(".class")) { - for (Path path : loadFromClassFile(file)) { - addDexFile(fileName, path); - } - return; - } - if (isApkFile(file) || isZipDexFile(file)) { - loadFromZip(".dex"); - return; - } - if (fileName.endsWith(".jar") || fileName.endsWith(".aar")) { - // check if jar/aar contains '.dex' files - if (loadFromZip(".dex")) { - return; - } - if (fileName.endsWith(".jar")) { - for (Path path : loadFromJar(file.toPath())) { - addDexFile(fileName, path); - } - return; - } - if (fileName.endsWith(".aar")) { - loadFromZip(".jar"); - return; - } - return; - } - if (skipSources) { - return; - } - LOG.warn("No dex files found in {}", file); - } - - private boolean loadFromZip(String ext) throws IOException, DecodeException { - int index = 0; - try (ZipFile zf = new ZipFile(file)) { - // Input file could be .apk or .zip files - // we should consider the input file could contain only one single dex, multi-dex, - // or instantRun support dex for Android .apk files - String instantRunDexSuffix = "classes" + ext; - for (Enumeration e = zf.entries(); e.hasMoreElements();) { - ZipEntry entry = e.nextElement(); - if (!ZipSecurity.isValidZipEntry(entry)) { - continue; - } - - String entryName = entry.getName(); - try (InputStream inputStream = zf.getInputStream(entry)) { - if ((entryName.startsWith("classes") && entryName.endsWith(ext)) - || entryName.endsWith(instantRunDexSuffix)) { - switch (ext) { - case ".dex": - Path path = copyToTmpDex(entryName, inputStream); - if (addDexFile(entryName, path)) { - index++; - } - break; - - case ".jar": - index++; - Path jarFile = FileUtils.createTempFile(entryName); - Files.copy(inputStream, jarFile, StandardCopyOption.REPLACE_EXISTING); - for (Path p : loadFromJar(jarFile)) { - addDexFile(entryName, p); - } - break; - - default: - throw new JadxRuntimeException("Unexpected extension in zip: " + ext); - } - } else if (entryName.equals("instant-run.zip") && ext.equals(".dex")) { - Path jarFile = FileUtils.createTempFile("instant-run.zip"); - Files.copy(inputStream, jarFile, StandardCopyOption.REPLACE_EXISTING); - InputFile tempFile = new InputFile(jarFile.toFile()); - tempFile.loadFromZip(ext); - List files = tempFile.getDexFiles(); - if (!files.isEmpty()) { - index += files.size(); - this.dexFiles.addAll(files); - } - } - } - } - } - return index > 0; - } - - private boolean addDexFile(String entryName, @Nullable Path filePath) { - if (filePath == null) { - return false; - } - Dex dexBuf = loadDexBufFromPath(filePath, entryName); - if (dexBuf == null) { - return false; - } - dexFiles.add(new DexFile(this, entryName, dexBuf, filePath)); - return true; - } - - @Nullable - private Dex loadDexBufFromPath(Path path, String entryName) { - try { - return new Dex(Files.readAllBytes(path)); - } catch (DexException e) { - LOG.error("Failed to load dex file: {}, error: {}", entryName, e.getMessage()); - } catch (Exception e) { - LOG.error("Failed to load dex file: {}, error: {}", entryName, e.getMessage(), e); - } - return null; - } - - @Nullable - private Path copyToTmpDex(String entryName, InputStream inputStream) { - try { - Path path = FileUtils.createTempFile(".dex"); - Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING); - return path; - } catch (Exception e) { - LOG.error("Failed to load file: {}, error: {}", entryName, e.getMessage(), e); - return null; - } - } - - private static List loadFromJar(Path jar) throws DecodeException { - JavaToDex j2d = new JavaToDex(); - try { - LOG.info("converting to dex: {} ...", jar.getFileName()); - List pathList = j2d.convert(jar); - if (pathList.isEmpty()) { - throw new JadxException("Empty dx output"); - } - if (LOG.isDebugEnabled()) { - LOG.debug("result dex files: {}", pathList); - } - return pathList; - } catch (Exception e) { - throw new DecodeException("java class to dex conversion error:" + NL + " " + e.getMessage(), e); - } finally { - if (j2d.isError()) { - LOG.warn("dx message: {}", j2d.getDxErrors()); - } - } - } - - private static List loadFromClassFile(File file) throws IOException, DecodeException { - Path outFile = FileUtils.createTempFile(".jar"); - try (JarOutputStream jo = new JarOutputStream(Files.newOutputStream(outFile))) { - String clsName = AsmUtils.getNameFromClassFile(file); - if (clsName == null || !ZipSecurity.isValidZipEntryName(clsName)) { - throw new IOException("Can't read class name from file: " + file); - } - FileUtils.addFileToJar(jo, file, clsName + ".class"); - } - return loadFromJar(outFile); - } - - public File getFile() { - return file; - } - - public List getDexFiles() { - return dexFiles; - } - - @Override - public String toString() { - return file.getAbsolutePath(); - } -} diff --git a/jadx-core/src/main/java/jadx/core/utils/files/JavaToDex.java b/jadx-core/src/main/java/jadx/core/utils/files/JavaToDex.java deleted file mode 100644 index ec37aebaa..000000000 --- a/jadx-core/src/main/java/jadx/core/utils/files/JavaToDex.java +++ /dev/null @@ -1,74 +0,0 @@ -package jadx.core.utils.files; - -import java.io.ByteArrayOutputStream; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -import com.android.dx.command.dexer.DxContext; -import com.android.dx.command.dexer.Main; -import com.android.dx.command.dexer.Main.Arguments; - -import jadx.core.utils.exceptions.JadxException; - -public class JavaToDex { - - private static final String CHARSET_NAME = "UTF-8"; - - private static class DxArgs extends Arguments { - public DxArgs(DxContext context, String dexDir, String[] input) { - super(context); - outName = dexDir; - fileNames = input; - jarOutput = false; - multiDex = true; - - optimize = true; - localInfo = true; - coreLibrary = true; - - debug = true; - warnings = true; - minSdkVersion = 28; - } - } - - private String dxErrors; - - public List convert(Path jar) throws JadxException { - try (ByteArrayOutputStream out = new ByteArrayOutputStream(); - ByteArrayOutputStream errOut = new ByteArrayOutputStream()) { - DxContext context = new DxContext(out, errOut); - Path dir = FileUtils.createTempDir("jar-to-dex-"); - DxArgs args = new DxArgs( - context, - dir.toAbsolutePath().toString(), - new String[] { jar.toAbsolutePath().toString() }); - int result = new Main(context).runDx(args); - dxErrors = errOut.toString(CHARSET_NAME); - if (result != 0) { - throw new JadxException("Java to dex conversion error, code: " + result); - } - List list = new ArrayList<>(); - try (DirectoryStream ds = Files.newDirectoryStream(dir)) { - for (Path child : ds) { - list.add(child); - child.toFile().deleteOnExit(); - } - } - return list; - } catch (Exception e) { - throw new JadxException("dx exception: " + e.getMessage(), e); - } - } - - public String getDxErrors() { - return dxErrors; - } - - public boolean isError() { - return dxErrors != null && !dxErrors.isEmpty(); - } -} diff --git a/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java b/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java index 805f7123c..49c07bdbb 100644 --- a/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java +++ b/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java @@ -1,20 +1,21 @@ package jadx.api; import java.io.File; +import java.net.URL; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import jadx.core.utils.files.FileUtils; -import jadx.core.utils.files.InputFileTest; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; public class JadxDecompilerTest { @Test public void testExampleUsage() { - File sampleApk = InputFileTest.getFileFromSampleDir("app-with-fake-dex.apk"); + File sampleApk = getFileFromSampleDir("app-with-fake-dex.apk"); File outDir = FileUtils.createTempDir("jadx-usage-example").toFile(); // test simple apk loading @@ -22,18 +23,28 @@ public class JadxDecompilerTest { args.getInputFiles().add(sampleApk); args.setOutDir(outDir); - JadxDecompiler jadx = new JadxDecompiler(args); - jadx.load(); - jadx.save(); - jadx.printErrorsReport(); + try (JadxDecompiler jadx = new JadxDecompiler(args)) { + jadx.load(); + jadx.save(); + jadx.printErrorsReport(); - // test class print - for (JavaClass cls : jadx.getClasses()) { - System.out.println(cls.getCode()); + // test class print + for (JavaClass cls : jadx.getClasses()) { + System.out.println(cls.getCode()); + } + + assertThat(jadx.getClasses(), Matchers.hasSize(3)); + assertThat(jadx.getErrorsCount(), Matchers.is(0)); } + } - assertThat(jadx.getClasses(), Matchers.hasSize(3)); - assertThat(jadx.getErrorsCount(), Matchers.is(0)); + private static final String TEST_SAMPLES_DIR = "test-samples/"; + + public static File getFileFromSampleDir(String fileName) { + URL resource = JadxDecompilerTest.class.getClassLoader().getResource(TEST_SAMPLES_DIR + fileName); + assertThat(resource, notNullValue()); + String pathStr = resource.getFile(); + return new File(pathStr); } // TODO add more tests diff --git a/jadx-core/src/test/java/jadx/core/dex/info/AccessInfoTest.java b/jadx-core/src/test/java/jadx/core/dex/info/AccessInfoTest.java index 23587c044..f65b92a9c 100644 --- a/jadx-core/src/test/java/jadx/core/dex/info/AccessInfoTest.java +++ b/jadx-core/src/test/java/jadx/core/dex/info/AccessInfoTest.java @@ -2,8 +2,7 @@ package jadx.core.dex.info; import org.junit.jupiter.api.Test; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; import jadx.core.dex.info.AccessInfo.AFType; import static org.hamcrest.MatcherAssert.assertThat; @@ -14,8 +13,8 @@ public class AccessInfoTest { @Test public void changeVisibility() { - AccessInfo accessInfo = new AccessInfo(AccessFlags.ACC_PROTECTED | AccessFlags.ACC_STATIC, AFType.METHOD); - AccessInfo result = accessInfo.changeVisibility(AccessFlags.ACC_PUBLIC); + AccessInfo accessInfo = new AccessInfo(AccessFlags.PROTECTED | AccessFlags.STATIC, AFType.METHOD); + AccessInfo result = accessInfo.changeVisibility(AccessFlags.PUBLIC); assertThat(result.isPublic(), is(true)); assertThat(result.isPrivate(), is(false)); @@ -26,8 +25,8 @@ public class AccessInfoTest { @Test public void changeVisibilityNoOp() { - AccessInfo accessInfo = new AccessInfo(AccessFlags.ACC_PUBLIC, AFType.METHOD); - AccessInfo result = accessInfo.changeVisibility(AccessFlags.ACC_PUBLIC); + AccessInfo accessInfo = new AccessInfo(AccessFlags.PUBLIC, AFType.METHOD); + AccessInfo result = accessInfo.changeVisibility(AccessFlags.PUBLIC); assertSame(accessInfo, result); } } diff --git a/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java b/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java index 035a35892..220f30c61 100644 --- a/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java +++ b/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java @@ -43,7 +43,7 @@ public class TypeCompareTest { public void init() { JadxArgs args = new JadxArgs(); RootNode root = new RootNode(args); - root.load(Collections.emptyList()); + root.loadClasses(Collections.emptyList()); root.initClassPath(); compare = new TypeCompare(root); } diff --git a/jadx-core/src/test/java/jadx/core/utils/files/InputFileTest.java b/jadx-core/src/test/java/jadx/core/utils/files/InputFileTest.java deleted file mode 100644 index f86c5d7bf..000000000 --- a/jadx-core/src/test/java/jadx/core/utils/files/InputFileTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package jadx.core.utils.files; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import jadx.core.utils.exceptions.DecodeException; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.notNullValue; - -public class InputFileTest { - private static final String TEST_SAMPLES_DIR = "test-samples/"; - - @Test - public void testApkWithFakeDex() throws IOException, DecodeException { - File sample = getFileFromSampleDir("app-with-fake-dex.apk"); - - List list = new ArrayList<>(); - InputFile.addFilesFrom(sample, list, false); - assertThat(list, hasSize(1)); - InputFile inputFile = list.get(0); - assertThat(inputFile.getDexFiles(), hasSize(1)); - } - - public static File getFileFromSampleDir(String fileName) { - URL resource = InputFileTest.class.getClassLoader().getResource(TEST_SAMPLES_DIR + fileName); - assertThat(resource, notNullValue()); - String pathStr = resource.getFile(); - return new File(pathStr); - } -} diff --git a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java index 2e4bec5fb..c27a1d6fd 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -104,6 +104,8 @@ public abstract class IntegrationTest extends TestUtils { DebugChecks.checksEnabled = true; } + private JadxDecompiler jadxDecompiler; + @BeforeEach public void init() { this.deleteTmpFiles = true; @@ -124,6 +126,9 @@ public abstract class IntegrationTest extends TestUtils { @AfterEach public void after() { FileUtils.clearTempRootDir(); + if (jadxDecompiler != null) { + jadxDecompiler.close(); + } } public String getTestName() { @@ -146,14 +151,14 @@ public abstract class IntegrationTest extends TestUtils { } public ClassNode getClassNodeFromFile(File file, String clsName) { - JadxDecompiler d = loadFiles(Collections.singletonList(file)); - RootNode root = JadxInternalAccess.getRoot(d); + jadxDecompiler = loadFiles(Collections.singletonList(file)); + RootNode root = JadxInternalAccess.getRoot(jadxDecompiler); - ClassNode cls = root.searchClassByName(clsName); + ClassNode cls = root.resolveClass(clsName); assertThat("Class not found: " + clsName, cls, notNullValue()); assertThat(clsName, is(cls.getClassInfo().getFullName())); - decompileAndCheck(d, Collections.singletonList(cls)); + decompileAndCheck(jadxDecompiler, Collections.singletonList(cls)); return cls; } @@ -169,12 +174,13 @@ public abstract class IntegrationTest extends TestUtils { protected JadxDecompiler loadFiles(List inputFiles) { JadxDecompiler d; + args.setInputFiles(inputFiles); + d = new JadxDecompiler(args); try { - args.setInputFiles(inputFiles); - d = new JadxDecompiler(args); d.load(); } catch (Exception e) { e.printStackTrace(); + d.close(); fail(e.getMessage()); return null; } @@ -487,6 +493,7 @@ public abstract class IntegrationTest extends TestUtils { } protected void setFallback() { + disableCompilation(); this.args.setFallbackMode(true); } diff --git a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java index 4d7ac5bba..55ba0b7db 100644 --- a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java +++ b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java @@ -100,7 +100,7 @@ public abstract class BaseExternalTest extends IntegrationTest { throw new JadxRuntimeException("Class process failed", e); } LOG.info("----------------------------------------------------------------"); - LOG.info("Print class: {}, {}", classNode.getFullName(), classNode.dex()); + LOG.info("Print class: {}, {}", classNode.getFullName(), classNode.getInputPath()); if (mthPattern != null) { printMethods(classNode, mthPattern); } else { diff --git a/jadx-core/src/test/java/jadx/tests/functional/JadxClasspathTest.java b/jadx-core/src/test/java/jadx/tests/functional/JadxClasspathTest.java index 8437c6e3f..310f1a975 100644 --- a/jadx-core/src/test/java/jadx/tests/functional/JadxClasspathTest.java +++ b/jadx-core/src/test/java/jadx/tests/functional/JadxClasspathTest.java @@ -26,7 +26,7 @@ public class JadxClasspathTest { @BeforeEach public void initClsp() { this.root = new RootNode(new JadxArgs()); - this.root.load(Collections.emptyList()); + this.root.loadClasses(Collections.emptyList()); this.root.initClassPath(); this.clsp = root.getClsp(); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestLoopInTry2.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestLoopInTry2.java deleted file mode 100644 index 9942167eb..000000000 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestLoopInTry2.java +++ /dev/null @@ -1,52 +0,0 @@ -package jadx.tests.integration.others; - -import java.io.EOFException; - -import org.junit.jupiter.api.Test; - -import com.android.dex.Code; -import com.android.dx.io.instructions.DecodedInstruction; -import com.android.dx.io.instructions.ShortArrayCodeInput; - -import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.MethodNode; -import jadx.core.utils.exceptions.DecodeException; -import jadx.tests.api.IntegrationTest; - -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.MatcherAssert.assertThat; - -public class TestLoopInTry2 extends IntegrationTest { - - public static class TestCls { - private MethodNode method; - public DecodedInstruction[] insnArr; - - public void test(Code mthCode) throws DecodeException { - short[] encodedInstructions = mthCode.getInstructions(); - int size = encodedInstructions.length; - DecodedInstruction[] decoded = new DecodedInstruction[size]; - ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions); - try { - while (in.hasMore()) { - decoded[in.cursor()] = DecodedInstruction.decode(in); - } - } catch (EOFException e) { - throw new DecodeException(method, "", e); - } - insnArr = decoded; - } - } - - @Test - public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("try {")); - assertThat(code, containsOne("while (in.hasMore()) {")); - assertThat(code, containsOne("decoded[in.cursor()] = DecodedInstruction.decode(")); - assertThat(code, containsOne("} catch (EOFException e) {")); - assertThat(code, containsOne("throw new DecodeException")); - } -} diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinally.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinally.java index a4aa17c32..53b07bea6 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinally.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinally.java @@ -57,6 +57,7 @@ public class TestFinally extends IntegrationTest { ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); + assertThat(code, containsOne("} finally {")); assertThat(code, containsOne("cursor.getString(columnIndex);")); assertThat(code, not(containsOne("String str = true;"))); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally8.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally8.java index 09d014d39..a30f17d46 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally8.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally8.java @@ -49,6 +49,8 @@ public class TestTryCatchFinally8 extends IntegrationTest { String code = cls.getCode().toString(); assertThat(code, containsString("try {")); + assertThat(code, containsString("} catch (IOException e) {")); + assertThat(code, containsString("} finally {")); assertThat(code, containsString("file.delete();")); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchLastInsn.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchLastInsn.java index d604d3b6a..f1b7f441d 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchLastInsn.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchLastInsn.java @@ -2,11 +2,9 @@ package jadx.tests.integration.trycatch; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.SmaliTest; -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestTryCatchLastInsn extends SmaliTest { @@ -16,7 +14,7 @@ public class TestTryCatchLastInsn extends SmaliTest { ? r1 = "result"; // String try { r1 = call(); // Exception - } catch(Exception e) { + } catch (Exception e) { System.out.println(r1); // String r1 = e; } @@ -27,9 +25,9 @@ public class TestTryCatchLastInsn extends SmaliTest { @Test public void test() { - ClassNode cls = getClassNodeFromSmali(); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("return call();")); + assertThat(getClassNodeFromSmali()) + .code() + .containsOne("return call();") + .containsOne("} catch (Exception e) {"); } } diff --git a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java index 86b590858..68f4766ce 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java @@ -8,7 +8,7 @@ import java.util.List; import java.util.concurrent.ThreadPoolExecutor; import java.util.stream.Collectors; -import javax.swing.ProgressMonitor; +import javax.swing.*; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -33,6 +33,7 @@ public class JadxWrapper { } public void openFile(File file) { + close(); this.openFile = file; try { JadxArgs jadxArgs = settings.toJadxArgs(); @@ -42,6 +43,17 @@ public class JadxWrapper { this.decompiler.load(); } catch (Exception e) { LOG.error("Jadx init error", e); + close(); + } + } + + public void close() { + if (decompiler != null) { + try { + decompiler.close(); + } catch (Exception e) { + LOG.error("jadx decompiler close error", e); + } } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 396aa408e..9690d0a55 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -1098,6 +1098,7 @@ public class MainWindow extends JFrame { settings.saveWindowPos(this); settings.setMainWindowExtendedState(getExtendedState()); cancelBackgroundJobs(); + wrapper.close(); dispose(); FileUtils.deleteTempRootDir(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index 06fe2bfbf..5241181c7 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -21,9 +21,7 @@ import jadx.api.JavaClass; import jadx.api.JavaField; import jadx.api.JavaMethod; import jadx.api.JavaNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.RootNode; -import jadx.core.utils.files.InputFile; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JField; import jadx.gui.treemodel.JMethod; @@ -66,12 +64,12 @@ public class RenameDialog extends JDialog { } private Path getDeobfMapPath(RootNode root) { - List dexNodes = root.getDexNodes(); - if (dexNodes.isEmpty()) { + List inputFiles = root.getArgs().getInputFiles(); + if (inputFiles.isEmpty()) { return null; } - InputFile firstInputFile = dexNodes.get(0).getDexFile().getInputFile(); - Path inputFilePath = firstInputFile.getFile().getAbsoluteFile().toPath(); + File firstInputFile = inputFiles.get(0); + Path inputFilePath = firstInputFile.getAbsoluteFile().toPath(); String inputName = inputFilePath.getFileName().toString(); String baseName = inputName.substring(0, inputName.lastIndexOf('.')); diff --git a/jadx-plugins/jadx-dex-input/build.gradle b/jadx-plugins/jadx-dex-input/build.gradle new file mode 100644 index 000000000..f5d10c75f --- /dev/null +++ b/jadx-plugins/jadx-dex-input/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java-library' +} + +dependencies { + api(project(":jadx-plugins:jadx-plugins-api")) + + testImplementation('org.smali:smali:2.4.0') +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexException.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexException.java new file mode 100644 index 000000000..936f4cfc8 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexException.java @@ -0,0 +1,13 @@ +package jadx.plugins.input.dex; + +public class DexException extends RuntimeException { + private static final long serialVersionUID = -5575702801815409269L; + + public DexException(String message, Throwable cause) { + super(message, cause); + } + + public DexException(String message) { + super(message); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java new file mode 100644 index 000000000..925d250b9 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java @@ -0,0 +1,85 @@ +package jadx.plugins.input.dex; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.plugins.input.dex.sections.DexConsts; + +public class DexFileLoader { + private static final Logger LOG = LoggerFactory.getLogger(DexFileLoader.class); + + public static List collectDexFiles(List pathsList) { + return pathsList.stream() + .map((Path path) -> loadDexFromPath(path, 0)) + .flatMap(Collection::stream) + .peek(dr -> LOG.debug("Loading dex: {}", dr)) + .collect(Collectors.toList()); + } + + private static List loadDexFromPath(Path path, int depth) { + try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) { + if (isDex(fileChannel)) { + return Collections.singletonList(new DexReader(path, fileChannel)); + } + if (depth == 0 && isZip(fileChannel)) { + return collectDexFromZip(path, depth); + } + } catch (Exception e) { + LOG.error("File open error: {}", path, e); + } + return Collections.emptyList(); + } + + private static List collectDexFromZip(Path path, int depth) throws IOException { + List result = new ArrayList<>(); + FileSystem zip = FileSystems.newFileSystem(path, (ClassLoader) null); + for (Path rootDir : zip.getRootDirectories()) { + Files.walkFileTree(rootDir, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + // TODO: add zip security checks + result.addAll(loadDexFromPath(file, depth + 1)); + return FileVisitResult.CONTINUE; + } + }); + } + return result; + } + + private static boolean isDex(FileChannel fileChannel) { + return isStartWithBytes(fileChannel, DexConsts.DEX_FILE_MAGIC); + } + + private static boolean isZip(FileChannel fileChannel) { + return isStartWithBytes(fileChannel, DexConsts.ZIP_FILE_MAGIC); + } + + private static boolean isStartWithBytes(FileChannel fileChannel, byte[] startBytes) { + try { + fileChannel.position(0); + ByteBuffer buf = ByteBuffer.allocate(startBytes.length); + fileChannel.read(buf); + return Arrays.equals(startBytes, buf.array()); + } catch (Exception e) { + return false; + } + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java new file mode 100644 index 000000000..049506827 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java @@ -0,0 +1,21 @@ +package jadx.plugins.input.dex; + +import java.nio.file.Path; +import java.util.List; + +import jadx.api.plugins.JadxPluginInfo; +import jadx.api.plugins.input.JadxInputPlugin; +import jadx.api.plugins.input.data.ILoadResult; + +public class DexInputPlugin implements JadxInputPlugin { + + @Override + public JadxPluginInfo getPluginInfo() { + return new JadxPluginInfo("dex-input", "DexInput", "Load .dex and .apk files"); + } + + @Override + public ILoadResult loadFiles(List input) { + return new DexLoadResult(DexFileLoader.collectDexFiles(input)); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java new file mode 100644 index 000000000..967b91737 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java @@ -0,0 +1,35 @@ +package jadx.plugins.input.dex; + +import java.io.IOException; +import java.util.List; +import java.util.function.Consumer; + +import jadx.api.plugins.input.data.IClassData; +import jadx.api.plugins.input.data.ILoadResult; +import jadx.api.plugins.input.data.IResourceData; + +public class DexLoadResult implements ILoadResult { + private final List dexReaders; + + public DexLoadResult(List dexReaders) { + this.dexReaders = dexReaders; + } + + @Override + public void visitClasses(Consumer consumer) { + for (DexReader dexReader : dexReaders) { + dexReader.visitClasses(consumer); + } + } + + @Override + public void visitResources(Consumer consumer) { + } + + @Override + public void close() throws IOException { + for (DexReader dexReader : dexReaders) { + dexReader.close(); + } + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java new file mode 100644 index 000000000..4ae196da0 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java @@ -0,0 +1,96 @@ +package jadx.plugins.input.dex; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.util.function.Consumer; + +import jadx.api.plugins.input.data.IClassData; +import jadx.plugins.input.dex.sections.DexClassData; +import jadx.plugins.input.dex.sections.DexHeader; +import jadx.plugins.input.dex.sections.SectionReader; +import jadx.plugins.input.dex.sections.annotations.AnnotationsParser; + +public class DexReader implements Closeable { + + private final Path path; + private final FileChannel fileChannel; + private final ByteBuffer buf; + private final DexHeader header; + + public DexReader(Path path, FileChannel fileChannel) throws IOException { + this.path = path; + this.fileChannel = fileChannel; + this.buf = loadIntoByteBuffer(fileChannel); + this.header = new DexHeader(new SectionReader(this, 0)); + } + + private static ByteBuffer loadIntoByteBuffer(FileChannel fileChannel) throws IOException { + long size = fileChannel.size(); + if (size > Integer.MAX_VALUE) { + throw new IOException("File too big"); + } + int readSize = (int) size; + ByteBuffer buf = ByteBuffer.allocate(readSize); + fileChannel.position(0); + int read = fileChannel.read(buf); + if (read != readSize) { + throw new IOException("Failed to read whole file into buffer. Read: " + read + ", expected: " + readSize); + } + return buf; + } + + public String getDexVersion() { + return this.header.getVersion(); + } + + public void visitClasses(Consumer consumer) { + int count = header.getClassDefsSize(); + if (count == 0) { + return; + } + int classDefsOff = header.getClassDefsOff(); + SectionReader in = new SectionReader(this, classDefsOff); + AnnotationsParser annotationsParser = new AnnotationsParser(in.copy(), in.copy()); + DexClassData classData = new DexClassData(in, annotationsParser); + for (int i = 0; i < count; i++) { + consumer.accept(classData); + in.shiftOffset(DexClassData.SIZE); + } + } + + public ByteBuffer getBuf() { + return buf; + } + + public DexHeader getHeader() { + return header; + } + + public Path getPath() { + return path; + } + + public String getFullPath() { + StringBuilder sb = new StringBuilder(); + FileSystem fileSystem = path.getFileSystem(); + if (fileSystem.getClass().getName().contains("Zip")) { + sb.append(fileSystem.toString()).append(':'); + } + sb.append(path.toAbsolutePath()); + return sb.toString(); + } + + @Override + public void close() throws IOException { + this.fileChannel.close(); + } + + @Override + public String toString() { + return getFullPath(); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnData.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnData.java new file mode 100644 index 000000000..36fcc71bf --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnData.java @@ -0,0 +1,208 @@ +package jadx.plugins.input.dex.insns; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.IMethodData; +import jadx.api.plugins.input.insns.InsnData; +import jadx.api.plugins.input.insns.InsnIndexType; +import jadx.api.plugins.input.insns.Opcode; +import jadx.api.plugins.input.insns.custom.ICustomPayload; +import jadx.plugins.input.dex.sections.DexCodeReader; +import jadx.plugins.input.dex.sections.SectionReader; + +public class DexInsnData implements InsnData { + private final DexCodeReader codeData; + private final SectionReader externalReader; + + private DexInsnInfo insnInfo; + private boolean decoded; + private int opcodeUnit; + private int length; + + private int offset; + private int[] argsReg = new int[5]; + private int regsCount; + private long literal; + private int target; + private int index; + @Nullable + private ICustomPayload payload; + + public DexInsnData(DexCodeReader codeData, SectionReader externalReader) { + this.codeData = codeData; + this.externalReader = externalReader; + } + + @Override + public void decode() { + if (insnInfo != null && !decoded) { + codeData.decode(this); + } + } + + @Override + public int getOffset() { + return offset; + } + + @Override + public Opcode getOpcode() { + DexInsnInfo info = this.insnInfo; + if (info == null) { + return Opcode.UNKNOWN; + } + return info.getApiOpcode(); + } + + @Override + public int getRawOpcodeUnit() { + return opcodeUnit; + } + + @Override + public int getRegsCount() { + return regsCount; + } + + @Override + public int getReg(int argNum) { + return argsReg[argNum]; + } + + @Override + public long getLiteral() { + return literal; + } + + @Override + public int getTarget() { + return target; + } + + @Override + public int getIndex() { + return index; + } + + @Override + public InsnIndexType getIndexType() { + return insnInfo.getIndexType(); + } + + @Override + public String getIndexAsString() { + return externalReader.getString(index); + } + + @Override + public String getIndexAsType() { + return externalReader.getType(index); + } + + @Override + public IFieldData getIndexAsField() { + return externalReader.getFieldData(index); + } + + @Override + public IMethodData getIndexAsMethod() { + return externalReader.getMethodData(index); + } + + @Nullable + @Override + public ICustomPayload getPayload() { + return payload; + } + + public int[] getArgsReg() { + return argsReg; + } + + public void setArgsReg(int[] argsReg) { + this.argsReg = argsReg; + } + + public void setRegsCount(int regsCount) { + this.regsCount = regsCount; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public void setLiteral(long literal) { + this.literal = literal; + } + + public void setTarget(int target) { + this.target = target; + } + + public void setIndex(int index) { + this.index = index; + } + + public boolean isDecoded() { + return decoded; + } + + public void setDecoded(boolean decoded) { + this.decoded = decoded; + } + + public void setOffset(int offset) { + this.offset = offset; + } + + public DexInsnInfo getInsnInfo() { + return insnInfo; + } + + public void setInsnInfo(DexInsnInfo insnInfo) { + this.insnInfo = insnInfo; + } + + public DexCodeReader getCodeData() { + return codeData; + } + + public int getOpcodeUnit() { + return opcodeUnit; + } + + public void setOpcodeUnit(int opcodeUnit) { + this.opcodeUnit = opcodeUnit; + } + + public void setPayload(ICustomPayload payload) { + this.payload = payload; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(String.format("0x%04X", offset)); + sb.append(": ").append(getOpcode()); + if (insnInfo == null) { + sb.append(String.format("(0x%04X)", opcodeUnit)); + } else { + int regsCount = getRegsCount(); + if (isDecoded()) { + sb.append(' '); + for (int i = 0; i < regsCount; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append("r").append(argsReg[i]); + } + } + } + return sb.toString(); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnFormat.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnFormat.java new file mode 100644 index 000000000..07618abf4 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnFormat.java @@ -0,0 +1,451 @@ +package jadx.plugins.input.dex.insns; + +import jadx.plugins.input.dex.DexException; +import jadx.plugins.input.dex.insns.payloads.DexArrayPayload; +import jadx.plugins.input.dex.insns.payloads.DexSwitchPayload; +import jadx.plugins.input.dex.sections.SectionReader; + +public abstract class DexInsnFormat { + public static final DexInsnFormat FORMAT_10X = new DexInsnFormat(1, 0) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + // no op + } + }; + + public static final DexInsnFormat FORMAT_12X = new DexInsnFormat(1, 2) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = nibble2(opcodeUnit); + regs[1] = nibble3(opcodeUnit); + } + }; + + public static final DexInsnFormat FORMAT_11N = new DexInsnFormat(1, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = nibble2(opcodeUnit); + insn.setLiteral(signedNibble3(opcodeUnit)); + } + }; + + public static final DexInsnFormat FORMAT_11X = new DexInsnFormat(1, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + } + }; + + public static final DexInsnFormat FORMAT_10T = new DexInsnFormat(1, 0) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + insn.setTarget(insn.getOffset() + signedByte1(opcodeUnit)); + } + }; + + public static final DexInsnFormat FORMAT_20T = new DexInsnFormat(2, 0) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + insn.setTarget(insn.getOffset() + in.readShort()); + } + }; + + public static final DexInsnFormat FORMAT_20BC = new DexInsnFormat(2, 0) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + insn.setLiteral(byte1(opcodeUnit)); + insn.setIndex(in.readUShort()); + } + }; + + public static final DexInsnFormat FORMAT_22X = new DexInsnFormat(2, 2) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + regs[1] = in.readUShort(); + } + }; + + public static final DexInsnFormat FORMAT_21T = new DexInsnFormat(2, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = signedByte1(opcodeUnit); + insn.setTarget(insn.getOffset() + in.readShort()); + } + }; + + public static final DexInsnFormat FORMAT_21S = new DexInsnFormat(2, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + insn.setLiteral(in.readShort()); + } + }; + + public static final DexInsnFormat FORMAT_21H = new DexInsnFormat(2, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + + long literal = in.readShort(); + literal <<= (byte0(opcodeUnit) == DexOpcodes.CONST_HIGH16) ? 16 : 48; + insn.setLiteral(literal); + } + }; + + public static final DexInsnFormat FORMAT_21C = new DexInsnFormat(2, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + insn.setIndex(in.readUShort()); + } + }; + + public static final DexInsnFormat FORMAT_23X = new DexInsnFormat(2, 3) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + int next = in.readUShort(); + regs[1] = byte0(next); + regs[2] = byte1(next); + } + }; + + public static final DexInsnFormat FORMAT_22B = new DexInsnFormat(2, 2) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + int next = in.readUShort(); + regs[1] = byte0(next); + insn.setLiteral(signedByte1(next)); + } + }; + + public static final DexInsnFormat FORMAT_22T = new DexInsnFormat(2, 2) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = nibble2(opcodeUnit); + regs[1] = nibble3(opcodeUnit); + insn.setTarget(insn.getOffset() + in.readShort()); + } + }; + + public static final DexInsnFormat FORMAT_22S = new DexInsnFormat(2, 2) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = nibble2(opcodeUnit); + regs[1] = nibble3(opcodeUnit); + insn.setLiteral(in.readShort()); + } + }; + + public static final DexInsnFormat FORMAT_22C = new DexInsnFormat(2, 2) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = nibble2(opcodeUnit); + regs[1] = nibble3(opcodeUnit); + insn.setIndex(in.readUShort()); + } + }; + + public static final DexInsnFormat FORMAT_22CS = FORMAT_22C; + + public static final DexInsnFormat FORMAT_30T = new DexInsnFormat(3, 0) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + insn.setTarget(insn.getOffset() + in.readInt()); + } + }; + + public static final DexInsnFormat FORMAT_32X = new DexInsnFormat(3, 2) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = in.readUShort(); + regs[1] = in.readUShort(); + } + }; + + public static final DexInsnFormat FORMAT_31I = new DexInsnFormat(3, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + insn.setLiteral(in.readInt()); + } + }; + + public static final DexInsnFormat FORMAT_31T = new DexInsnFormat(3, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + insn.setTarget(insn.getOffset() + in.readInt()); + } + }; + + public static final DexInsnFormat FORMAT_31C = new DexInsnFormat(3, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + insn.setIndex(in.readInt()); + } + }; + + public static final DexInsnFormat FORMAT_35C = new DexInsnFormat(3, -1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + readRegsList(insn, opcodeUnit, in); + } + }; + + public static final DexInsnFormat FORMAT_35MS = FORMAT_35C; + public static final DexInsnFormat FORMAT_35MI = FORMAT_35C; + + public static final DexInsnFormat FORMAT_3RC = new DexInsnFormat(3, -1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + readRegsRange(insn, opcodeUnit, in); + } + }; + + public static final DexInsnFormat FORMAT_3RMS = FORMAT_3RC; + public static final DexInsnFormat FORMAT_3RMI = FORMAT_3RC; + + public static final DexInsnFormat FORMAT_45CC = new DexInsnFormat(4, -1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + readRegsList(insn, opcodeUnit, in); + insn.setTarget(in.readUShort()); + } + }; + + public static final DexInsnFormat FORMAT_4RCC = new DexInsnFormat(4, -1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + readRegsRange(insn, opcodeUnit, in); + insn.setTarget(in.readUShort()); + } + }; + + public static final DexInsnFormat FORMAT_51I = new DexInsnFormat(5, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + insn.setLiteral(in.readLong()); + } + }; + + public static final DexInsnFormat FORMAT_PACKED_SWITCH_PAYLOAD = new DexInsnFormat(-1, -1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int size = in.readUShort(); + int firstKey = in.readInt(); + int[] keys = new int[size]; + int[] targets = new int[size]; + for (int i = 0; i < size; i++) { + targets[i] = in.readInt(); + keys[i] = firstKey + i; + } + insn.setPayload(new DexSwitchPayload(size, keys, targets)); + insn.setLength(size * 2 + 4); + } + + @Override + public void skip(DexInsnData insn, SectionReader in) { + int size = in.readUShort(); + in.skip(4 + size * 4); + insn.setLength(size * 2 + 4); + } + }; + + public static final DexInsnFormat FORMAT_SPARSE_SWITCH_PAYLOAD = new DexInsnFormat(-1, -1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int size = in.readUShort(); + int[] keys = new int[size]; + for (int i = 0; i < size; i++) { + keys[i] = in.readInt(); + } + int[] targets = new int[size]; + for (int i = 0; i < size; i++) { + targets[i] = in.readInt(); + } + insn.setPayload(new DexSwitchPayload(size, keys, targets)); + insn.setLength(size * 4 + 2); + } + + @Override + public void skip(DexInsnData insn, SectionReader in) { + int size = in.readUShort(); + in.skip(4 + size * 4 * 2); + insn.setLength(size * 4 + 2); + } + }; + + public static final DexInsnFormat FORMAT_FILL_ARRAY_DATA_PAYLOAD = new DexInsnFormat(-1, -1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int elemSize = in.readUShort(); + int size = in.readInt(); + Object data; + switch (elemSize) { + case 1: { + data = in.readByteArray(size); + if (size % 2 != 0) { + in.readUByte(); + } + break; + } + case 2: { + short[] array = new short[size]; + for (int i = 0; i < size; i++) { + array[i] = (short) in.readShort(); + } + data = array; + break; + } + case 4: { + int[] array = new int[size]; + for (int i = 0; i < size; i++) { + array[i] = in.readInt(); + } + data = array; + break; + } + case 8: { + long[] array = new long[size]; + for (int i = 0; i < size; i++) { + array[i] = in.readLong(); + } + data = array; + break; + } + default: + throw new DexException("Unexpected element size in FILL_ARRAY_DATA_PAYLOAD: " + elemSize); + } + insn.setLength((size * elemSize + 1) / 2 + 4); + insn.setPayload(new DexArrayPayload(size, elemSize, data)); + } + + @Override + public void skip(DexInsnData insn, SectionReader in) { + int elemSize = in.readUShort(); + int size = in.readInt(); + if (size == 1) { + in.skip(size + size % 2); + } else { + in.skip(size * elemSize); + } + insn.setLength((size * elemSize + 1) / 2 + 4); + } + }; + + protected void readRegsList(DexInsnData insn, int opcodeUnit, SectionReader in) { + int regsCount1 = nibble3(opcodeUnit); + int index = in.readUShort(); + int rs = in.readUShort(); + + int[] regs = insn.getArgsReg(); + regs[0] = nibble0(rs); + regs[1] = nibble1(rs); + regs[2] = nibble2(rs); + regs[3] = nibble3(rs); + regs[4] = nibble2(opcodeUnit); + + insn.setRegsCount(regsCount1); + insn.setIndex(index); + } + + protected void readRegsRange(DexInsnData insn, int opcodeUnit, SectionReader in) { + int regsCount = byte1(opcodeUnit); + int index = in.readUShort(); + int startReg = in.readUShort(); + + int[] regs = insn.getArgsReg(); + if (regs.length < regsCount) { + regs = new int[regsCount]; + insn.setArgsReg(regs); + } + int regNum = startReg; + for (int i = 0; i < regsCount; i++) { + regs[i] = regNum; + regNum++; + } + insn.setRegsCount(regsCount); + insn.setIndex(index); + } + + private final int length; + private final int regsCount; + + protected DexInsnFormat(int length, int regsCount) { + this.length = length; + this.regsCount = regsCount; + } + + public abstract void decode(DexInsnData insn, int opcodeUnit, SectionReader in); + + public void skip(DexInsnData insn, SectionReader in) { + int len = this.length; + if (len == 1) { + return; + } + in.skip((len - 1) * 2); + } + + public int getLength() { + return length; + } + + public int getRegsCount() { + return regsCount; + } + + private static int byte0(int value) { + return value & 0xFF; + } + + private static int byte1(int value) { + return (value >> 8) & 0xFF; + } + + private static int signedByte1(int value) { + return (byte) (value >> 8); + } + + private static int nibble0(int value) { + return value & 0xF; + } + + private static int nibble1(int value) { + return (value >> 4) & 0xF; + } + + private static int nibble2(int value) { + return (value >> 8) & 0xF; + } + + private static int nibble3(int value) { + return (value >> 12) & 0xF; + } + + private static int signedNibble3(int value) { + return (((value >> 12) & 0xF) << 28) >> 28; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnInfo.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnInfo.java new file mode 100644 index 000000000..75dbee4ed --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnInfo.java @@ -0,0 +1,350 @@ +package jadx.plugins.input.dex.insns; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.insns.InsnIndexType; +import jadx.api.plugins.input.insns.Opcode; + +public class DexInsnInfo { + + private static final DexInsnInfo[] INSN_INFO; + private static final Map PAYLOAD_INFO; + + static { + DexInsnInfo[] arr = new DexInsnInfo[0x100]; + INSN_INFO = arr; + register(arr, DexOpcodes.NOP, Opcode.NOP, DexInsnFormat.FORMAT_10X); + + register(arr, DexOpcodes.MOVE, Opcode.MOVE, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.MOVE_FROM16, Opcode.MOVE, DexInsnFormat.FORMAT_22X); + register(arr, DexOpcodes.MOVE_16, Opcode.MOVE, DexInsnFormat.FORMAT_32X); + + register(arr, DexOpcodes.MOVE_WIDE, Opcode.MOVE_WIDE, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.MOVE_WIDE_FROM16, Opcode.MOVE_WIDE, DexInsnFormat.FORMAT_22X); + register(arr, DexOpcodes.MOVE_WIDE_16, Opcode.MOVE_WIDE, DexInsnFormat.FORMAT_32X); + + register(arr, DexOpcodes.MOVE_OBJECT, Opcode.MOVE_OBJECT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.MOVE_OBJECT_FROM16, Opcode.MOVE_OBJECT, DexInsnFormat.FORMAT_22X); + register(arr, DexOpcodes.MOVE_OBJECT_16, Opcode.MOVE_OBJECT, DexInsnFormat.FORMAT_32X); + + register(arr, DexOpcodes.MOVE_RESULT, Opcode.MOVE_RESULT, DexInsnFormat.FORMAT_11X); + register(arr, DexOpcodes.MOVE_RESULT_WIDE, Opcode.MOVE_RESULT, DexInsnFormat.FORMAT_11X); + register(arr, DexOpcodes.MOVE_RESULT_OBJECT, Opcode.MOVE_RESULT, DexInsnFormat.FORMAT_11X); + + register(arr, DexOpcodes.MOVE_EXCEPTION, Opcode.MOVE_EXCEPTION, DexInsnFormat.FORMAT_11X); + + register(arr, DexOpcodes.RETURN_VOID, Opcode.RETURN_VOID, DexInsnFormat.FORMAT_10X); + register(arr, DexOpcodes.RETURN, Opcode.RETURN, DexInsnFormat.FORMAT_11X); + register(arr, DexOpcodes.RETURN_WIDE, Opcode.RETURN, DexInsnFormat.FORMAT_11X); + register(arr, DexOpcodes.RETURN_OBJECT, Opcode.RETURN, DexInsnFormat.FORMAT_11X); + + register(arr, DexOpcodes.CONST_4, Opcode.CONST, DexInsnFormat.FORMAT_11N); + register(arr, DexOpcodes.CONST_16, Opcode.CONST, DexInsnFormat.FORMAT_21S); + register(arr, DexOpcodes.CONST, Opcode.CONST, DexInsnFormat.FORMAT_31I); + register(arr, DexOpcodes.CONST_HIGH16, Opcode.CONST, DexInsnFormat.FORMAT_21H); + + register(arr, DexOpcodes.CONST_WIDE_16, Opcode.CONST_WIDE, DexInsnFormat.FORMAT_21S); + register(arr, DexOpcodes.CONST_WIDE_32, Opcode.CONST_WIDE, DexInsnFormat.FORMAT_31I); + register(arr, DexOpcodes.CONST_WIDE, Opcode.CONST_WIDE, DexInsnFormat.FORMAT_51I); + register(arr, DexOpcodes.CONST_WIDE_HIGH16, Opcode.CONST_WIDE, DexInsnFormat.FORMAT_21H); + + register(arr, DexOpcodes.CONST_STRING, Opcode.CONST_STRING, DexInsnFormat.FORMAT_21C, InsnIndexType.STRING_REF); + register(arr, DexOpcodes.CONST_STRING_JUMBO, Opcode.CONST_STRING, DexInsnFormat.FORMAT_31C, InsnIndexType.STRING_REF); + + register(arr, DexOpcodes.CONST_CLASS, Opcode.CONST_CLASS, DexInsnFormat.FORMAT_21C, InsnIndexType.TYPE_REF); + + register(arr, DexOpcodes.MONITOR_ENTER, Opcode.MONITOR_ENTER, DexInsnFormat.FORMAT_11X); + register(arr, DexOpcodes.MONITOR_EXIT, Opcode.MONITOR_EXIT, DexInsnFormat.FORMAT_11X); + + register(arr, DexOpcodes.CHECK_CAST, Opcode.CHECK_CAST, DexInsnFormat.FORMAT_21C, InsnIndexType.TYPE_REF); + register(arr, DexOpcodes.INSTANCE_OF, Opcode.INSTANCE_OF, DexInsnFormat.FORMAT_22C, InsnIndexType.TYPE_REF); + register(arr, DexOpcodes.ARRAY_LENGTH, Opcode.ARRAY_LENGTH, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.NEW_INSTANCE, Opcode.NEW_INSTANCE, DexInsnFormat.FORMAT_21C, InsnIndexType.TYPE_REF); + register(arr, DexOpcodes.NEW_ARRAY, Opcode.NEW_ARRAY, DexInsnFormat.FORMAT_22C, InsnIndexType.TYPE_REF); + + register(arr, DexOpcodes.FILLED_NEW_ARRAY, Opcode.FILLED_NEW_ARRAY, DexInsnFormat.FORMAT_35C, InsnIndexType.TYPE_REF); + register(arr, DexOpcodes.FILLED_NEW_ARRAY_RANGE, Opcode.FILLED_NEW_ARRAY_RANGE, DexInsnFormat.FORMAT_3RC, InsnIndexType.TYPE_REF); + register(arr, DexOpcodes.FILL_ARRAY_DATA, Opcode.FILL_ARRAY_DATA, DexInsnFormat.FORMAT_31T); + + register(arr, DexOpcodes.THROW, Opcode.THROW, DexInsnFormat.FORMAT_11X); + + register(arr, DexOpcodes.GOTO, Opcode.GOTO, DexInsnFormat.FORMAT_10T); + register(arr, DexOpcodes.GOTO_16, Opcode.GOTO, DexInsnFormat.FORMAT_20T); + register(arr, DexOpcodes.GOTO_32, Opcode.GOTO, DexInsnFormat.FORMAT_30T); + + register(arr, DexOpcodes.PACKED_SWITCH, Opcode.PACKED_SWITCH, DexInsnFormat.FORMAT_31T); + register(arr, DexOpcodes.SPARSE_SWITCH, Opcode.SPARSE_SWITCH, DexInsnFormat.FORMAT_31T); + + register(arr, DexOpcodes.CMPL_FLOAT, Opcode.CMPL_FLOAT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.CMPG_FLOAT, Opcode.CMPG_FLOAT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.CMPL_DOUBLE, Opcode.CMPL_DOUBLE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.CMPG_DOUBLE, Opcode.CMPG_DOUBLE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.CMP_LONG, Opcode.CMP_LONG, DexInsnFormat.FORMAT_23X); + + register(arr, DexOpcodes.IF_EQ, Opcode.IF_EQ, DexInsnFormat.FORMAT_22T); + register(arr, DexOpcodes.IF_NE, Opcode.IF_NE, DexInsnFormat.FORMAT_22T); + register(arr, DexOpcodes.IF_LT, Opcode.IF_LT, DexInsnFormat.FORMAT_22T); + register(arr, DexOpcodes.IF_GE, Opcode.IF_GE, DexInsnFormat.FORMAT_22T); + register(arr, DexOpcodes.IF_GT, Opcode.IF_GT, DexInsnFormat.FORMAT_22T); + register(arr, DexOpcodes.IF_LE, Opcode.IF_LE, DexInsnFormat.FORMAT_22T); + + register(arr, DexOpcodes.IF_EQZ, Opcode.IF_EQZ, DexInsnFormat.FORMAT_21T); + register(arr, DexOpcodes.IF_NEZ, Opcode.IF_NEZ, DexInsnFormat.FORMAT_21T); + register(arr, DexOpcodes.IF_LTZ, Opcode.IF_LTZ, DexInsnFormat.FORMAT_21T); + register(arr, DexOpcodes.IF_GEZ, Opcode.IF_GEZ, DexInsnFormat.FORMAT_21T); + register(arr, DexOpcodes.IF_GTZ, Opcode.IF_GTZ, DexInsnFormat.FORMAT_21T); + register(arr, DexOpcodes.IF_LEZ, Opcode.IF_LEZ, DexInsnFormat.FORMAT_21T); + + register(arr, DexOpcodes.AGET, Opcode.AGET, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AGET_WIDE, Opcode.AGET_WIDE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AGET_OBJECT, Opcode.AGET_OBJECT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AGET_BOOLEAN, Opcode.AGET_BOOLEAN, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AGET_BYTE, Opcode.AGET_BYTE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AGET_CHAR, Opcode.AGET_CHAR, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AGET_SHORT, Opcode.AGET_SHORT, DexInsnFormat.FORMAT_23X); + + register(arr, DexOpcodes.APUT, Opcode.APUT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.APUT_WIDE, Opcode.APUT_WIDE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.APUT_OBJECT, Opcode.APUT_OBJECT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.APUT_BOOLEAN, Opcode.APUT_BOOLEAN, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.APUT_BYTE, Opcode.APUT_BYTE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.APUT_CHAR, Opcode.APUT_CHAR, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.APUT_SHORT, Opcode.APUT_SHORT, DexInsnFormat.FORMAT_23X); + + register(arr, DexOpcodes.IGET, Opcode.IGET, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IGET_WIDE, Opcode.IGET, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IGET_OBJECT, Opcode.IGET, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IGET_BOOLEAN, Opcode.IGET, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IGET_BYTE, Opcode.IGET, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IGET_CHAR, Opcode.IGET, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IGET_SHORT, Opcode.IGET, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + + register(arr, DexOpcodes.IPUT, Opcode.IPUT, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IPUT_WIDE, Opcode.IPUT, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IPUT_OBJECT, Opcode.IPUT, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IPUT_BOOLEAN, Opcode.IPUT, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IPUT_BYTE, Opcode.IPUT, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IPUT_CHAR, Opcode.IPUT, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IPUT_SHORT, Opcode.IPUT, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + + register(arr, DexOpcodes.SGET, Opcode.SGET, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SGET_WIDE, Opcode.SGET, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SGET_OBJECT, Opcode.SGET, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SGET_BOOLEAN, Opcode.SGET, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SGET_BYTE, Opcode.SGET, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SGET_CHAR, Opcode.SGET, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SGET_SHORT, Opcode.SGET, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + + register(arr, DexOpcodes.SPUT, Opcode.SPUT, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SPUT_WIDE, Opcode.SPUT, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SPUT_OBJECT, Opcode.SPUT, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SPUT_BOOLEAN, Opcode.SPUT, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SPUT_BYTE, Opcode.SPUT, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SPUT_CHAR, Opcode.SPUT, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SPUT_SHORT, Opcode.SPUT, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + + register(arr, DexOpcodes.INVOKE_VIRTUAL, Opcode.INVOKE_VIRTUAL, DexInsnFormat.FORMAT_35C, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_SUPER, Opcode.INVOKE_SUPER, DexInsnFormat.FORMAT_35C, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_DIRECT, Opcode.INVOKE_DIRECT, DexInsnFormat.FORMAT_35C, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_STATIC, Opcode.INVOKE_STATIC, DexInsnFormat.FORMAT_35C, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_INTERFACE, Opcode.INVOKE_INTERFACE, DexInsnFormat.FORMAT_35C, InsnIndexType.METHOD_REF); + + register(arr, DexOpcodes.INVOKE_VIRTUAL_RANGE, Opcode.INVOKE_VIRTUAL_RANGE, DexInsnFormat.FORMAT_3RC, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_SUPER_RANGE, Opcode.INVOKE_SUPER_RANGE, DexInsnFormat.FORMAT_3RC, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_DIRECT_RANGE, Opcode.INVOKE_DIRECT_RANGE, DexInsnFormat.FORMAT_3RC, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_STATIC_RANGE, Opcode.INVOKE_STATIC_RANGE, DexInsnFormat.FORMAT_3RC, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_INTERFACE_RANGE, Opcode.INVOKE_INTERFACE_RANGE, DexInsnFormat.FORMAT_3RC, InsnIndexType.METHOD_REF); + + register(arr, DexOpcodes.NEG_INT, Opcode.NEG_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.NOT_INT, Opcode.NOT_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.NEG_LONG, Opcode.NEG_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.NOT_LONG, Opcode.NOT_LONG, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.NEG_FLOAT, Opcode.NEG_FLOAT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.NEG_DOUBLE, Opcode.NEG_DOUBLE, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.INT_TO_LONG, Opcode.INT_TO_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.INT_TO_FLOAT, Opcode.INT_TO_FLOAT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.INT_TO_DOUBLE, Opcode.INT_TO_DOUBLE, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.LONG_TO_INT, Opcode.LONG_TO_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.LONG_TO_FLOAT, Opcode.LONG_TO_FLOAT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.LONG_TO_DOUBLE, Opcode.LONG_TO_DOUBLE, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.FLOAT_TO_INT, Opcode.FLOAT_TO_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.FLOAT_TO_LONG, Opcode.FLOAT_TO_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.FLOAT_TO_DOUBLE, Opcode.FLOAT_TO_DOUBLE, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.DOUBLE_TO_INT, Opcode.DOUBLE_TO_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.DOUBLE_TO_LONG, Opcode.DOUBLE_TO_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.DOUBLE_TO_FLOAT, Opcode.DOUBLE_TO_FLOAT, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.INT_TO_BYTE, Opcode.INT_TO_BYTE, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.INT_TO_CHAR, Opcode.INT_TO_CHAR, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.INT_TO_SHORT, Opcode.INT_TO_SHORT, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.ADD_INT, Opcode.ADD_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SUB_INT, Opcode.SUB_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.MUL_INT, Opcode.MUL_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.DIV_INT, Opcode.DIV_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.REM_INT, Opcode.REM_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AND_INT, Opcode.AND_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.OR_INT, Opcode.OR_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.XOR_INT, Opcode.XOR_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SHL_INT, Opcode.SHL_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SHR_INT, Opcode.SHR_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.USHR_INT, Opcode.USHR_INT, DexInsnFormat.FORMAT_23X); + + register(arr, DexOpcodes.ADD_LONG, Opcode.ADD_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SUB_LONG, Opcode.SUB_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.MUL_LONG, Opcode.MUL_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.DIV_LONG, Opcode.DIV_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.REM_LONG, Opcode.REM_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AND_LONG, Opcode.AND_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.OR_LONG, Opcode.OR_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.XOR_LONG, Opcode.XOR_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SHL_LONG, Opcode.SHL_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SHR_LONG, Opcode.SHR_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.USHR_LONG, Opcode.USHR_LONG, DexInsnFormat.FORMAT_23X); + + register(arr, DexOpcodes.ADD_FLOAT, Opcode.ADD_FLOAT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SUB_FLOAT, Opcode.SUB_FLOAT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.MUL_FLOAT, Opcode.MUL_FLOAT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.DIV_FLOAT, Opcode.DIV_FLOAT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.REM_FLOAT, Opcode.REM_FLOAT, DexInsnFormat.FORMAT_23X); + + register(arr, DexOpcodes.ADD_DOUBLE, Opcode.ADD_DOUBLE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SUB_DOUBLE, Opcode.SUB_DOUBLE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.MUL_DOUBLE, Opcode.MUL_DOUBLE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.DIV_DOUBLE, Opcode.DIV_DOUBLE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.REM_DOUBLE, Opcode.REM_DOUBLE, DexInsnFormat.FORMAT_23X); + + register(arr, DexOpcodes.ADD_INT_2ADDR, Opcode.ADD_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SUB_INT_2ADDR, Opcode.SUB_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.MUL_INT_2ADDR, Opcode.MUL_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.DIV_INT_2ADDR, Opcode.DIV_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.REM_INT_2ADDR, Opcode.REM_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.AND_INT_2ADDR, Opcode.AND_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.OR_INT_2ADDR, Opcode.OR_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.XOR_INT_2ADDR, Opcode.XOR_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SHL_INT_2ADDR, Opcode.SHL_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SHR_INT_2ADDR, Opcode.SHR_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.USHR_INT_2ADDR, Opcode.USHR_INT, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.ADD_LONG_2ADDR, Opcode.ADD_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SUB_LONG_2ADDR, Opcode.SUB_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.MUL_LONG_2ADDR, Opcode.MUL_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.DIV_LONG_2ADDR, Opcode.DIV_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.REM_LONG_2ADDR, Opcode.REM_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.AND_LONG_2ADDR, Opcode.AND_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.OR_LONG_2ADDR, Opcode.OR_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.XOR_LONG_2ADDR, Opcode.XOR_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SHL_LONG_2ADDR, Opcode.SHL_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SHR_LONG_2ADDR, Opcode.SHR_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.USHR_LONG_2ADDR, Opcode.USHR_LONG, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.ADD_FLOAT_2ADDR, Opcode.ADD_FLOAT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SUB_FLOAT_2ADDR, Opcode.SUB_FLOAT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.MUL_FLOAT_2ADDR, Opcode.MUL_FLOAT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.DIV_FLOAT_2ADDR, Opcode.DIV_FLOAT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.REM_FLOAT_2ADDR, Opcode.REM_FLOAT, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.ADD_DOUBLE_2ADDR, Opcode.ADD_DOUBLE, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SUB_DOUBLE_2ADDR, Opcode.SUB_DOUBLE, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.MUL_DOUBLE_2ADDR, Opcode.MUL_DOUBLE, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.DIV_DOUBLE_2ADDR, Opcode.DIV_DOUBLE, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.REM_DOUBLE_2ADDR, Opcode.REM_DOUBLE, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.ADD_INT_LIT16, Opcode.ADD_INT_LIT, DexInsnFormat.FORMAT_22S); + register(arr, DexOpcodes.RSUB_INT, Opcode.RSUB_INT, DexInsnFormat.FORMAT_22S); + register(arr, DexOpcodes.MUL_INT_LIT16, Opcode.MUL_INT_LIT, DexInsnFormat.FORMAT_22S); + register(arr, DexOpcodes.DIV_INT_LIT16, Opcode.DIV_INT_LIT, DexInsnFormat.FORMAT_22S); + register(arr, DexOpcodes.REM_INT_LIT16, Opcode.REM_INT_LIT, DexInsnFormat.FORMAT_22S); + register(arr, DexOpcodes.AND_INT_LIT16, Opcode.AND_INT_LIT, DexInsnFormat.FORMAT_22S); + register(arr, DexOpcodes.OR_INT_LIT16, Opcode.OR_INT_LIT, DexInsnFormat.FORMAT_22S); + register(arr, DexOpcodes.XOR_INT_LIT16, Opcode.XOR_INT_LIT, DexInsnFormat.FORMAT_22S); + + register(arr, DexOpcodes.ADD_INT_LIT8, Opcode.ADD_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.RSUB_INT_LIT8, Opcode.RSUB_INT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.MUL_INT_LIT8, Opcode.MUL_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.DIV_INT_LIT8, Opcode.DIV_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.REM_INT_LIT8, Opcode.REM_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.AND_INT_LIT8, Opcode.AND_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.OR_INT_LIT8, Opcode.OR_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.XOR_INT_LIT8, Opcode.XOR_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.SHL_INT_LIT8, Opcode.SHL_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.SHR_INT_LIT8, Opcode.SHR_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.USHR_INT_LIT8, Opcode.USHR_INT_LIT, DexInsnFormat.FORMAT_22B); + + register(arr, DexOpcodes.INVOKE_POLYMORPHIC, Opcode.INVOKE_POLYMORPHIC, DexInsnFormat.FORMAT_45CC); + register(arr, DexOpcodes.INVOKE_POLYMORPHIC_RANGE, Opcode.INVOKE_POLYMORPHIC_RANGE, DexInsnFormat.FORMAT_4RCC); + + register(arr, DexOpcodes.INVOKE_CUSTOM, Opcode.INVOKE_CUSTOM, DexInsnFormat.FORMAT_35C); + register(arr, DexOpcodes.INVOKE_CUSTOM_RANGE, Opcode.INVOKE_CUSTOM_RANGE, DexInsnFormat.FORMAT_3RC); + + register(arr, DexOpcodes.CONST_METHOD_HANDLE, Opcode.CONST_METHOD_HANDLE, DexInsnFormat.FORMAT_21C); + register(arr, DexOpcodes.CONST_METHOD_TYPE, Opcode.CONST_METHOD_TYPE, DexInsnFormat.FORMAT_21C); + + PAYLOAD_INFO = new ConcurrentHashMap<>(3); + registerPayload(DexOpcodes.PACKED_SWITCH_PAYLOAD, Opcode.PACKED_SWITCH_PAYLOAD, DexInsnFormat.FORMAT_PACKED_SWITCH_PAYLOAD); + registerPayload(DexOpcodes.SPARSE_SWITCH_PAYLOAD, Opcode.SPARSE_SWITCH_PAYLOAD, DexInsnFormat.FORMAT_SPARSE_SWITCH_PAYLOAD); + registerPayload(DexOpcodes.FILL_ARRAY_DATA_PAYLOAD, Opcode.FILL_ARRAY_DATA_PAYLOAD, DexInsnFormat.FORMAT_FILL_ARRAY_DATA_PAYLOAD); + } + + private static void register(DexInsnInfo[] arr, int opcode, Opcode apiOpcode, DexInsnFormat format) { + arr[opcode] = new DexInsnInfo(opcode, apiOpcode, format, InsnIndexType.NONE); + } + + private static void register(DexInsnInfo[] arr, int opcode, Opcode apiOpcode, DexInsnFormat format, InsnIndexType indexType) { + arr[opcode] = new DexInsnInfo(opcode, apiOpcode, format, indexType); + } + + private static void registerPayload(int opcode, Opcode apiOpcode, DexInsnFormat format) { + PAYLOAD_INFO.put(opcode, new DexInsnInfo(opcode, apiOpcode, format, InsnIndexType.NONE)); + } + + @Nullable + public static DexInsnInfo get(int opcodeUnit) { + int opcode = opcodeUnit & 0xFF; + if (opcode == 0 && opcodeUnit != 0) { + return PAYLOAD_INFO.get(opcodeUnit); + } + return INSN_INFO[opcode]; + } + + private final int opcode; + private final Opcode apiOpcode; + private final DexInsnFormat format; + private final InsnIndexType indexType; + + public DexInsnInfo(int opcode, Opcode apiOpcode, DexInsnFormat format, InsnIndexType indexType) { + this.opcode = opcode; + this.apiOpcode = apiOpcode; + this.format = format; + this.indexType = indexType; + } + + public int getOpcode() { + return opcode; + } + + public Opcode getApiOpcode() { + return apiOpcode; + } + + public DexInsnFormat getFormat() { + return format; + } + + public InsnIndexType getIndexType() { + return indexType; + } + + @Override + public String toString() { + return String.format("0x%X :%d%d", opcode, format.getLength(), format.getRegsCount()); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexOpcodes.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexOpcodes.java new file mode 100644 index 000000000..4333df9cf --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexOpcodes.java @@ -0,0 +1,233 @@ +package jadx.plugins.input.dex.insns; + +public class DexOpcodes { + public static final int NOP = 0x00; + public static final int MOVE = 0x01; + public static final int MOVE_FROM16 = 0x02; + public static final int MOVE_16 = 0x03; + public static final int MOVE_WIDE = 0x04; + public static final int MOVE_WIDE_FROM16 = 0x05; + public static final int MOVE_WIDE_16 = 0x06; + public static final int MOVE_OBJECT = 0x07; + public static final int MOVE_OBJECT_FROM16 = 0x08; + public static final int MOVE_OBJECT_16 = 0x09; + public static final int MOVE_RESULT = 0x0a; + public static final int MOVE_RESULT_WIDE = 0x0b; + public static final int MOVE_RESULT_OBJECT = 0x0c; + public static final int MOVE_EXCEPTION = 0x0d; + public static final int RETURN_VOID = 0x0e; + public static final int RETURN = 0x0f; + public static final int RETURN_WIDE = 0x10; + public static final int RETURN_OBJECT = 0x11; + public static final int CONST_4 = 0x12; + public static final int CONST_16 = 0x13; + public static final int CONST = 0x14; + public static final int CONST_HIGH16 = 0x15; + public static final int CONST_WIDE_16 = 0x16; + public static final int CONST_WIDE_32 = 0x17; + public static final int CONST_WIDE = 0x18; + public static final int CONST_WIDE_HIGH16 = 0x19; + public static final int CONST_STRING = 0x1a; + public static final int CONST_STRING_JUMBO = 0x1b; + public static final int CONST_CLASS = 0x1c; + public static final int MONITOR_ENTER = 0x1d; + public static final int MONITOR_EXIT = 0x1e; + public static final int CHECK_CAST = 0x1f; + public static final int INSTANCE_OF = 0x20; + public static final int ARRAY_LENGTH = 0x21; + public static final int NEW_INSTANCE = 0x22; + public static final int NEW_ARRAY = 0x23; + public static final int FILLED_NEW_ARRAY = 0x24; + public static final int FILLED_NEW_ARRAY_RANGE = 0x25; + public static final int FILL_ARRAY_DATA = 0x26; + public static final int THROW = 0x27; + public static final int GOTO = 0x28; + public static final int GOTO_16 = 0x29; + public static final int GOTO_32 = 0x2a; + public static final int PACKED_SWITCH = 0x2b; + public static final int SPARSE_SWITCH = 0x2c; + public static final int CMPL_FLOAT = 0x2d; + public static final int CMPG_FLOAT = 0x2e; + public static final int CMPL_DOUBLE = 0x2f; + public static final int CMPG_DOUBLE = 0x30; + public static final int CMP_LONG = 0x31; + public static final int IF_EQ = 0x32; + public static final int IF_NE = 0x33; + public static final int IF_LT = 0x34; + public static final int IF_GE = 0x35; + public static final int IF_GT = 0x36; + public static final int IF_LE = 0x37; + public static final int IF_EQZ = 0x38; + public static final int IF_NEZ = 0x39; + public static final int IF_LTZ = 0x3a; + public static final int IF_GEZ = 0x3b; + public static final int IF_GTZ = 0x3c; + public static final int IF_LEZ = 0x3d; + public static final int AGET = 0x44; + public static final int AGET_WIDE = 0x45; + public static final int AGET_OBJECT = 0x46; + public static final int AGET_BOOLEAN = 0x47; + public static final int AGET_BYTE = 0x48; + public static final int AGET_CHAR = 0x49; + public static final int AGET_SHORT = 0x4a; + public static final int APUT = 0x4b; + public static final int APUT_WIDE = 0x4c; + public static final int APUT_OBJECT = 0x4d; + public static final int APUT_BOOLEAN = 0x4e; + public static final int APUT_BYTE = 0x4f; + public static final int APUT_CHAR = 0x50; + public static final int APUT_SHORT = 0x51; + public static final int IGET = 0x52; + public static final int IGET_WIDE = 0x53; + public static final int IGET_OBJECT = 0x54; + public static final int IGET_BOOLEAN = 0x55; + public static final int IGET_BYTE = 0x56; + public static final int IGET_CHAR = 0x57; + public static final int IGET_SHORT = 0x58; + public static final int IPUT = 0x59; + public static final int IPUT_WIDE = 0x5a; + public static final int IPUT_OBJECT = 0x5b; + public static final int IPUT_BOOLEAN = 0x5c; + public static final int IPUT_BYTE = 0x5d; + public static final int IPUT_CHAR = 0x5e; + public static final int IPUT_SHORT = 0x5f; + public static final int SGET = 0x60; + public static final int SGET_WIDE = 0x61; + public static final int SGET_OBJECT = 0x62; + public static final int SGET_BOOLEAN = 0x63; + public static final int SGET_BYTE = 0x64; + public static final int SGET_CHAR = 0x65; + public static final int SGET_SHORT = 0x66; + public static final int SPUT = 0x67; + public static final int SPUT_WIDE = 0x68; + public static final int SPUT_OBJECT = 0x69; + public static final int SPUT_BOOLEAN = 0x6a; + public static final int SPUT_BYTE = 0x6b; + public static final int SPUT_CHAR = 0x6c; + public static final int SPUT_SHORT = 0x6d; + public static final int INVOKE_VIRTUAL = 0x6e; + public static final int INVOKE_SUPER = 0x6f; + public static final int INVOKE_DIRECT = 0x70; + public static final int INVOKE_STATIC = 0x71; + public static final int INVOKE_INTERFACE = 0x72; + public static final int INVOKE_VIRTUAL_RANGE = 0x74; + public static final int INVOKE_SUPER_RANGE = 0x75; + public static final int INVOKE_DIRECT_RANGE = 0x76; + public static final int INVOKE_STATIC_RANGE = 0x77; + public static final int INVOKE_INTERFACE_RANGE = 0x78; + public static final int NEG_INT = 0x7b; + public static final int NOT_INT = 0x7c; + public static final int NEG_LONG = 0x7d; + public static final int NOT_LONG = 0x7e; + public static final int NEG_FLOAT = 0x7f; + public static final int NEG_DOUBLE = 0x80; + public static final int INT_TO_LONG = 0x81; + public static final int INT_TO_FLOAT = 0x82; + public static final int INT_TO_DOUBLE = 0x83; + public static final int LONG_TO_INT = 0x84; + public static final int LONG_TO_FLOAT = 0x85; + public static final int LONG_TO_DOUBLE = 0x86; + public static final int FLOAT_TO_INT = 0x87; + public static final int FLOAT_TO_LONG = 0x88; + public static final int FLOAT_TO_DOUBLE = 0x89; + public static final int DOUBLE_TO_INT = 0x8a; + public static final int DOUBLE_TO_LONG = 0x8b; + public static final int DOUBLE_TO_FLOAT = 0x8c; + public static final int INT_TO_BYTE = 0x8d; + public static final int INT_TO_CHAR = 0x8e; + public static final int INT_TO_SHORT = 0x8f; + public static final int ADD_INT = 0x90; + public static final int SUB_INT = 0x91; + public static final int MUL_INT = 0x92; + public static final int DIV_INT = 0x93; + public static final int REM_INT = 0x94; + public static final int AND_INT = 0x95; + public static final int OR_INT = 0x96; + public static final int XOR_INT = 0x97; + public static final int SHL_INT = 0x98; + public static final int SHR_INT = 0x99; + public static final int USHR_INT = 0x9a; + public static final int ADD_LONG = 0x9b; + public static final int SUB_LONG = 0x9c; + public static final int MUL_LONG = 0x9d; + public static final int DIV_LONG = 0x9e; + public static final int REM_LONG = 0x9f; + public static final int AND_LONG = 0xa0; + public static final int OR_LONG = 0xa1; + public static final int XOR_LONG = 0xa2; + public static final int SHL_LONG = 0xa3; + public static final int SHR_LONG = 0xa4; + public static final int USHR_LONG = 0xa5; + public static final int ADD_FLOAT = 0xa6; + public static final int SUB_FLOAT = 0xa7; + public static final int MUL_FLOAT = 0xa8; + public static final int DIV_FLOAT = 0xa9; + public static final int REM_FLOAT = 0xaa; + public static final int ADD_DOUBLE = 0xab; + public static final int SUB_DOUBLE = 0xac; + public static final int MUL_DOUBLE = 0xad; + public static final int DIV_DOUBLE = 0xae; + public static final int REM_DOUBLE = 0xaf; + public static final int ADD_INT_2ADDR = 0xb0; + public static final int SUB_INT_2ADDR = 0xb1; + public static final int MUL_INT_2ADDR = 0xb2; + public static final int DIV_INT_2ADDR = 0xb3; + public static final int REM_INT_2ADDR = 0xb4; + public static final int AND_INT_2ADDR = 0xb5; + public static final int OR_INT_2ADDR = 0xb6; + public static final int XOR_INT_2ADDR = 0xb7; + public static final int SHL_INT_2ADDR = 0xb8; + public static final int SHR_INT_2ADDR = 0xb9; + public static final int USHR_INT_2ADDR = 0xba; + public static final int ADD_LONG_2ADDR = 0xbb; + public static final int SUB_LONG_2ADDR = 0xbc; + public static final int MUL_LONG_2ADDR = 0xbd; + public static final int DIV_LONG_2ADDR = 0xbe; + public static final int REM_LONG_2ADDR = 0xbf; + public static final int AND_LONG_2ADDR = 0xc0; + public static final int OR_LONG_2ADDR = 0xc1; + public static final int XOR_LONG_2ADDR = 0xc2; + public static final int SHL_LONG_2ADDR = 0xc3; + public static final int SHR_LONG_2ADDR = 0xc4; + public static final int USHR_LONG_2ADDR = 0xc5; + public static final int ADD_FLOAT_2ADDR = 0xc6; + public static final int SUB_FLOAT_2ADDR = 0xc7; + public static final int MUL_FLOAT_2ADDR = 0xc8; + public static final int DIV_FLOAT_2ADDR = 0xc9; + public static final int REM_FLOAT_2ADDR = 0xca; + public static final int ADD_DOUBLE_2ADDR = 0xcb; + public static final int SUB_DOUBLE_2ADDR = 0xcc; + public static final int MUL_DOUBLE_2ADDR = 0xcd; + public static final int DIV_DOUBLE_2ADDR = 0xce; + public static final int REM_DOUBLE_2ADDR = 0xcf; + public static final int ADD_INT_LIT16 = 0xd0; + public static final int RSUB_INT = 0xd1; + public static final int MUL_INT_LIT16 = 0xd2; + public static final int DIV_INT_LIT16 = 0xd3; + public static final int REM_INT_LIT16 = 0xd4; + public static final int AND_INT_LIT16 = 0xd5; + public static final int OR_INT_LIT16 = 0xd6; + public static final int XOR_INT_LIT16 = 0xd7; + public static final int ADD_INT_LIT8 = 0xd8; + public static final int RSUB_INT_LIT8 = 0xd9; + public static final int MUL_INT_LIT8 = 0xda; + public static final int DIV_INT_LIT8 = 0xdb; + public static final int REM_INT_LIT8 = 0xdc; + public static final int AND_INT_LIT8 = 0xdd; + public static final int OR_INT_LIT8 = 0xde; + public static final int XOR_INT_LIT8 = 0xdf; + public static final int SHL_INT_LIT8 = 0xe0; + public static final int SHR_INT_LIT8 = 0xe1; + public static final int USHR_INT_LIT8 = 0xe2; + public static final int INVOKE_POLYMORPHIC = 0xfa; + public static final int INVOKE_POLYMORPHIC_RANGE = 0xfb; + public static final int INVOKE_CUSTOM = 0xfc; + public static final int INVOKE_CUSTOM_RANGE = 0xfd; + public static final int CONST_METHOD_HANDLE = 0xfe; + public static final int CONST_METHOD_TYPE = 0xff; + + // payload pseudo-instructions + public static final int PACKED_SWITCH_PAYLOAD = 0x0100; + public static final int SPARSE_SWITCH_PAYLOAD = 0x0200; + public static final int FILL_ARRAY_DATA_PAYLOAD = 0x0300; +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexArrayPayload.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexArrayPayload.java new file mode 100644 index 000000000..f46c72ccb --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexArrayPayload.java @@ -0,0 +1,31 @@ +package jadx.plugins.input.dex.insns.payloads; + +import jadx.api.plugins.input.insns.custom.IArrayPayload; + +public class DexArrayPayload implements IArrayPayload { + + private final int size; + private final int elemSize; + private final Object data; + + public DexArrayPayload(int size, int elemSize, Object data) { + this.size = size; + this.elemSize = elemSize; + this.data = data; + } + + @Override + public int getSize() { + return size; + } + + @Override + public int getElementSize() { + return elemSize; + } + + @Override + public Object getData() { + return data; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexSwitchPayload.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexSwitchPayload.java new file mode 100644 index 000000000..1b0889b90 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexSwitchPayload.java @@ -0,0 +1,31 @@ +package jadx.plugins.input.dex.insns.payloads; + +import jadx.api.plugins.input.insns.custom.ISwitchPayload; + +public class DexSwitchPayload implements ISwitchPayload { + + private final int size; + private final int[] keys; + private final int[] targets; + + public DexSwitchPayload(int size, int[] keys, int[] targets) { + this.size = size; + this.keys = keys; + this.targets = targets; + } + + @Override + public int getSize() { + return size; + } + + @Override + public int[] getKeys() { + return keys; + } + + @Override + public int[] getTargets() { + return targets; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java new file mode 100644 index 000000000..e351e7b1b --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java @@ -0,0 +1,193 @@ +package jadx.plugins.input.dex.sections; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.IClassData; +import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.IMethodData; +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.plugins.input.dex.sections.annotations.AnnotationsParser; + +public class DexClassData implements IClassData { + public static final int SIZE = 8 * 4; + + private final SectionReader in; + private final AnnotationsParser annotationsParser; + + public DexClassData(SectionReader sectionReader, AnnotationsParser annotationsParser) { + this.in = sectionReader; + this.annotationsParser = annotationsParser; + } + + @Override + public String getType() { + int typeIdx = in.pos(0).readInt(); + String clsType = in.getType(typeIdx); + if (clsType == null) { + throw new NullPointerException("Unknown class type"); + } + return clsType; + } + + @Override + public int getAccessFlags() { + return in.pos(4).readInt(); + } + + @Nullable + @Override + public String getSuperType() { + int typeIdx = in.pos(2 * 4).readInt(); + return in.getType(typeIdx); + } + + @Override + public List getInterfacesTypes() { + int offset = in.pos(3 * 4).readInt(); + if (offset == 0) { + return Collections.emptyList(); + } + return in.absPos(offset).readTypeList(); + } + + @Nullable + @Override + public String getSourceFile() { + int strIdx = in.pos(4 * 4).readInt(); + return in.getString(strIdx); + } + + @Override + public Path getInputPath() { + return in.getDexReader().getPath(); + } + + public int getAnnotationsOff() { + return in.pos(5 * 4).readInt(); + } + + public int getClassDataOff() { + return in.pos(6 * 4).readInt(); + } + + public int getStaticValuesOff() { + return in.pos(7 * 4).readInt(); + } + + @Override + public void visitFieldsAndMethods(Consumer fieldConsumer, Consumer mthConsumer) { + int classDataOff = getClassDataOff(); + if (classDataOff == 0) { + return; + } + SectionReader data = in.copy(classDataOff); + int staticFieldsCount = data.readUleb128(); + int instanceFieldsCount = data.readUleb128(); + int directMthCount = data.readUleb128(); + int virtualMthCount = data.readUleb128(); + + annotationsParser.setOffset(getAnnotationsOff()); + visitFields(fieldConsumer, data, staticFieldsCount, instanceFieldsCount); + visitMethods(mthConsumer, data, directMthCount, virtualMthCount); + } + + private void visitFields(Consumer fieldConsumer, SectionReader data, int staticFieldsCount, int instanceFieldsCount) { + Map annotationOffsetMap = annotationsParser.readFieldsAnnotationOffsetMap(); + DexFieldData fieldData = new DexFieldData(annotationsParser); + fieldData.setParentClassType(getType()); + readFields(fieldConsumer, data, fieldData, staticFieldsCount, annotationOffsetMap); + readFields(fieldConsumer, data, fieldData, instanceFieldsCount, annotationOffsetMap); + } + + private void readFields(Consumer fieldConsumer, SectionReader data, DexFieldData fieldData, int count, + Map annOffsetMap) { + int fieldId = 0; + for (int i = 0; i < count; i++) { + fieldId += data.readUleb128(); + int accFlags = data.readUleb128(); + in.fillFieldData(fieldData, fieldId); + fieldData.setAccessFlags(accFlags); + fieldData.setAnnotationsOffset(getOffsetFromMap(fieldId, annOffsetMap)); + fieldConsumer.accept(fieldData); + } + } + + private void visitMethods(Consumer mthConsumer, SectionReader data, int directMthCount, int virtualMthCount) { + DexMethodData methodData = new DexMethodData(annotationsParser); + Map annotationOffsetMap = annotationsParser.readMethodsAnnotationOffsetMap(); + Map paramsAnnOffsetMap = annotationsParser.readMethodParamsAnnRefOffsetMap(); + + methodData.setParentClassType(getType()); + methodData.setDirect(true); + readMethods(mthConsumer, data, methodData, directMthCount, annotationOffsetMap, paramsAnnOffsetMap); + methodData.setDirect(false); + readMethods(mthConsumer, data, methodData, virtualMthCount, annotationOffsetMap, paramsAnnOffsetMap); + } + + private void readMethods(Consumer mthConsumer, SectionReader data, DexMethodData methodData, int count, + Map annotationOffsetMap, Map paramsAnnOffsetMap) { + DexCodeReader dexCodeReader = new DexCodeReader(in.copy()); + int mthIdx = 0; + for (int i = 0; i < count; i++) { + mthIdx += data.readUleb128(); + int accFlags = data.readUleb128(); + int codeOff = data.readUleb128(); + in.fillMethodData(methodData, mthIdx); + methodData.setAccessFlags(accFlags); + if (codeOff == 0) { + methodData.setCodeReader(null); + } else { + dexCodeReader.setMthId(mthIdx); + dexCodeReader.setOffset(codeOff); + methodData.setCodeReader(dexCodeReader); + } + methodData.setAnnotationsOffset(getOffsetFromMap(mthIdx, annotationOffsetMap)); + methodData.setParamAnnotationsOffset(getOffsetFromMap(mthIdx, paramsAnnOffsetMap)); + mthConsumer.accept(methodData); + } + } + + private static int getOffsetFromMap(int idx, Map annOffsetMap) { + Integer offset = annOffsetMap.get(idx); + return offset != null ? offset : 0; + } + + @Override + public List getStaticFieldInitValues() { + int staticValuesOff = getStaticValuesOff(); + if (staticValuesOff == 0) { + return Collections.emptyList(); + } + in.absPos(staticValuesOff); + int count = in.readUleb128(); + List list = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + list.add(annotationsParser.parseEncodedValue(in)); + } + return list; + } + + @Override + public List getAnnotations() { + annotationsParser.setOffset(getAnnotationsOff()); + return annotationsParser.readClassAnnotations(); + } + + @Override + public int getClassDefOffset() { + return in.pos(0).getAbsPos(); + } + + @Override + public String toString() { + return getType(); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexCodeReader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexCodeReader.java new file mode 100644 index 000000000..b6bcda91e --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexCodeReader.java @@ -0,0 +1,189 @@ +package jadx.plugins.input.dex.sections; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.ICatch; +import jadx.api.plugins.input.data.ICodeReader; +import jadx.api.plugins.input.data.IDebugInfo; +import jadx.api.plugins.input.data.ITry; +import jadx.api.plugins.input.insns.InsnData; +import jadx.plugins.input.dex.DexException; +import jadx.plugins.input.dex.insns.DexInsnData; +import jadx.plugins.input.dex.insns.DexInsnFormat; +import jadx.plugins.input.dex.insns.DexInsnInfo; +import jadx.plugins.input.dex.sections.debuginfo.DebugInfoParser; +import jadx.plugins.input.dex.sections.trycatch.DexCatch; +import jadx.plugins.input.dex.sections.trycatch.DexTryData; + +public class DexCodeReader implements ICodeReader { + + private final SectionReader in; + private int mthId; + + public DexCodeReader(SectionReader in) { + this.in = in; + } + + @Override + public DexCodeReader copy() { + DexCodeReader copy = new DexCodeReader(in.copy()); + copy.setMthId(this.getMthId()); + return copy; + } + + public void setOffset(int offset) { + this.in.setOffset(offset); + } + + @Override + public int getRegistersCount() { + return in.pos(0).readUShort(); + } + + @Override + public int getInsnsCount() { + return in.pos(12).readInt(); + } + + @Override + public void visitInstructions(Consumer insnConsumer) { + DexInsnData insnData = new DexInsnData(this, in.copy()); + in.pos(12); + int size = in.readInt(); + int offset = 0; // in code units (2 byte) + while (offset < size) { + int opcodeUnit = in.readUShort(); + DexInsnInfo insnInfo = DexInsnInfo.get(opcodeUnit); + insnData.setOffset(offset); + insnData.setInsnInfo(insnInfo); + insnData.setOpcodeUnit(opcodeUnit); + insnData.setPayload(null); + insnData.setDecoded(false); + if (insnInfo != null) { + DexInsnFormat format = insnInfo.getFormat(); + insnData.setRegsCount(format.getRegsCount()); + insnData.setLength(format.getLength()); + } else { + insnData.setRegsCount(0); + insnData.setLength(1); + } + + insnConsumer.accept(insnData); + + if (!insnData.isDecoded()) { + skip(insnData); + } + offset += insnData.getLength(); + } + } + + public void decode(DexInsnData insn) { + DexInsnFormat format = insn.getInsnInfo().getFormat(); + format.decode(insn, insn.getOpcodeUnit(), insn.getCodeData().in); + insn.setDecoded(true); + } + + public void skip(DexInsnData insn) { + DexInsnInfo insnInfo = insn.getInsnInfo(); + if (insnInfo != null) { + DexCodeReader codeReader = insn.getCodeData(); + insnInfo.getFormat().skip(insn, codeReader.in); + } + } + + @Nullable + @Override + public IDebugInfo getDebugInfo() { + int debugOff = in.pos(8).readInt(); + if (debugOff == 0) { + return null; + } + int regsCount = getRegistersCount(); + DebugInfoParser debugInfoParser = new DebugInfoParser(in, regsCount, getInsnsCount()); + debugInfoParser.initMthArgs(regsCount, in.getMethodParamTypes(mthId)); + return debugInfoParser.process(debugOff); + } + + private int getTriesCount() { + return in.pos(6).readUShort(); + } + + private int getTriesOffset() { + int triesCount = getTriesCount(); + if (triesCount == 0) { + return -1; + } + int insnsCount = getInsnsCount(); + int padding = insnsCount % 2 == 1 ? 2 : 0; + return 4 * 4 + insnsCount * 2 + padding; + } + + @Override + public List getTries() { + int triesOffset = getTriesOffset(); + if (triesOffset == -1) { + return Collections.emptyList(); + } + int triesCount = getTriesCount(); + Map catchHandlers = getCatchHandlers(triesOffset + 8 * triesCount, in.copy()); + in.pos(triesOffset); + List triesList = new ArrayList<>(triesCount); + for (int i = 0; i < triesCount; i++) { + int startAddr = in.readInt(); + int insnsCount = in.readUShort(); + int handlerOff = in.readUShort(); + ICatch catchHandler = catchHandlers.get(handlerOff); + if (catchHandler == null) { + throw new DexException("Catch handler not found by byte offset: " + handlerOff); + } + triesList.add(new DexTryData(catchHandler, startAddr, insnsCount)); + } + return triesList; + } + + private Map getCatchHandlers(int offset, SectionReader ext) { + in.pos(offset); + int byteOffsetStart = in.getAbsPos(); + int size = in.readUleb128(); + Map map = new HashMap<>(size); + for (int i = 0; i < size; i++) { + int byteIndex = in.getAbsPos() - byteOffsetStart; + int sizeAndType = in.readSleb128(); + int handlersLen = Math.abs(sizeAndType); + int[] addr = new int[handlersLen]; + String[] types = new String[handlersLen]; + for (int h = 0; h < handlersLen; h++) { + types[h] = ext.getType(in.readUleb128()); + addr[h] = in.readUleb128(); + } + int catchAllAddr; + if (sizeAndType <= 0) { + catchAllAddr = in.readUleb128(); + } else { + catchAllAddr = -1; + } + map.put(byteIndex, new DexCatch(addr, types, catchAllAddr)); + } + return map; + } + + @Override + public int getCodeOffset() { + return in.getOffset(); + } + + public void setMthId(int mthId) { + this.mthId = mthId; + } + + public int getMthId() { + return mthId; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexConsts.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexConsts.java new file mode 100644 index 000000000..7323574fb --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexConsts.java @@ -0,0 +1,12 @@ +package jadx.plugins.input.dex.sections; + +public class DexConsts { + + public static final byte[] DEX_FILE_MAGIC = { 0x64, 0x65, 0x78, 0x0a }; // 'dex\n' + + public static final byte[] ZIP_FILE_MAGIC = { 0x50, 0x4B, 0x03, 0x04 }; + + public static final int ENDIAN_CONSTANT = 0x12345678; + + public static final int NO_INDEX = -1; +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexFieldData.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexFieldData.java new file mode 100644 index 000000000..ce482d468 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexFieldData.java @@ -0,0 +1,77 @@ +package jadx.plugins.input.dex.sections; + +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.plugins.input.dex.sections.annotations.AnnotationsParser; + +public class DexFieldData implements IFieldData { + @Nullable + private final AnnotationsParser annotationsParser; + + private String parentClassType; + private String type; + private String name; + private int accessFlags; + private int annotationsOffset; + + public DexFieldData(@Nullable AnnotationsParser parser) { + this.annotationsParser = parser; + } + + @Override + public String getParentClassType() { + return parentClassType; + } + + public void setParentClassType(String parentClassType) { + this.parentClassType = parentClassType; + } + + @Override + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int getAccessFlags() { + return accessFlags; + } + + public void setAccessFlags(int accessFlags) { + this.accessFlags = accessFlags; + } + + public void setAnnotationsOffset(int annotationsOffset) { + this.annotationsOffset = annotationsOffset; + } + + @Override + public List getAnnotations() { + if (annotationsParser == null) { + throw new NullPointerException("Annotation parser not initialized"); + } + return annotationsParser.readAnnotationList(annotationsOffset); + } + + @Override + public String toString() { + return getParentClassType() + "->" + getName() + ":" + getType(); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexHeader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexHeader.java new file mode 100644 index 000000000..3dc24259d --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexHeader.java @@ -0,0 +1,97 @@ +package jadx.plugins.input.dex.sections; + +import jadx.plugins.input.dex.DexException; + +public class DexHeader { + private final String version; + private final int classDefsSize; + private final int classDefsOff; + private final int stringIdsOff; + private final int typeIdsOff; + private final int typeIdsSize; + private final int fieldIdsSize; + private final int fieldIdsOff; + private final int protoIdsSize; + private final int protoIdsOff; + private final int methodIdsOff; + private final int methodIdsSize; + + public DexHeader(SectionReader buf) { + byte[] magic = buf.readByteArray(4); + version = buf.readString(3); + buf.skip(1); + int checksum = buf.readInt(); + byte[] signature = buf.readByteArray(20); + int fileSize = buf.readInt(); + int headerSize = buf.readInt(); + int endianTag = buf.readInt(); + if (endianTag != DexConsts.ENDIAN_CONSTANT) { + throw new DexException("Unexpected endian tag: 0x" + Integer.toHexString(endianTag)); + } + int linkSize = buf.readInt(); + int linkOff = buf.readInt(); + int mapListOff = buf.readInt(); + int stringIdsSize = buf.readInt(); + stringIdsOff = buf.readInt(); + typeIdsSize = buf.readInt(); + typeIdsOff = buf.readInt(); + protoIdsSize = buf.readInt(); + protoIdsOff = buf.readInt(); + fieldIdsSize = buf.readInt(); + fieldIdsOff = buf.readInt(); + methodIdsSize = buf.readInt(); + methodIdsOff = buf.readInt(); + classDefsSize = buf.readInt(); + classDefsOff = buf.readInt(); + int dataSize = buf.readInt(); + int dataOff = buf.readInt(); + } + + public String getVersion() { + return version; + } + + public int getClassDefsSize() { + return classDefsSize; + } + + public int getClassDefsOff() { + return classDefsOff; + } + + public int getStringIdsOff() { + return stringIdsOff; + } + + public int getTypeIdsOff() { + return typeIdsOff; + } + + public int getTypeIdsSize() { + return typeIdsSize; + } + + public int getFieldIdsSize() { + return fieldIdsSize; + } + + public int getFieldIdsOff() { + return fieldIdsOff; + } + + public int getProtoIdsSize() { + return protoIdsSize; + } + + public int getProtoIdsOff() { + return protoIdsOff; + } + + public int getMethodIdsOff() { + return methodIdsOff; + } + + public int getMethodIdsSize() { + return methodIdsSize; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodData.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodData.java new file mode 100644 index 000000000..cba773eda --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodData.java @@ -0,0 +1,133 @@ +package jadx.plugins.input.dex.sections; + +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.ICodeReader; +import jadx.api.plugins.input.data.IMethodData; +import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.plugins.input.dex.sections.annotations.AnnotationsParser; +import jadx.plugins.input.dex.smali.SmaliPrinter; +import jadx.plugins.input.dex.utils.Utils; + +public class DexMethodData implements IMethodData { + @Nullable + private final AnnotationsParser annotationsParser; + + private String parentClassType; + private String returnType; + private List argTypes; + private String name; + private int accessFlags; + private boolean isDirect; + private int annotationsOffset; + private int paramAnnotationsOffset; + + @Nullable + private DexCodeReader codeReader; + + public DexMethodData(@Nullable AnnotationsParser annotationsParser) { + this.annotationsParser = annotationsParser; + } + + @Override + public String getParentClassType() { + return parentClassType; + } + + public void setParentClassType(String parentClassType) { + this.parentClassType = parentClassType; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int getAccessFlags() { + return accessFlags; + } + + public void setAccessFlags(int accessFlags) { + this.accessFlags = accessFlags; + } + + @Override + public String getReturnType() { + return returnType; + } + + public void setReturnType(String returnType) { + this.returnType = returnType; + } + + @Override + public List getArgTypes() { + return argTypes; + } + + public void setArgTypes(List argTypes) { + this.argTypes = argTypes; + } + + @Override + public boolean isDirect() { + return isDirect; + } + + public void setDirect(boolean direct) { + isDirect = direct; + } + + @Nullable + @Override + public ICodeReader getCodeReader() { + return codeReader; + } + + public void setCodeReader(@Nullable DexCodeReader codeReader) { + this.codeReader = codeReader; + } + + @Override + public String disassembleMethod() { + return SmaliPrinter.printMethod(this); + } + + public void setAnnotationsOffset(int annotationsOffset) { + this.annotationsOffset = annotationsOffset; + } + + public void setParamAnnotationsOffset(int paramAnnotationsOffset) { + this.paramAnnotationsOffset = paramAnnotationsOffset; + } + + @Override + public List getAnnotations() { + return getAnnotationsParser().readAnnotationList(annotationsOffset); + } + + @Override + public List> getParamsAnnotations() { + return getAnnotationsParser().readAnnotationRefList(paramAnnotationsOffset); + } + + private AnnotationsParser getAnnotationsParser() { + if (annotationsParser == null) { + throw new NullPointerException("Annotation parser not initialized"); + } + return annotationsParser; + } + + @Override + public String toString() { + return getParentClassType() + "->" + getName() + + '(' + Utils.listToStr(getArgTypes()) + ")" + getReturnType(); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/SectionReader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/SectionReader.java new file mode 100644 index 000000000..ebd9b4ab7 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/SectionReader.java @@ -0,0 +1,245 @@ +package jadx.plugins.input.dex.sections; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.IMethodData; +import jadx.plugins.input.dex.DexReader; +import jadx.plugins.input.dex.utils.Leb128; +import jadx.plugins.input.dex.utils.MUtf8; + +import static jadx.plugins.input.dex.sections.DexConsts.NO_INDEX; + +public class SectionReader { + private final ByteBuffer buf; + private final DexReader dexReader; + private int offset; + + public SectionReader(DexReader dexReader, int off) { + this.dexReader = dexReader; + this.offset = off; + this.buf = duplicate(dexReader.getBuf(), off); + } + + private SectionReader(SectionReader sectionReader, int off) { + this(sectionReader.dexReader, off); + } + + public SectionReader copy() { + return new SectionReader(this, offset); + } + + public SectionReader copy(int off) { + return new SectionReader(this, off); + } + + private static ByteBuffer duplicate(ByteBuffer baseBuffer, int off) { + ByteBuffer dupBuf = baseBuffer.duplicate(); + dupBuf.order(ByteOrder.LITTLE_ENDIAN); + dupBuf.position(off); + return dupBuf; + } + + public void setOffset(int offset) { + this.offset = offset; + } + + public int getOffset() { + return offset; + } + + public void shiftOffset(int shift) { + this.offset += shift; + } + + public SectionReader pos(int pos) { + buf.position(offset + pos); + return this; + } + + public SectionReader absPos(int pos) { + buf.position(pos); + return this; + } + + public int getAbsPos() { + return buf.position(); + } + + public void skip(int skip) { + int pos = buf.position(); + buf.position(pos + skip); + } + + public int readInt() { + return buf.getInt(); + } + + public long readLong() { + return buf.getLong(); + } + + public byte readByte() { + return buf.get(); + } + + public int readUByte() { + return buf.get() & 0xFF; + } + + public int readUShort() { + return buf.getShort() & 0xFFFF; + } + + public int readShort() { + return buf.getShort(); + } + + public byte[] readByteArray(int len) { + byte[] arr = new byte[len]; + buf.get(arr); + return arr; + } + + public int[] readUShortArray(int size) { + int[] arr = new int[size]; + for (int i = 0; i < size; i++) { + arr[i] = readUShort(); + } + return arr; + } + + public String readString(int len) { + return new String(readByteArray(len), StandardCharsets.US_ASCII); + } + + public List readTypeList() { + int size = readInt(); + if (size == 0) { + return Collections.emptyList(); + } + int[] typeIds = readUShortArray(size); + List types = new ArrayList<>(size); + for (int typeId : typeIds) { + types.add(getType(typeId)); + } + return types; + } + + @Nullable + public String getType(int idx) { + if (idx == NO_INDEX) { + return null; + } + int typeIdsOff = dexReader.getHeader().getTypeIdsOff(); + absPos(typeIdsOff + idx * 4); + int strIdx = readInt(); + return getString(strIdx); + } + + @Nullable + public String getString(int idx) { + if (idx == NO_INDEX) { + return null; + } + // TODO: make string pool cache? + int stringIdsOff = dexReader.getHeader().getStringIdsOff(); + absPos(stringIdsOff + idx * 4); + int strOff = readInt(); + absPos(strOff); + return MUtf8.decode(this); + } + + public IFieldData getFieldData(int idx) { + DexFieldData fieldData = new DexFieldData(null); + int clsTypeIdx = fillFieldData(fieldData, idx); + fieldData.setParentClassType(getType(clsTypeIdx)); + return fieldData; + } + + public int fillFieldData(DexFieldData fieldData, int idx) { + int fieldIdsOff = dexReader.getHeader().getFieldIdsOff(); + absPos(fieldIdsOff + idx * 8); + int classTypeIdx = readUShort(); + int typeIdx = readUShort(); + int nameIdx = readInt(); + fieldData.setType(getType(typeIdx)); + fieldData.setName(getString(nameIdx)); + return classTypeIdx; + } + + public IMethodData getMethodData(int idx) { + DexMethodData methodData = new DexMethodData(null); + int clsTypeIdx = fillMethodData(methodData, idx); + methodData.setParentClassType(getType(clsTypeIdx)); + return methodData; + } + + public int fillMethodData(DexMethodData methodData, int idx) { + int methodIdsOff = dexReader.getHeader().getMethodIdsOff(); + absPos(methodIdsOff + idx * 8); + int classTypeIdx = readUShort(); + int protoIdx = readUShort(); + int nameIdx = readInt(); + + int protoIdsOff = dexReader.getHeader().getProtoIdsOff(); + absPos(protoIdsOff + protoIdx * 12); + int shortyIdx = readInt(); + int returnTypeIdx = readInt(); + int paramsOff = readInt(); + + List argTypes; + if (paramsOff == 0) { + argTypes = Collections.emptyList(); + } else { + argTypes = absPos(paramsOff).readTypeList(); + } + methodData.setName(getString(nameIdx)); + methodData.setReturnType(getType(returnTypeIdx)); + methodData.setArgTypes(argTypes); + return classTypeIdx; + } + + public List getMethodParamTypes(int idx) { + int methodIdsOff = dexReader.getHeader().getMethodIdsOff(); + absPos(methodIdsOff + idx * 8 + 2); + int protoIdx = readUShort(); + + int protoIdsOff = dexReader.getHeader().getProtoIdsOff(); + absPos(protoIdsOff + protoIdx * 12 + 8); + int paramsOff = readInt(); + + if (paramsOff == 0) { + return Collections.emptyList(); + } + return absPos(paramsOff).readTypeList(); + } + + public DexReader getDexReader() { + return dexReader; + } + + public int readUleb128() { + return Leb128.readUnsignedLeb128(this); + } + + public int readUleb128p1() { + return Leb128.readUnsignedLeb128(this) - 1; + } + + public int readSleb128() { + return Leb128.readSignedLeb128(this); + } + + @Override + public String toString() { + return "SectionReader{buf=" + buf + ", offset=" + offset + '}'; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/AnnotationsParser.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/AnnotationsParser.java new file mode 100644 index 000000000..cf8a8ba6f --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/AnnotationsParser.java @@ -0,0 +1,167 @@ +package jadx.plugins.input.dex.sections.annotations; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import jadx.api.plugins.input.data.annotations.AnnotationVisibility; +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.plugins.input.dex.DexException; +import jadx.plugins.input.dex.sections.SectionReader; + +public class AnnotationsParser { + private final SectionReader in; + private final SectionReader ext; + + private int offset; + private int fieldsCount; + private int methodsCount; + private int paramsRefCount; + + public AnnotationsParser(SectionReader in, SectionReader ext) { + this.in = in; + this.ext = ext; + } + + public void setOffset(int offset) { + this.offset = offset; + if (offset == 0) { + this.fieldsCount = 0; + this.methodsCount = 0; + this.paramsRefCount = 0; + } else { + in.setOffset(offset); + in.pos(4); + this.fieldsCount = in.readInt(); + this.methodsCount = in.readInt(); + this.paramsRefCount = in.readInt(); + } + } + + public List readClassAnnotations() { + if (offset == 0) { + return Collections.emptyList(); + } + int classAnnotationsOffset = in.absPos(offset).readInt(); + return readAnnotationList(classAnnotationsOffset); + } + + public Map readFieldsAnnotationOffsetMap() { + if (fieldsCount == 0) { + return Collections.emptyMap(); + } + in.pos(4 * 4); + Map map = new HashMap<>(fieldsCount); + for (int i = 0; i < fieldsCount; i++) { + int fieldIdx = in.readInt(); + int fieldAnnOffset = in.readInt(); + map.put(fieldIdx, fieldAnnOffset); + } + return map; + } + + public Map readMethodsAnnotationOffsetMap() { + if (methodsCount == 0) { + return Collections.emptyMap(); + } + in.pos(4 * 4 + fieldsCount * 2 * 4); + Map map = new HashMap<>(methodsCount); + for (int i = 0; i < methodsCount; i++) { + int methodIdx = in.readInt(); + int methodAnnOffset = in.readInt(); + map.put(methodIdx, methodAnnOffset); + } + return map; + } + + public Map readMethodParamsAnnRefOffsetMap() { + if (paramsRefCount == 0) { + return Collections.emptyMap(); + } + in.pos(4 * 4 + fieldsCount * 2 * 4 + methodsCount * 2 * 4); + Map map = new HashMap<>(paramsRefCount); + for (int i = 0; i < paramsRefCount; i++) { + int methodIdx = in.readInt(); + int methodAnnRefOffset = in.readInt(); + map.put(methodIdx, methodAnnRefOffset); + } + return map; + } + + public List readAnnotationList(int offset) { + if (offset == 0) { + return Collections.emptyList(); + } + in.absPos(offset); + int size = in.readInt(); + if (size == 0) { + return Collections.emptyList(); + } + List list = new ArrayList<>(size); + int pos = in.getAbsPos(); + for (int i = 0; i < size; i++) { + in.absPos(pos + i * 4); + int annOffset = in.readInt(); + in.absPos(annOffset); + list.add(readAnnotation(in, ext, true)); + } + return list; + } + + public List> readAnnotationRefList(int offset) { + if (offset == 0) { + return Collections.emptyList(); + } + in.absPos(offset); + int size = in.readInt(); + if (size == 0) { + return Collections.emptyList(); + } + List> list = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + int refOff = in.readInt(); + int pos = in.getAbsPos(); + list.add(readAnnotationList(refOff)); + in.absPos(pos); + } + return list; + } + + public static IAnnotation readAnnotation(SectionReader in, SectionReader ext, boolean readVisibility) { + AnnotationVisibility visibility = null; + if (readVisibility) { + int v = in.readUByte(); + visibility = getVisibilityValue(v); + } + int typeIndex = in.readUleb128(); + int size = in.readUleb128(); + Map values = new LinkedHashMap<>(size); + for (int i = 0; i < size; i++) { + String name = ext.getString(in.readUleb128()); + values.put(name, EncodedValueParser.parseValue(in, ext)); + } + String type = ext.getType(typeIndex); + return new DexAnnotation(visibility, type, values); + } + + private static AnnotationVisibility getVisibilityValue(int value) { + switch (value) { + case 0: + return AnnotationVisibility.BUILD; + case 1: + return AnnotationVisibility.RUNTIME; + case 2: + return AnnotationVisibility.SYSTEM; + default: + throw new DexException("Unknown annotation visibility value: " + value); + } + } + + public EncodedValue parseEncodedValue(SectionReader in) { + return EncodedValueParser.parseValue(in, ext); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/DexAnnotation.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/DexAnnotation.java new file mode 100644 index 000000000..d4b42b603 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/DexAnnotation.java @@ -0,0 +1,39 @@ +package jadx.plugins.input.dex.sections.annotations; + +import java.util.Map; + +import jadx.api.plugins.input.data.annotations.AnnotationVisibility; +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.data.annotations.IAnnotation; + +public class DexAnnotation implements IAnnotation { + private final AnnotationVisibility visibility; + private final String type; + private final Map values; + + public DexAnnotation(AnnotationVisibility visibility, String type, Map values) { + this.visibility = visibility; + this.type = type; + this.values = values; + } + + @Override + public String getAnnotationClass() { + return type; + } + + @Override + public AnnotationVisibility getVisibility() { + return visibility; + } + + @Override + public Map getValues() { + return values; + } + + @Override + public String toString() { + return "DexAnnotation{" + visibility + ", type=" + type + ", values=" + values + '}'; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/EncodedValueParser.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/EncodedValueParser.java new file mode 100644 index 000000000..adaaad25c --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/EncodedValueParser.java @@ -0,0 +1,117 @@ +package jadx.plugins.input.dex.sections.annotations; + +import java.util.ArrayList; +import java.util.List; + +import jadx.api.plugins.input.data.annotations.EncodedType; +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.plugins.input.dex.DexException; +import jadx.plugins.input.dex.sections.SectionReader; + +public class EncodedValueParser { + + private static final int ENCODED_BYTE = 0x00; + private static final int ENCODED_SHORT = 0x02; + private static final int ENCODED_CHAR = 0x03; + private static final int ENCODED_INT = 0x04; + private static final int ENCODED_LONG = 0x06; + private static final int ENCODED_FLOAT = 0x10; + private static final int ENCODED_DOUBLE = 0x11; + private static final int ENCODED_STRING = 0x17; + private static final int ENCODED_TYPE = 0x18; + private static final int ENCODED_FIELD = 0x19; + private static final int ENCODED_ENUM = 0x1b; + private static final int ENCODED_METHOD = 0x1a; + private static final int ENCODED_ARRAY = 0x1c; + private static final int ENCODED_ANNOTATION = 0x1d; + private static final int ENCODED_NULL = 0x1e; + private static final int ENCODED_BOOLEAN = 0x1f; + + static EncodedValue parseValue(SectionReader in, SectionReader ext) { + int argAndType = in.readUByte(); + int type = argAndType & 0x1F; + int arg = (argAndType & 0xE0) >> 5; + int size = arg + 1; + + switch (type) { + case ENCODED_NULL: + return EncodedValue.NULL; + + case ENCODED_BOOLEAN: + return new EncodedValue(EncodedType.ENCODED_BOOLEAN, arg == 1); + case ENCODED_BYTE: + return new EncodedValue(EncodedType.ENCODED_BYTE, in.readByte()); + + case ENCODED_SHORT: + return new EncodedValue(EncodedType.ENCODED_SHORT, (short) parseNumber(in, size, true)); + case ENCODED_CHAR: + return new EncodedValue(EncodedType.ENCODED_CHAR, (char) parseUnsignedInt(in, size)); + case ENCODED_INT: + return new EncodedValue(EncodedType.ENCODED_INT, (int) parseNumber(in, size, true)); + case ENCODED_LONG: + return new EncodedValue(EncodedType.ENCODED_LONG, parseNumber(in, size, true)); + + case ENCODED_FLOAT: + return new EncodedValue(EncodedType.ENCODED_FLOAT, Float.intBitsToFloat((int) parseNumber(in, size, false, 4))); + case ENCODED_DOUBLE: + return new EncodedValue(EncodedType.ENCODED_DOUBLE, Double.longBitsToDouble(parseNumber(in, size, false, 8))); + + case ENCODED_STRING: + return new EncodedValue(EncodedType.ENCODED_STRING, ext.getString(parseUnsignedInt(in, size))); + + case ENCODED_TYPE: + return new EncodedValue(EncodedType.ENCODED_TYPE, ext.getType(parseUnsignedInt(in, size))); + + case ENCODED_METHOD: + return new EncodedValue(EncodedType.ENCODED_METHOD, ext.getMethodData(parseUnsignedInt(in, size))); + + case ENCODED_FIELD: + case ENCODED_ENUM: + return new EncodedValue(EncodedType.ENCODED_FIELD, ext.getFieldData(parseUnsignedInt(in, size))); + + case ENCODED_ARRAY: + int count = in.readUleb128(); + List values = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + values.add(parseValue(in, ext)); + } + return new EncodedValue(EncodedType.ENCODED_ARRAY, values); + + case ENCODED_ANNOTATION: + return new EncodedValue(EncodedType.ENCODED_ANNOTATION, AnnotationsParser.readAnnotation(in, ext, false)); + + default: + throw new DexException("Unknown encoded value type: 0x" + Integer.toHexString(type)); + } + } + + private static int parseUnsignedInt(SectionReader in, int byteCount) { + return (int) parseNumber(in, byteCount, false, 0); + } + + private static long parseNumber(SectionReader in, int byteCount, boolean isSignExtended) { + return parseNumber(in, byteCount, isSignExtended, 0); + } + + private static long parseNumber(SectionReader in, int byteCount, boolean isSignExtended, int fillOnRight) { + long result = 0; + long last = 0; + for (int i = 0; i < byteCount; i++) { + last = in.readUByte(); + result |= last << (i * 8); + } + if (fillOnRight != 0) { + for (int i = byteCount; i < fillOnRight; i++) { + result <<= 8; + } + } else { + if (isSignExtended && (last & 0x80) != 0) { + for (int i = byteCount; i < 8; i++) { + result |= (long) 0xFF << (i * 8); + } + } + } + return result; + } + +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfo.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfo.java new file mode 100644 index 000000000..955dfb45f --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfo.java @@ -0,0 +1,33 @@ +package jadx.plugins.input.dex.sections.debuginfo; + +import java.util.List; +import java.util.Map; + +import jadx.api.plugins.input.data.IDebugInfo; +import jadx.api.plugins.input.data.ILocalVar; + +public class DebugInfo implements IDebugInfo { + + private final Map sourceLineMap; + private final List localVars; + + public DebugInfo(Map sourceLineMap, List localVars) { + this.sourceLineMap = sourceLineMap; + this.localVars = localVars; + } + + @Override + public Map getSourceLineMapping() { + return sourceLineMap; + } + + @Override + public List getLocalVars() { + return localVars; + } + + @Override + public String toString() { + return "DebugInfo{sourceLineMap=" + sourceLineMap + ", localVars=" + localVars + '}'; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParser.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfoParser.java similarity index 56% rename from jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParser.java rename to jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfoParser.java index e0f225221..5a48d54e5 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParser.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfoParser.java @@ -1,15 +1,16 @@ -package jadx.core.dex.visitors.debuginfo; +package jadx.plugins.input.dex.sections.debuginfo; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import com.android.dex.Dex.Section; +import org.jetbrains.annotations.Nullable; -import jadx.core.dex.attributes.nodes.SourceFileAttr; -import jadx.core.dex.instructions.args.RegisterArg; -import jadx.core.dex.nodes.DexNode; -import jadx.core.dex.nodes.InsnNode; -import jadx.core.dex.nodes.MethodNode; +import jadx.api.plugins.input.data.ILocalVar; +import jadx.plugins.input.dex.sections.DexConsts; +import jadx.plugins.input.dex.sections.SectionReader; public class DebugInfoParser { private static final int DBG_END_SEQUENCE = 0x00; @@ -30,46 +31,73 @@ public class DebugInfoParser { // the number of line increments represented private static final int DBG_LINE_RANGE = 15; - private final MethodNode mth; - private final Section section; - private final DexNode dex; + private final SectionReader in; + private final SectionReader ext; private final LocalVar[] locals; - private final InsnNode[] insnByOffset; + private final int codeSize; - private List resultList; + private List resultList; + private Map linesMap; + @Nullable + private String sourceFile; - public DebugInfoParser(MethodNode mth, int debugOffset, InsnNode[] insnByOffset) { - this.mth = mth; - this.dex = mth.dex(); - this.section = dex.openSection(debugOffset); + private List argTypes; + private int[] argRegs; - int regsCount = mth.getRegsCount(); + public DebugInfoParser(SectionReader in, int regsCount, int codeSize) { + this.in = in; + this.ext = in.copy(); this.locals = new LocalVar[regsCount]; - this.insnByOffset = insnByOffset; + this.codeSize = codeSize; } - public List process() { + public void initMthArgs(int regsCount, List argTypes) { + if (argTypes.isEmpty()) { + this.argTypes = Collections.emptyList(); + return; + } + + int argsCount = argTypes.size(); + int[] argRegsArr = new int[argsCount]; + int regNum = regsCount; + for (int i = argsCount - 1; i >= 0; i--) { + regNum -= getTypeLen(argTypes.get(i)); + argRegsArr[i] = regNum; + } + this.argRegs = argRegsArr; + this.argTypes = argTypes; + } + + public static int getTypeLen(String type) { + switch (type.charAt(0)) { + case 'J': + case 'D': + return 2; + default: + return 1; + } + } + + public DebugInfo process(int debugOff) { + in.absPos(debugOff); + boolean varsInfoFound = false; resultList = new ArrayList<>(); + linesMap = new HashMap<>(); int addr = 0; - int line = section.readUleb128(); - - int paramsCount = section.readUleb128(); - List mthArgs = mth.getArgRegs(); + int line = in.readUleb128(); + int paramsCount = in.readUleb128(); + int argsCount = argTypes.size(); for (int i = 0; i < paramsCount; i++) { - int nameId = section.readUleb128() - 1; - if (nameId != DexNode.NO_INDEX) { - String name = dex.getString(nameId); - if (i < mthArgs.size() && name != null) { - RegisterArg arg = mthArgs.get(i); - int regNum = arg.getRegNum(); - LocalVar lVar = new LocalVar(regNum, name, arg.getInitType()); - startVar(lVar, -1); - varsInfoFound = true; - } + int nameId = in.readUleb128p1(); + String name = ext.getString(nameId); + if (name != null && i < argsCount) { + int regNum = argRegs[i]; + startVar(new LocalVar(regNum, name, argTypes.get(i)), -1); + varsInfoFound = true; } } @@ -77,47 +105,47 @@ public class DebugInfoParser { addrChange(-1, 1, line); setLine(addr, line); - int c = section.readByte() & 0xFF; + int c = in.readUByte(); while (c != DBG_END_SEQUENCE) { switch (c) { case DBG_ADVANCE_PC: { - int addrInc = section.readUleb128(); + int addrInc = in.readUleb128(); addr = addrChange(addr, addrInc, line); setLine(addr, line); break; } case DBG_ADVANCE_LINE: { - line += section.readSleb128(); + line += in.readSleb128(); break; } case DBG_START_LOCAL: { - int regNum = section.readUleb128(); - int nameId = section.readUleb128() - 1; - int type = section.readUleb128() - 1; - LocalVar var = new LocalVar(dex, regNum, nameId, type, DexNode.NO_INDEX); + int regNum = in.readUleb128(); + int nameId = in.readUleb128() - 1; + int type = in.readUleb128() - 1; + LocalVar var = new LocalVar(ext, regNum, nameId, type, DexConsts.NO_INDEX); startVar(var, addr); varsInfoFound = true; break; } case DBG_START_LOCAL_EXTENDED: { - int regNum = section.readUleb128(); - int nameId = section.readUleb128() - 1; - int type = section.readUleb128() - 1; - int sign = section.readUleb128() - 1; - LocalVar var = new LocalVar(dex, regNum, nameId, type, sign); + int regNum = in.readUleb128(); + int nameId = in.readUleb128p1(); + int type = in.readUleb128p1(); + int sign = in.readUleb128p1(); + LocalVar var = new LocalVar(ext, regNum, nameId, type, sign); startVar(var, addr); varsInfoFound = true; break; } case DBG_RESTART_LOCAL: { - int regNum = section.readUleb128(); + int regNum = in.readUleb128(); restartVar(regNum, addr); varsInfoFound = true; break; } case DBG_END_LOCAL: { - int regNum = section.readUleb128(); + int regNum = in.readUleb128(); LocalVar var = locals[regNum]; if (var != null) { endVar(var, addr); @@ -132,11 +160,8 @@ public class DebugInfoParser { break; case DBG_SET_FILE: { - int idx = section.readUleb128() - 1; - if (idx != DexNode.NO_INDEX) { - String sourceFile = dex.getString(idx); - mth.addAttr(new SourceFileAttr(sourceFile)); - } + int idx = in.readUleb128() - 1; + this.sourceFile = ext.getString(idx); break; } @@ -149,24 +174,24 @@ public class DebugInfoParser { break; } } - c = section.readByte() & 0xFF; + c = in.readUByte(); } if (varsInfoFound) { for (LocalVar var : locals) { if (var != null && !var.isEnd()) { - endVar(var, mth.getCodeSize() - 1); + endVar(var, codeSize - 1); } } } - setSourceLines(addr, insnByOffset.length, line); + setSourceLines(addr, codeSize, line); - return resultList; + return new DebugInfo(linesMap, resultList); } private int addrChange(int addr, int addrInc, int line) { int newAddr = addr + addrInc; - int maxAddr = insnByOffset.length - 1; + int maxAddr = codeSize - 1; newAddr = Math.min(newAddr, maxAddr); setSourceLines(addr, newAddr, line); return newAddr; @@ -179,20 +204,15 @@ public class DebugInfoParser { } private void setLine(int offset, int line) { - InsnNode insn = insnByOffset[offset]; - if (insn != null) { - insn.setSourceLine(line); - } + linesMap.put(offset, line); } private void restartVar(int regNum, int addr) { LocalVar prev = locals[regNum]; if (prev != null) { endVar(prev, addr); - LocalVar newVar = new LocalVar(regNum, prev.getName(), prev.getType()); + LocalVar newVar = new LocalVar(regNum, prev.getName(), prev.getType(), prev.getSignature()); startVar(newVar, addr); - } else { - mth.addComment("Debug info: failed to restart local var, previous not found, register: " + regNum); } } diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/LocalVar.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/LocalVar.java new file mode 100644 index 000000000..4e8930b12 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/LocalVar.java @@ -0,0 +1,107 @@ +package jadx.plugins.input.dex.sections.debuginfo; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.ILocalVar; +import jadx.plugins.input.dex.sections.SectionReader; +import jadx.plugins.input.dex.utils.Utils; + +public class LocalVar implements ILocalVar { + private final int regNum; + private final String name; + private final String type; + @Nullable + private final String sign; + + private boolean isEnd; + private int startOffset; + private int endOffset; + + public LocalVar(SectionReader dex, int regNum, int nameId, int typeId, int signId) { + this(regNum, dex.getString(nameId), dex.getType(typeId), dex.getString(signId)); + } + + public LocalVar(int regNum, String name, String type) { + this(regNum, name, type, null); + } + + public LocalVar(int regNum, String name, String type, @Nullable String sign) { + this.regNum = regNum; + this.name = name; + this.type = type; + this.sign = sign; + } + + public void start(int addr) { + this.isEnd = false; + this.startOffset = addr; + } + + /** + * Sets end address of local variable + * + * @param addr address + * @return true if local variable was active, else false + */ + public boolean end(int addr) { + if (isEnd) { + return false; + } + this.isEnd = true; + this.endOffset = addr; + return true; + } + + @Override + public int getRegNum() { + return regNum; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getType() { + return type; + } + + @Nullable + @Override + public String getSignature() { + return sign; + } + + @Override + public int getStartOffset() { + return startOffset; + } + + @Override + public int getEndOffset() { + return endOffset; + } + + public boolean isEnd() { + return isEnd; + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public String toString() { + return Utils.formatOffset(startOffset) + + '-' + (isEnd ? Utils.formatOffset(endOffset) : " ") + + ": r" + regNum + " '" + name + "' " + type + + (sign != null ? ", signature: " + sign : ""); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexCatch.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexCatch.java new file mode 100644 index 000000000..c9ebc0eee --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexCatch.java @@ -0,0 +1,30 @@ +package jadx.plugins.input.dex.sections.trycatch; + +import jadx.api.plugins.input.data.ICatch; + +public class DexCatch implements ICatch { + private final int[] addr; + private final String[] types; + private final int allAddr; + + public DexCatch(int[] addr, String[] types, int allAddr) { + this.addr = addr; + this.types = types; + this.allAddr = allAddr; + } + + @Override + public int[] getAddresses() { + return addr; + } + + @Override + public String[] getTypes() { + return types; + } + + @Override + public int getCatchAllAddress() { + return allAddr; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexTryData.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexTryData.java new file mode 100644 index 000000000..fcb606c2a --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexTryData.java @@ -0,0 +1,32 @@ +package jadx.plugins.input.dex.sections.trycatch; + +import jadx.api.plugins.input.data.ICatch; +import jadx.api.plugins.input.data.ITry; + +public class DexTryData implements ITry { + + private final ICatch catchHandler; + private final int startAddr; + private final int insnsCount; + + public DexTryData(ICatch catchHandler, int startAddr, int insnsCount) { + this.catchHandler = catchHandler; + this.startAddr = startAddr; + this.insnsCount = insnsCount; + } + + @Override + public ICatch getCatch() { + return catchHandler; + } + + @Override + public int getStartAddress() { + return startAddr; + } + + @Override + public int getInstructionCount() { + return insnsCount; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/InsnFormatter.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/InsnFormatter.java new file mode 100644 index 000000000..abf14dc5d --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/InsnFormatter.java @@ -0,0 +1,5 @@ +package jadx.plugins.input.dex.smali; + +interface InsnFormatter { + void format(InsnFormatterInfo insnFormatInfo); +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/InsnFormatterInfo.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/InsnFormatterInfo.java new file mode 100644 index 000000000..7961702b6 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/InsnFormatterInfo.java @@ -0,0 +1,49 @@ +package jadx.plugins.input.dex.smali; + +import java.util.Objects; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.IMethodData; +import jadx.api.plugins.input.insns.InsnData; + +public class InsnFormatterInfo { + private final SmaliCodeWriter codeWriter; + @Nullable + private IMethodData mth; + @Nullable + private InsnData insn; + + public InsnFormatterInfo(SmaliCodeWriter codeWriter, IMethodData mth) { + this.codeWriter = codeWriter; + this.mth = Objects.requireNonNull(mth); + } + + public InsnFormatterInfo(SmaliCodeWriter codeWriter, InsnData insn) { + this.codeWriter = codeWriter; + this.insn = Objects.requireNonNull(insn); + } + + public SmaliCodeWriter getCodeWriter() { + return codeWriter; + } + + public void setMth(IMethodData mth) { + this.mth = mth; + } + + public IMethodData getMth() { + return mth; + } + + public InsnData getInsn() { + if (insn == null) { + throw new NullPointerException("Instruction not set for formatter"); + } + return insn; + } + + public void setInsn(InsnData insn) { + this.insn = insn; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliCodeWriter.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliCodeWriter.java new file mode 100644 index 000000000..dd6a56838 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliCodeWriter.java @@ -0,0 +1,77 @@ +package jadx.plugins.input.dex.smali; + +import java.util.List; + +public class SmaliCodeWriter { + public static final String NL = System.getProperty("line.separator"); + public static final String INDENT_STR = " "; + + private final StringBuilder code = new StringBuilder(); + + private int indent; + private String indentStr = ""; + + public SmaliCodeWriter startLine(String line) { + startLine(); + code.append(line); + return this; + } + + public SmaliCodeWriter startLine() { + if (code.length() != 0) { + code.append(NL); + code.append(indentStr); + } + return this; + } + + public SmaliCodeWriter add(Object obj) { + code.append(obj); + return this; + } + + public SmaliCodeWriter add(int i) { + code.append(i); + return this; + } + + public SmaliCodeWriter add(char c) { + code.append(c); + return this; + } + + public SmaliCodeWriter add(String str) { + code.append(str); + return this; + } + + public SmaliCodeWriter addArgs(List argTypes) { + for (String type : argTypes) { + code.append(type); + } + return this; + } + + public void incIndent() { + this.indent++; + buildIndent(); + } + + public void decIndent() { + this.indent--; + buildIndent(); + } + + private void buildIndent() { + StringBuilder s = new StringBuilder(indent * INDENT_STR.length()); + for (int i = 0; i < indent; i++) { + s.append(INDENT_STR); + } + this.indentStr = s.toString(); + } + + public String getCode() { + return code.toString(); + } + +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliInsnFormat.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliInsnFormat.java new file mode 100644 index 000000000..11fe0c350 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliInsnFormat.java @@ -0,0 +1,155 @@ +package jadx.plugins.input.dex.smali; + +import java.util.HashMap; +import java.util.Map; + +import org.jetbrains.annotations.NotNull; + +import jadx.api.plugins.input.insns.InsnData; +import jadx.plugins.input.dex.insns.DexOpcodes; + +public class SmaliInsnFormat { + + private static SmaliInsnFormat instance; + + public static synchronized SmaliInsnFormat getInstance() { + SmaliInsnFormat instance = SmaliInsnFormat.instance; + if (instance == null) { + instance = new SmaliInsnFormat(); + SmaliInsnFormat.instance = instance; + } + return instance; + } + + private final Map formatters; + + public SmaliInsnFormat() { + formatters = registerFormatters(); + } + + private Map registerFormatters() { + Map map = new HashMap<>(); + map.put(DexOpcodes.NOP, fi -> fi.getCodeWriter().add("nop")); + map.put(DexOpcodes.SGET_OBJECT, staticFieldInsn("sget-object")); + map.put(DexOpcodes.SPUT_BOOLEAN, staticFieldInsn("sput-boolean")); + map.put(DexOpcodes.CONST, constInsn("const")); + map.put(DexOpcodes.CONST_HIGH16, constInsn("const/high16")); + map.put(DexOpcodes.CONST_STRING, stringInsn("const-string")); + map.put(DexOpcodes.INVOKE_VIRTUAL, invokeInsn("invoke-virtual")); + map.put(DexOpcodes.INVOKE_DIRECT, invokeInsn("invoke-direct")); + map.put(DexOpcodes.INVOKE_SUPER, invokeInsn("invoke-super")); + map.put(DexOpcodes.INVOKE_STATIC, invokeInsn("invoke-static")); + map.put(DexOpcodes.MOVE_RESULT, oneArgsInsn("move-result")); + map.put(DexOpcodes.RETURN_VOID, noArgsInsn("return-void")); + map.put(DexOpcodes.GOTO, gotoInsn("goto")); + map.put(DexOpcodes.GOTO_16, gotoInsn("goto-16")); + map.put(DexOpcodes.MOVE, simpleInsn("move")); + // TODO: complete list + return map; + } + + private InsnFormatter simpleInsn(String name) { + return fi -> { + SmaliCodeWriter code = fi.getCodeWriter(); + code.add(name); + InsnData insn = fi.getInsn(); + int regsCount = insn.getRegsCount(); + for (int i = 0; i < regsCount; i++) { + if (i == 0) { + code.add(' '); + } else { + code.add(", "); + } + code.add(regAt(fi, i)); + } + }; + } + + private InsnFormatter gotoInsn(String name) { + return fi -> fi.getCodeWriter().add(name).add(" :goto").add(Integer.toHexString(fi.getInsn().getTarget())); + } + + @NotNull + private InsnFormatter staticFieldInsn(String name) { + return fi -> fi.getCodeWriter().add(name).add(' ').add(regAt(fi, 0)).add(", ").add(field(fi)); + } + + @NotNull + private InsnFormatter constInsn(String name) { + return fi -> fi.getCodeWriter().add(name).add(' ').add(regAt(fi, 0)).add(", ").add(literal(fi)); + } + + @NotNull + private InsnFormatter stringInsn(String name) { + return fi -> fi.getCodeWriter().add(name).add(' ').add(regAt(fi, 0)).add(", ").add(str(fi)); + } + + @NotNull + private InsnFormatter invokeInsn(String name) { + return fi -> { + SmaliCodeWriter code = fi.getCodeWriter(); + code.add(name).add(' '); + regsList(code, fi.getInsn()); + code.add(", ").add(method(fi)); + }; + } + + private InsnFormatter oneArgsInsn(String name) { + return fi -> fi.getCodeWriter().add(name).add(' ').add(regAt(fi, 0)); + } + + private InsnFormatter noArgsInsn(String name) { + return (fi) -> fi.getCodeWriter().add(name); + } + + private String literal(InsnFormatterInfo fi) { + return "0x" + Long.toHexString(fi.getInsn().getLiteral()); + } + + private String str(InsnFormatterInfo fi) { + return "\"" + fi.getInsn().getIndexAsString() + "\""; + } + + private String field(InsnFormatterInfo fi) { + return fi.getInsn().getIndexAsField().toString(); + } + + private String method(InsnFormatterInfo fi) { + return fi.getInsn().getIndexAsMethod().toString(); + } + + private void regsList(SmaliCodeWriter code, InsnData insn) { + int argsCount = insn.getRegsCount(); + code.add('{'); + for (int i = 0; i < argsCount; i++) { + if (i != 0) { + code.add(", "); + } + code.add("v").add(insn.getReg(i)); + } + code.add('}'); + } + + private String regAt(InsnFormatterInfo fi, int argNum) { + return "v" + fi.getInsn().getReg(argNum); + } + + public void format(InsnFormatterInfo formatInfo) { + InsnData insn = formatInfo.getInsn(); + insn.decode(); + int rawOpcodeUnit = insn.getRawOpcodeUnit(); + int opcode = rawOpcodeUnit & 0xFF; + InsnFormatter insnFormatter = formatters.get(opcode); + if (insnFormatter != null) { + insnFormatter.format(formatInfo); + } else { + formatInfo.getCodeWriter().add("# ").add(insn.getOpcode()).add(" (?0x").add(Integer.toHexString(rawOpcodeUnit)).add(')'); + } + } + + public String format(InsnData insn) { + InsnFormatterInfo formatInfo = new InsnFormatterInfo(new SmaliCodeWriter(), insn); + format(formatInfo); + return formatInfo.getCodeWriter().getCode(); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliPrinter.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliPrinter.java new file mode 100644 index 000000000..3f10cfe35 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliPrinter.java @@ -0,0 +1,36 @@ +package jadx.plugins.input.dex.smali; + +import jadx.api.plugins.input.data.AccessFlags; +import jadx.api.plugins.input.data.ICodeReader; +import jadx.plugins.input.dex.sections.DexMethodData; + +import static jadx.api.plugins.input.data.AccessFlagsScope.METHOD; + +// TODO: not finished +public class SmaliPrinter { + + public static String printMethod(DexMethodData mth) { + SmaliCodeWriter codeWriter = new SmaliCodeWriter(); + codeWriter.startLine(".method "); + codeWriter.add(AccessFlags.format(mth.getAccessFlags(), METHOD)); + codeWriter.add(mth.getName()); + codeWriter.add('(').addArgs(mth.getArgTypes()).add(')'); + codeWriter.add(mth.getReturnType()); + codeWriter.incIndent(); + + ICodeReader codeReader = mth.getCodeReader(); + if (codeReader != null) { + codeWriter.startLine(".registers ").add(codeReader.getRegistersCount()); + SmaliInsnFormat insnFormat = SmaliInsnFormat.getInstance(); + InsnFormatterInfo formatterInfo = new InsnFormatterInfo(codeWriter, mth); + codeReader.visitInstructions(insn -> { + codeWriter.startLine(); + formatterInfo.setInsn(insn); + insnFormat.format(formatterInfo); + }); + codeWriter.decIndent(); + } + codeWriter.startLine(".end method"); + return codeWriter.getCode(); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Leb128.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Leb128.java new file mode 100644 index 000000000..538e0ab5f --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Leb128.java @@ -0,0 +1,45 @@ +package jadx.plugins.input.dex.utils; + +import jadx.plugins.input.dex.DexException; +import jadx.plugins.input.dex.sections.SectionReader; + +public final class Leb128 { + + public static int readSignedLeb128(SectionReader in) { + int result = 0; + int cur; + int count = 0; + int signBits = -1; + do { + cur = in.readUByte(); + result |= (cur & 0x7f) << (count * 7); + signBits <<= 7; + count++; + } while (((cur & 0x80) == 0x80) && count < 5); + + if ((cur & 0x80) == 0x80) { + throw new DexException("Invalid LEB128 sequence"); + } + // Sign extend if appropriate + if (((signBits >> 1) & result) != 0) { + result |= signBits; + } + return result; + } + + public static int readUnsignedLeb128(SectionReader in) { + int result = 0; + int cur; + int count = 0; + do { + cur = in.readUByte(); + result |= (cur & 0x7f) << (count * 7); + count++; + } while (((cur & 0x80) == 0x80) && count < 5); + + if ((cur & 0x80) == 0x80) { + throw new DexException("Invalid LEB128 sequence"); + } + return result; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/MUtf8.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/MUtf8.java new file mode 100644 index 000000000..e83b0a322 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/MUtf8.java @@ -0,0 +1,40 @@ +package jadx.plugins.input.dex.utils; + +import jadx.plugins.input.dex.DexException; +import jadx.plugins.input.dex.sections.SectionReader; + +public class MUtf8 { + + public static String decode(SectionReader in) { + int len = in.readUleb128(); + char[] out = new char[len]; + int k = 0; + while (true) { + char a = (char) (in.readUByte() & 0xff); + if (a == 0) { + return new String(out, 0, k); + } + out[k] = a; + if (a < '\u0080') { + k++; + } else if ((a & 0xE0) == 0xC0) { + int b = in.readUByte(); + if ((b & 0xC0) != 0x80) { + throw new DexException("Bad second byte"); + } + out[k] = (char) (((a & 0x1F) << 6) | (b & 0x3F)); + k++; + } else if ((a & 0xF0) == 0xE0) { + int b = in.readUByte(); + int c = in.readUByte(); + if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) { + throw new DexException("Bad second or third byte"); + } + out[k] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F)); + k++; + } else { + throw new DexException("Bad byte"); + } + } + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Utils.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Utils.java new file mode 100644 index 000000000..a0881a2c5 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Utils.java @@ -0,0 +1,23 @@ +package jadx.plugins.input.dex.utils; + +import java.util.Iterator; +import java.util.List; + +public class Utils { + + public static String listToStr(List collection) { + StringBuilder sb = new StringBuilder(); + Iterator it = collection.iterator(); + if (it.hasNext()) { + sb.append(it.next()); + } + while (it.hasNext()) { + sb.append(", ").append(it.next()); + } + return sb.toString(); + } + + public static String formatOffset(int offset) { + return String.format("0x%04x", offset); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin b/jadx-plugins/jadx-dex-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin new file mode 100644 index 000000000..a8b08d540 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin @@ -0,0 +1 @@ +jadx.plugins.input.dex.DexInputPlugin diff --git a/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/DexInputPluginTest.java b/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/DexInputPluginTest.java new file mode 100644 index 000000000..5896d910e --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/DexInputPluginTest.java @@ -0,0 +1,73 @@ +package jadx.plugins.input.dex; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +import jadx.api.plugins.input.data.AccessFlags; +import jadx.api.plugins.input.data.AccessFlagsScope; +import jadx.api.plugins.input.data.ICodeReader; +import jadx.api.plugins.input.data.ILoadResult; +import jadx.plugins.input.dex.utils.SmaliUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +class DexInputPluginTest { + + @Test + public void loadSampleApk() throws Exception { + processFile(Paths.get(ClassLoader.getSystemResource("samples/app-with-fake-dex.apk").toURI())); + } + + @Test + public void loadHelloWorld() throws Exception { + processFile(Paths.get(ClassLoader.getSystemResource("samples/hello.dex").toURI())); + } + + @Test + public void loadTestSmali() throws Exception { + processFile(SmaliUtils.compileSmaliFromResource("samples/test.smali")); + } + + private static void processFile(Path sample) throws IOException { + System.out.println("Input file: " + sample.toAbsolutePath()); + long start = System.currentTimeMillis(); + List files = Collections.singletonList(sample); + try (ILoadResult result = new DexInputPlugin().loadFiles(files)) { + AtomicInteger count = new AtomicInteger(); + result.visitClasses(cls -> { + System.out.println(); + System.out.println("Class: " + cls.getType()); + System.out.println("AccessFlags: " + AccessFlags.format(cls.getAccessFlags(), AccessFlagsScope.CLASS)); + System.out.println("SuperType: " + cls.getSuperType()); + System.out.println("Interfaces: " + cls.getInterfacesTypes()); + System.out.println("SourceFile: " + cls.getSourceFile()); + count.getAndIncrement(); + + cls.visitFieldsAndMethods( + System.out::println, + mth -> { + System.out.println("---"); + System.out.println(mth); + ICodeReader codeReader = mth.getCodeReader(); + if (codeReader != null) { + codeReader.visitInstructions(insn -> { + insn.decode(); + System.out.println(insn); + }); + } + System.out.println("---"); + System.out.println(mth.disassembleMethod()); + System.out.println("---"); + }); + }); + assertThat(count.get()).isGreaterThan(0); + } + System.out.println("Time: " + (System.currentTimeMillis() - start) + "ms"); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/utils/SmaliUtils.java b/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/utils/SmaliUtils.java new file mode 100644 index 000000000..cc784d89d --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/utils/SmaliUtils.java @@ -0,0 +1,47 @@ +package jadx.plugins.input.dex.utils; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.jf.smali.Smali; +import org.jf.smali.SmaliOptions; + +public class SmaliUtils { + + public static Path compileSmaliFromResource(String res) { + try { + Path input = Paths.get(ClassLoader.getSystemResource(res).toURI()); + return compileSmali(input); + } catch (Exception e) { + throw new AssertionError("Smali assemble error", e); + } + } + + public static Path compileSmali(Path input) { + try { + Path tempFile = Files.createTempFile("jadx", "smali.dex"); + compileSmali(tempFile, Collections.singletonList(input)); + return tempFile; + } catch (Exception e) { + throw new AssertionError("Smali assemble error", e); + } + } + + private static void compileSmali(Path output, List inputFiles) { + try { + SmaliOptions options = new SmaliOptions(); + options.outputDexFile = output.toAbsolutePath().toString(); + List inputFileNames = inputFiles.stream() + .map(Path::toAbsolutePath) + .map(Path::toString) + .collect(Collectors.toList()); + Smali.assemble(options, inputFileNames); + } catch (Exception e) { + throw new AssertionError("Smali assemble error", e); + } + } +} diff --git a/jadx-plugins/jadx-dex-input/src/test/resources/samples/app-with-fake-dex.apk b/jadx-plugins/jadx-dex-input/src/test/resources/samples/app-with-fake-dex.apk new file mode 100644 index 000000000..0a6969ad6 Binary files /dev/null and b/jadx-plugins/jadx-dex-input/src/test/resources/samples/app-with-fake-dex.apk differ diff --git a/jadx-plugins/jadx-dex-input/src/test/resources/samples/hello.dex b/jadx-plugins/jadx-dex-input/src/test/resources/samples/hello.dex new file mode 100644 index 000000000..226651de1 Binary files /dev/null and b/jadx-plugins/jadx-dex-input/src/test/resources/samples/hello.dex differ diff --git a/jadx-plugins/jadx-dex-input/src/test/resources/samples/test.smali b/jadx-plugins/jadx-dex-input/src/test/resources/samples/test.smali new file mode 100644 index 000000000..b19a35b80 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/test/resources/samples/test.smali @@ -0,0 +1,16 @@ +.class LHello; +.super Ljava/lang/Object; +.source "test.java" + + +.method public main([Ljava/lang/String;)V + .registers 2 + + .line 3 + nop + + move v0, v1 + + .line 4 + return-void +.end method diff --git a/jadx-plugins/jadx-java-convert/build.gradle b/jadx-plugins/jadx-java-convert/build.gradle new file mode 100644 index 000000000..69b8081dc --- /dev/null +++ b/jadx-plugins/jadx-java-convert/build.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java-library' +} + +dependencies { + api(project(":jadx-plugins:jadx-plugins-api")) + + implementation(project(":jadx-plugins:jadx-dex-input")) + implementation('com.android.tools:r8:1.6.84') + implementation(files('lib/dx-1.16.jar')) +} diff --git a/jadx-core/lib/dx-1.16.jar b/jadx-plugins/jadx-java-convert/lib/dx-1.16.jar similarity index 100% rename from jadx-core/lib/dx-1.16.jar rename to jadx-plugins/jadx-java-convert/lib/dx-1.16.jar diff --git a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/ConvertResult.java b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/ConvertResult.java new file mode 100644 index 000000000..c050782d8 --- /dev/null +++ b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/ConvertResult.java @@ -0,0 +1,67 @@ +package jadx.plugins.input.javaconvert; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ConvertResult { + private static final Logger LOG = LoggerFactory.getLogger(ConvertResult.class); + + private final List converted = new ArrayList<>(); + private final List tmpPaths = new ArrayList<>(); + + public List getConverted() { + return converted; + } + + public void addConvertedFiles(List paths) { + converted.addAll(paths); + } + + public void addTempPath(Path path) { + tmpPaths.add(path); + } + + public boolean isEmpty() { + return converted.isEmpty(); + } + + public void deleteTemp() { + for (Path tmpPath : tmpPaths) { + try { + delete(tmpPath); + } catch (Exception e) { + LOG.warn("Failed to delete temp path: {}", tmpPath, e); + } + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private static void delete(Path path) throws IOException { + if (Files.isRegularFile(path)) { + Files.delete(path); + return; + } + if (Files.isDirectory(path)) { + try (Stream pathStream = Files.walk(path)) { + pathStream + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + } + + @Override + public String toString() { + return "ConvertResult{converted=" + converted + ", tmpPaths=" + tmpPaths + '}'; + } +} diff --git a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/D8Converter.java b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/D8Converter.java new file mode 100644 index 000000000..6ec019c5b --- /dev/null +++ b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/D8Converter.java @@ -0,0 +1,53 @@ +package jadx.plugins.input.javaconvert; + +import java.nio.file.Path; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.CompilationMode; +import com.android.tools.r8.D8; +import com.android.tools.r8.D8Command; +import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.OutputMode; + +public class D8Converter { + private static final Logger LOG = LoggerFactory.getLogger(D8Converter.class); + + public static void run(Path path, Path tempDirectory) throws CompilationFailedException { + D8Command d8Command = D8Command.builder(new LogHandler()) + .addProgramFiles(path) + .setOutput(tempDirectory, OutputMode.DexIndexed) + .setMode(CompilationMode.DEBUG) + .setMinApiLevel(30) + .setIntermediate(true) + .setEnableDesugaring(false) + .build(); + D8.run(d8Command); + } + + private static class LogHandler implements DiagnosticsHandler { + @Override + public void error(Diagnostic diagnostic) { + LOG.error("D8 error: {}", format(diagnostic)); + } + + @Override + public void warning(Diagnostic diagnostic) { + LOG.warn("D8 warning: {}", format(diagnostic)); + } + + @Override + public void info(Diagnostic diagnostic) { + LOG.info("D8 info: {}", format(diagnostic)); + } + + public static String format(Diagnostic diagnostic) { + return diagnostic.getDiagnosticMessage() + + ", origin: " + diagnostic.getOrigin() + + ", position: " + diagnostic.getPosition(); + } + } +} diff --git a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/DxConverter.java b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/DxConverter.java new file mode 100644 index 000000000..8a9be16d9 --- /dev/null +++ b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/DxConverter.java @@ -0,0 +1,49 @@ +package jadx.plugins.input.javaconvert; + +import java.io.ByteArrayOutputStream; +import java.nio.file.Path; + +import com.android.dx.command.dexer.DxContext; +import com.android.dx.command.dexer.Main; + +public class DxConverter { + private static final String CHARSET_NAME = "UTF-8"; + + private static class DxArgs extends com.android.dx.command.dexer.Main.Arguments { + public DxArgs(DxContext context, String dexDir, String[] input) { + super(context); + outName = dexDir; + fileNames = input; + jarOutput = false; + multiDex = true; + + optimize = true; + localInfo = true; + coreLibrary = true; + + debug = true; + warnings = true; + minSdkVersion = 28; + } + } + + public static void run(Path path, Path tempDirectory) { + int result; + String dxErrors; + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream errOut = new ByteArrayOutputStream()) { + DxContext context = new DxContext(out, errOut); + DxArgs args = new DxArgs( + context, + tempDirectory.toAbsolutePath().toString(), + new String[] { path.toAbsolutePath().toString() }); + result = new Main(context).runDx(args); + dxErrors = errOut.toString(CHARSET_NAME); + } catch (Exception e) { + throw new RuntimeException("dx exception: " + e.getMessage(), e); + } + if (result != 0) { + throw new RuntimeException("Java to dex conversion error, code: " + result + "\n errors: " + dxErrors); + } + } +} diff --git a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java new file mode 100644 index 000000000..0db886ea7 --- /dev/null +++ b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java @@ -0,0 +1,52 @@ +package jadx.plugins.input.javaconvert; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JavaConvertLoader { + private static final Logger LOG = LoggerFactory.getLogger(JavaConvertLoader.class); + + public static ConvertResult process(List input) { + ConvertResult result = new ConvertResult(); + for (Path path : input) { + if (isJavaFile(path)) { + try { + convert(result, path); + } catch (Exception e) { + LOG.error("Failed to convert file: " + path.toAbsolutePath(), e); + } + } + } + return result; + } + + private static boolean isJavaFile(Path path) { + String fileName = path.getFileName().toString(); + return fileName.endsWith(".jar") + || fileName.endsWith(".class"); + } + + private static void convert(ConvertResult result, Path path) throws Exception { + Path tempDirectory = Files.createTempDirectory("jadx-"); + result.addTempPath(tempDirectory); + + // D8Converter.run(path, tempDirectory); + DxConverter.run(path, tempDirectory); + + LOG.debug("Converted to dex: {}", path.toAbsolutePath()); + result.addConvertedFiles(collectFilesInDir(tempDirectory)); + } + + private static List collectFilesInDir(Path tempDirectory) throws IOException { + try (Stream pathStream = Files.walk(tempDirectory)) { + return pathStream.collect(Collectors.toList()); + } + } +} diff --git a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertPlugin.java b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertPlugin.java new file mode 100644 index 000000000..8227a11ad --- /dev/null +++ b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertPlugin.java @@ -0,0 +1,38 @@ +package jadx.plugins.input.javaconvert; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import jadx.api.plugins.JadxPluginInfo; +import jadx.api.plugins.input.JadxInputPlugin; +import jadx.api.plugins.input.data.ILoadResult; +import jadx.api.plugins.input.data.impl.EmptyLoadResult; +import jadx.plugins.input.dex.DexFileLoader; +import jadx.plugins.input.dex.DexLoadResult; +import jadx.plugins.input.dex.DexReader; + +public class JavaConvertPlugin implements JadxInputPlugin { + + @Override + public JadxPluginInfo getPluginInfo() { + return new JadxPluginInfo("java-convert", "JavaConvert", "Convert .jar and .class files to dex"); + } + + @Override + public ILoadResult loadFiles(List input) { + ConvertResult result = JavaConvertLoader.process(input); + if (result.isEmpty()) { + result.deleteTemp(); + return EmptyLoadResult.INSTANCE; + } + List dexReaders = DexFileLoader.collectDexFiles(result.getConverted()); + return new DexLoadResult(dexReaders) { + @Override + public void close() throws IOException { + super.close(); + result.deleteTemp(); + } + }; + } +} diff --git a/jadx-plugins/jadx-java-convert/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin b/jadx-plugins/jadx-java-convert/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin new file mode 100644 index 000000000..df9405ff5 --- /dev/null +++ b/jadx-plugins/jadx-java-convert/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin @@ -0,0 +1 @@ +jadx.plugins.input.javaconvert.JavaConvertPlugin diff --git a/jadx-plugins/jadx-plugins-api/build.gradle b/jadx-plugins/jadx-plugins-api/build.gradle new file mode 100644 index 000000000..97734ac60 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/build.gradle @@ -0,0 +1,3 @@ +plugins { + id 'java-library' +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPlugin.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPlugin.java new file mode 100644 index 000000000..51337098b --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPlugin.java @@ -0,0 +1,5 @@ +package jadx.api.plugins; + +public interface JadxPlugin { + JadxPluginInfo getPluginInfo(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginInfo.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginInfo.java new file mode 100644 index 000000000..e9670f72b --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginInfo.java @@ -0,0 +1,30 @@ +package jadx.api.plugins; + +public class JadxPluginInfo { + private final String pluginId; + private final String name; + private final String description; + + public JadxPluginInfo(String id, String name, String description) { + this.pluginId = id; + this.name = name; + this.description = description; + } + + public String getPluginId() { + return pluginId; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + @Override + public String toString() { + return name + " - '" + description + '\''; + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginManager.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginManager.java new file mode 100644 index 000000000..ba2f7bcab --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginManager.java @@ -0,0 +1,44 @@ +package jadx.api.plugins; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.plugins.input.JadxInputPlugin; + +public class JadxPluginManager { + private static final Logger LOG = LoggerFactory.getLogger(JadxPluginManager.class); + + private final Map, JadxPlugin> allPlugins = new HashMap<>(); + + public JadxPluginManager() { + ServiceLoader jadxPlugins = ServiceLoader.load(JadxPlugin.class); + for (JadxPlugin jadxPlugin : jadxPlugins) { + register(jadxPlugin); + } + } + + public void register(JadxPlugin plugin) { + Objects.requireNonNull(plugin); + LOG.debug("Loaded plugin: {}", plugin.getPluginInfo().getName()); + allPlugins.put(plugin.getClass(), plugin); + } + + public List getAllPlugins() { + return new ArrayList<>(allPlugins.values()); + } + + public List getInputPlugins() { + return allPlugins.values().stream() + .filter(JadxInputPlugin.class::isInstance) + .map(JadxInputPlugin.class::cast) + .collect(Collectors.toList()); + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/JadxInputPlugin.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/JadxInputPlugin.java new file mode 100644 index 000000000..6f07f451f --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/JadxInputPlugin.java @@ -0,0 +1,11 @@ +package jadx.api.plugins.input; + +import java.nio.file.Path; +import java.util.List; + +import jadx.api.plugins.JadxPlugin; +import jadx.api.plugins.input.data.ILoadResult; + +public interface JadxInputPlugin extends JadxPlugin { + ILoadResult loadFiles(List input); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlags.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlags.java new file mode 100644 index 000000000..9f01c2c2b --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlags.java @@ -0,0 +1,91 @@ +package jadx.api.plugins.input.data; + +public class AccessFlags { + public static final int PUBLIC = 0x1; + public static final int PRIVATE = 0x2; + public static final int PROTECTED = 0x4; + public static final int STATIC = 0x8; + public static final int FINAL = 0x10; + public static final int SYNCHRONIZED = 0x20; + public static final int SUPER = 0x20; + public static final int VOLATILE = 0x40; + public static final int BRIDGE = 0x40; + public static final int TRANSIENT = 0x80; + public static final int VARARGS = 0x80; + public static final int NATIVE = 0x100; + public static final int INTERFACE = 0x200; + public static final int ABSTRACT = 0x400; + public static final int STRICT = 0x800; + public static final int SYNTHETIC = 0x1000; + public static final int ANNOTATION = 0x2000; + public static final int ENUM = 0x4000; + public static final int CONSTRUCTOR = 0x10000; + public static final int DECLARED_SYNCHRONIZED = 0x20000; + + public static boolean hasFlag(int flags, int flagValue) { + return (flags & flagValue) != 0; + } + + public static String format(int flags, AccessFlagsScope scope) { + StringBuilder code = new StringBuilder(); + if (hasFlag(flags, PUBLIC)) { + code.append("public "); + } + if (hasFlag(flags, PRIVATE)) { + code.append("private "); + } + if (hasFlag(flags, PROTECTED)) { + code.append("protected "); + } + if (hasFlag(flags, STATIC)) { + code.append("static "); + } + if (hasFlag(flags, FINAL)) { + code.append("final "); + } + if (hasFlag(flags, ABSTRACT)) { + code.append("abstract "); + } + if (hasFlag(flags, NATIVE)) { + code.append("native "); + } + switch (scope) { + case METHOD: + if (hasFlag(flags, SYNCHRONIZED)) { + code.append("synchronized "); + } + if (hasFlag(flags, BRIDGE)) { + code.append("bridge "); + } + if (hasFlag(flags, VARARGS)) { + code.append("varargs "); + } + break; + + case FIELD: + if (hasFlag(flags, VOLATILE)) { + code.append("volatile "); + } + if (hasFlag(flags, TRANSIENT)) { + code.append("transient "); + } + break; + + case CLASS: + if (hasFlag(flags, STRICT)) { + code.append("strict "); + } + if (hasFlag(flags, SUPER)) { + code.append("super "); + } + if (hasFlag(flags, ENUM)) { + code.append("enum "); + } + break; + } + if (hasFlag(flags, SYNTHETIC)) { + code.append("synthetic "); + } + return code.toString(); + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlagsScope.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlagsScope.java new file mode 100644 index 000000000..6a95fda8e --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlagsScope.java @@ -0,0 +1,5 @@ +package jadx.api.plugins.input.data; + +public enum AccessFlagsScope { + CLASS, FIELD, METHOD +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICatch.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICatch.java new file mode 100644 index 000000000..c4fb65851 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICatch.java @@ -0,0 +1,9 @@ +package jadx.api.plugins.input.data; + +public interface ICatch { + String[] getTypes(); + + int[] getAddresses(); + + int getCatchAllAddress(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IClassData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IClassData.java new file mode 100644 index 000000000..f35757fa0 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IClassData.java @@ -0,0 +1,35 @@ +package jadx.api.plugins.input.data; + +import java.nio.file.Path; +import java.util.List; +import java.util.function.Consumer; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.data.annotations.IAnnotation; + +public interface IClassData { + + String getType(); + + int getAccessFlags(); + + @Nullable + String getSuperType(); + + List getInterfacesTypes(); + + String getSourceFile(); + + Path getInputPath(); + + void visitFieldsAndMethods(Consumer fieldsConsumer, Consumer mthConsumer); + + List getStaticFieldInitValues(); + + List getAnnotations(); + + // TODO: make api methods to get dissembled code + int getClassDefOffset(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICodeReader.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICodeReader.java new file mode 100644 index 000000000..bf300b569 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICodeReader.java @@ -0,0 +1,25 @@ +package jadx.api.plugins.input.data; + +import java.util.List; +import java.util.function.Consumer; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.insns.InsnData; + +public interface ICodeReader { + ICodeReader copy(); + + void visitInstructions(Consumer insnConsumer); + + int getRegistersCount(); + + int getInsnsCount(); + + @Nullable + IDebugInfo getDebugInfo(); + + int getCodeOffset(); + + List getTries(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IDebugInfo.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IDebugInfo.java new file mode 100644 index 000000000..78aea095a --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IDebugInfo.java @@ -0,0 +1,14 @@ +package jadx.api.plugins.input.data; + +import java.util.List; +import java.util.Map; + +public interface IDebugInfo { + + /** + * Map instruction offset to source line number + */ + Map getSourceLineMapping(); + + List getLocalVars(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IFieldData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IFieldData.java new file mode 100644 index 000000000..99c59d74d --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IFieldData.java @@ -0,0 +1,17 @@ +package jadx.api.plugins.input.data; + +import java.util.List; + +import jadx.api.plugins.input.data.annotations.IAnnotation; + +public interface IFieldData { + String getParentClassType(); + + String getType(); + + String getName(); + + int getAccessFlags(); + + List getAnnotations(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ILoadResult.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ILoadResult.java new file mode 100644 index 000000000..932afa7c5 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ILoadResult.java @@ -0,0 +1,10 @@ +package jadx.api.plugins.input.data; + +import java.io.Closeable; +import java.util.function.Consumer; + +public interface ILoadResult extends Closeable { + void visitClasses(Consumer consumer); + + void visitResources(Consumer consumer); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ILocalVar.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ILocalVar.java new file mode 100644 index 000000000..24a52b283 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ILocalVar.java @@ -0,0 +1,18 @@ +package jadx.api.plugins.input.data; + +import org.jetbrains.annotations.Nullable; + +public interface ILocalVar { + String getName(); + + int getRegNum(); + + String getType(); + + @Nullable + String getSignature(); + + int getStartOffset(); + + int getEndOffset(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodData.java new file mode 100644 index 000000000..d942f6147 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodData.java @@ -0,0 +1,30 @@ +package jadx.api.plugins.input.data; + +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.annotations.IAnnotation; + +public interface IMethodData { + String getParentClassType(); + + String getName(); + + int getAccessFlags(); + + String getReturnType(); + + List getArgTypes(); + + boolean isDirect(); + + @Nullable + ICodeReader getCodeReader(); + + String disassembleMethod(); + + List getAnnotations(); + + List> getParamsAnnotations(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IResourceData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IResourceData.java new file mode 100644 index 000000000..2fd81769f --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IResourceData.java @@ -0,0 +1,4 @@ +package jadx.api.plugins.input.data; + +public interface IResourceData { +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ITry.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ITry.java new file mode 100644 index 000000000..85f54ff42 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ITry.java @@ -0,0 +1,9 @@ +package jadx.api.plugins.input.data; + +public interface ITry { + ICatch getCatch(); + + int getStartAddress(); + + int getInstructionCount(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/AnnotationVisibility.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/AnnotationVisibility.java new file mode 100644 index 000000000..95d96e9cb --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/AnnotationVisibility.java @@ -0,0 +1,7 @@ +package jadx.api.plugins.input.data.annotations; + +public enum AnnotationVisibility { + BUILD, + RUNTIME, + SYSTEM +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedType.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedType.java new file mode 100644 index 000000000..0a773a682 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedType.java @@ -0,0 +1,20 @@ +package jadx.api.plugins.input.data.annotations; + +public enum EncodedType { + ENCODED_NULL, + ENCODED_BOOLEAN, + ENCODED_BYTE, + ENCODED_SHORT, + ENCODED_CHAR, + ENCODED_INT, + ENCODED_LONG, + ENCODED_FLOAT, + ENCODED_DOUBLE, + ENCODED_STRING, + ENCODED_TYPE, + ENCODED_ENUM, + ENCODED_FIELD, + ENCODED_METHOD, + ENCODED_ARRAY, + ENCODED_ANNOTATION +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedValue.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedValue.java new file mode 100644 index 000000000..78b4188d4 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedValue.java @@ -0,0 +1,33 @@ +package jadx.api.plugins.input.data.annotations; + +public class EncodedValue { + public static final EncodedValue NULL = new EncodedValue(EncodedType.ENCODED_NULL, new Object()); + + private final EncodedType type; + private final Object value; + + public EncodedValue(EncodedType type, Object value) { + this.type = type; + this.value = value; + } + + public EncodedType getType() { + return type; + } + + public Object getValue() { + return value; + } + + @Override + public String toString() { + switch (type) { + case ENCODED_STRING: + return (String) value; + case ENCODED_ARRAY: + return "[" + value + "]"; + default: + return "{" + type + ": " + value + '}'; + } + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/IAnnotation.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/IAnnotation.java new file mode 100644 index 000000000..5d0a702d4 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/IAnnotation.java @@ -0,0 +1,18 @@ +package jadx.api.plugins.input.data.annotations; + +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +public interface IAnnotation { + String getAnnotationClass(); + + AnnotationVisibility getVisibility(); + + Map getValues(); + + @Nullable + default EncodedValue getDefaultValue() { + return getValues().get("value"); + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/EmptyLoadResult.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/EmptyLoadResult.java new file mode 100644 index 000000000..94fb0e74f --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/EmptyLoadResult.java @@ -0,0 +1,25 @@ +package jadx.api.plugins.input.data.impl; + +import java.io.IOException; +import java.util.function.Consumer; + +import jadx.api.plugins.input.data.IClassData; +import jadx.api.plugins.input.data.ILoadResult; +import jadx.api.plugins.input.data.IResourceData; + +public class EmptyLoadResult implements ILoadResult { + + public static final EmptyLoadResult INSTANCE = new EmptyLoadResult(); + + @Override + public void visitClasses(Consumer consumer) { + } + + @Override + public void visitResources(Consumer consumer) { + } + + @Override + public void close() throws IOException { + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnData.java new file mode 100644 index 000000000..dad3ae8f6 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnData.java @@ -0,0 +1,41 @@ +package jadx.api.plugins.input.insns; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.IMethodData; +import jadx.api.plugins.input.insns.custom.ICustomPayload; + +public interface InsnData { + + void decode(); + + int getOffset(); + + Opcode getOpcode(); + + InsnIndexType getIndexType(); + + int getRawOpcodeUnit(); + + int getRegsCount(); + + int getReg(int argNum); + + long getLiteral(); + + int getTarget(); + + int getIndex(); + + String getIndexAsString(); + + String getIndexAsType(); + + IFieldData getIndexAsField(); + + IMethodData getIndexAsMethod(); + + @Nullable + ICustomPayload getPayload(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnIndexType.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnIndexType.java new file mode 100644 index 000000000..1153c796b --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnIndexType.java @@ -0,0 +1,9 @@ +package jadx.api.plugins.input.insns; + +public enum InsnIndexType { + NONE, + TYPE_REF, + STRING_REF, + FIELD_REF, + METHOD_REF +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/Opcode.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/Opcode.java new file mode 100644 index 000000000..003c27310 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/Opcode.java @@ -0,0 +1,193 @@ +package jadx.api.plugins.input.insns; + +public enum Opcode { + UNKNOWN, + NOP, + + ADD_DOUBLE, + ADD_FLOAT, + ADD_INT, + ADD_INT_LIT, + ADD_LONG, + + AND_INT, + AND_INT_LIT, + AND_LONG, + + AGET, + AGET_BOOLEAN, + AGET_BYTE, + AGET_CHAR, + AGET_OBJECT, + AGET_SHORT, + AGET_WIDE, + + APUT, + APUT_BOOLEAN, + APUT_BYTE, + APUT_CHAR, + APUT_OBJECT, + APUT_SHORT, + APUT_WIDE, + + ARITH, + ARRAY_LENGTH, + + CAST, + CHECK_CAST, + + CMPG_DOUBLE, + CMPG_FLOAT, + CMPL_DOUBLE, + CMPL_FLOAT, + CMP_G, + CMP_L, + CMP_LONG, + + CONST, + CONST_CLASS, + CONST_STRING, + CONST_WIDE, + + DIV_DOUBLE, + DIV_FLOAT, + DIV_INT, + DIV_INT_LIT, + DIV_LONG, + + DOUBLE_TO_FLOAT, + DOUBLE_TO_INT, + DOUBLE_TO_LONG, + + FLOAT_TO_DOUBLE, + FLOAT_TO_INT, + FLOAT_TO_LONG, + + GOTO, + IF, + IF_EQ, + IF_EQZ, + IF_GE, + IF_GEZ, + IF_GT, + IF_GTZ, + IF_LE, + IF_LEZ, + IF_LT, + IF_LTZ, + IF_NE, + IF_NEZ, + + INSTANCE_OF, + + INT_TO_BYTE, + INT_TO_CHAR, + INT_TO_DOUBLE, + INT_TO_FLOAT, + INT_TO_LONG, + INT_TO_SHORT, + + INVOKE_DIRECT, + INVOKE_DIRECT_RANGE, + INVOKE_INTERFACE, + INVOKE_INTERFACE_RANGE, + INVOKE_STATIC, + INVOKE_STATIC_RANGE, + INVOKE_SUPER, + INVOKE_SUPER_RANGE, + INVOKE_VIRTUAL, + INVOKE_VIRTUAL_RANGE, + + IGET, + IPUT, + + SGET, + SPUT, + + LONG_TO_DOUBLE, + LONG_TO_FLOAT, + LONG_TO_INT, + + MONITOR_ENTER, + MONITOR_EXIT, + + MOVE, + MOVE_EXCEPTION, + MOVE_OBJECT, + MOVE_RESULT, + MOVE_WIDE, + + MUL_DOUBLE, + MUL_FLOAT, + MUL_INT, + MUL_INT_LIT, + MUL_LONG, + + NEG, + NEG_DOUBLE, + NEG_FLOAT, + NEG_INT, + NEG_LONG, + NEW_INSTANCE, + + NOT, + NOT_INT, + NOT_LONG, + + OR_INT, + OR_INT_LIT, + OR_LONG, + + REM_DOUBLE, + REM_FLOAT, + REM_INT, + REM_INT_LIT, + REM_LONG, + + RETURN, + RETURN_VOID, + + RSUB_INT, + SHL_INT, + SHL_INT_LIT, + SHL_LONG, + SHR_INT, + SHR_INT_LIT, + SHR_LONG, + SUB_DOUBLE, + SUB_FLOAT, + SUB_INT, + SUB_LONG, + + THROW, + + USHR_INT, + USHR_INT_LIT, + USHR_LONG, + XOR_INT, + XOR_INT_LIT, + XOR_LONG, + + NEW_ARRAY, + + FILLED_NEW_ARRAY, + FILLED_NEW_ARRAY_RANGE, + FILL_ARRAY, + FILL_ARRAY_DATA, + FILL_ARRAY_DATA_PAYLOAD, + + SWITCH, + PACKED_SWITCH, + PACKED_SWITCH_PAYLOAD, + SPARSE_SWITCH, + SPARSE_SWITCH_PAYLOAD, + + INVOKE_POLYMORPHIC, + INVOKE_POLYMORPHIC_RANGE, + + INVOKE_CUSTOM, + INVOKE_CUSTOM_RANGE, + + CONST_METHOD_HANDLE, + CONST_METHOD_TYPE, +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/IArrayPayload.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/IArrayPayload.java new file mode 100644 index 000000000..c4d66bd1d --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/IArrayPayload.java @@ -0,0 +1,9 @@ +package jadx.api.plugins.input.insns.custom; + +public interface IArrayPayload extends ICustomPayload { + int getSize(); + + int getElementSize(); + + Object getData(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/ICustomPayload.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/ICustomPayload.java new file mode 100644 index 000000000..2306a6ff2 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/ICustomPayload.java @@ -0,0 +1,4 @@ +package jadx.api.plugins.input.insns.custom; + +public interface ICustomPayload { +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/ISwitchPayload.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/ISwitchPayload.java new file mode 100644 index 000000000..56513855a --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/ISwitchPayload.java @@ -0,0 +1,9 @@ +package jadx.api.plugins.input.insns.custom; + +public interface ISwitchPayload extends ICustomPayload { + int getSize(); + + int[] getKeys(); + + int[] getTargets(); +} diff --git a/settings.gradle b/settings.gradle index cde32274a..17e164d65 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,3 +4,7 @@ include 'jadx-core' include 'jadx-cli' include 'jadx-gui' include 'jadx-samples' +include 'jadx-plugins' +include 'jadx-plugins:jadx-plugins-api' +include 'jadx-plugins:jadx-dex-input' +include 'jadx-plugins:jadx-java-convert'