mirror of
https://github.com/skylot/jadx.git
synced 2024-10-07 01:53:34 +00:00
refactor: use own dex parser instead deprecated dx lib
This commit is contained in:
parent
09e267f8bc
commit
0d69e0ac97
1
.gitignore
vendored
1
.gitignore
vendored
@ -27,7 +27,6 @@ jadx-output/
|
||||
*-tmp/
|
||||
**/tmp/
|
||||
|
||||
*.dex
|
||||
*.class
|
||||
*.dump
|
||||
*.log
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
* <p>
|
||||
@ -56,11 +62,12 @@ import jadx.core.xmlgen.ResourcesSaver;
|
||||
* </code>
|
||||
* </pre>
|
||||
*/
|
||||
public final class JadxDecompiler {
|
||||
public final class JadxDecompiler implements Closeable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
||||
|
||||
private JadxArgs args;
|
||||
private List<InputFile> inputFiles;
|
||||
private JadxPluginManager pluginManager = new JadxPluginManager();
|
||||
private List<ILoadResult> loadedInputs = new ArrayList<>();
|
||||
|
||||
private RootNode root;
|
||||
private List<JavaClass> classes;
|
||||
@ -68,9 +75,9 @@ public final class JadxDecompiler {
|
||||
|
||||
private BinaryXMLParser xmlParser;
|
||||
|
||||
private Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
|
||||
private Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
||||
private Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
||||
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
|
||||
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
||||
private final Map<FieldNode, JavaField> 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<Path> 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<InputFile> loadFiles(List<File> files) {
|
||||
if (files.isEmpty()) {
|
||||
throw new JadxRuntimeException("Empty file list");
|
||||
}
|
||||
List<InputFile> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -48,12 +48,12 @@ public final class JavaMethod implements JavaNode {
|
||||
}
|
||||
List<ArgType> 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() {
|
||||
|
@ -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<ResourceFile> load(List<InputFile> inputFiles) {
|
||||
List<ResourceFile> load() {
|
||||
List<File> inputFiles = jadxRef.getArgs().getInputFiles();
|
||||
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
|
||||
for (InputFile file : inputFiles) {
|
||||
loadFile(list, file.getFile());
|
||||
for (File file : inputFiles) {
|
||||
loadFile(list, file);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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<IDexTreeVisitor> getFallbackPassesList() {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>(3);
|
||||
passes.add(new AttachTryCatchVisitor());
|
||||
passes.add(new ProcessInstructionsVisitor());
|
||||
passes.add(new FallbackModeVisitor());
|
||||
return passes;
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
||||
List<IDexTreeVisitor> 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<IDexTreeVisitor> 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;
|
||||
}
|
||||
|
||||
|
@ -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("<output .jcst or .jar file> <several input dex or jar files> ");
|
||||
}
|
||||
|
||||
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<Path> inputPaths = Stream.of(args).map(s -> Paths.get(s)).collect(Collectors.toList());
|
||||
Path output = inputPaths.remove(0);
|
||||
|
||||
List<InputFile> 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<ILoadResult> 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<InputFile> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<String, Object> vl = a.getValues();
|
||||
Map<String, EncodedValue> vl = a.getValues();
|
||||
if (!vl.isEmpty()) {
|
||||
code.add('(');
|
||||
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext();) {
|
||||
Entry<String, Object> e = it.next();
|
||||
for (Iterator<Entry<String, EncodedValue>> it = vl.entrySet().iterator(); it.hasNext();) {
|
||||
Entry<String, EncodedValue> 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();
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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<LiteralArg> 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);
|
||||
|
@ -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) {
|
||||
|
@ -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("*/");
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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<DexNode> dexNodes;
|
||||
private final RootNode root;
|
||||
private final DeobfPresets deobfPresets;
|
||||
|
||||
private final Map<ClassInfo, DeobfClsInfo> clsMap = new LinkedHashMap<>();
|
||||
@ -67,9 +65,9 @@ public class Deobfuscator {
|
||||
private int fldIndex = 0;
|
||||
private int mthIndex = 0;
|
||||
|
||||
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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() {
|
@ -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 extends IAttribute> T get(AType<T> type);
|
||||
|
||||
Annotation getAnnotation(String cls);
|
||||
IAnnotation getAnnotation(String cls);
|
||||
|
||||
<T> List<T> getAll(AType<AttrList<T>> type);
|
||||
|
||||
|
@ -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<String, Object> values;
|
||||
|
||||
public Annotation(Visibility visibility, ArgType type, Map<String, Object> 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<String, Object> getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
public Object getDefaultValue() {
|
||||
return values.get("value");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Annotation[" + visibility + ", " + atype + ", " + values + ']';
|
||||
}
|
||||
}
|
@ -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<String, Annotation> map;
|
||||
|
||||
public AnnotationsList(List<Annotation> anList) {
|
||||
map = new HashMap<>(anList.size());
|
||||
for (Annotation a : anList) {
|
||||
map.put(a.getAnnotationClass(), a);
|
||||
public static void attach(ICodeNode node, List<IAnnotation> annotationList) {
|
||||
AnnotationsList attrList = pack(annotationList);
|
||||
if (attrList != null) {
|
||||
node.addAttr(attrList);
|
||||
}
|
||||
}
|
||||
|
||||
public Annotation get(String className) {
|
||||
@Nullable
|
||||
public static AnnotationsList pack(List<IAnnotation> annotationList) {
|
||||
if (annotationList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Map<String, IAnnotation> annMap = new HashMap<>(annotationList.size());
|
||||
for (IAnnotation ann : annotationList) {
|
||||
annMap.put(ann.getAnnotationClass(), ann);
|
||||
}
|
||||
return new AnnotationsList(annMap);
|
||||
}
|
||||
|
||||
private final Map<String, IAnnotation> map;
|
||||
|
||||
public AnnotationsList(Map<String, IAnnotation> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public IAnnotation get(String className) {
|
||||
return map.get(className);
|
||||
}
|
||||
|
||||
public Collection<Annotation> getAll() {
|
||||
public Collection<IAnnotation> getAll() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
|
@ -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<List<IAnnotation>> annotationRefList) {
|
||||
if (annotationRefList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<AnnotationsList> list = new ArrayList<>(annotationRefList.size());
|
||||
for (List<IAnnotation> annList : annotationRefList) {
|
||||
list.add(AnnotationsList.pack(annList));
|
||||
}
|
||||
node.addAttr(new MethodParameters(list));
|
||||
}
|
||||
|
||||
private final List<AnnotationsList> paramList;
|
||||
|
||||
public MethodParameters(int paramCount) {
|
||||
paramList = new ArrayList<>(paramCount);
|
||||
public MethodParameters(List<AnnotationsList> paramsList) {
|
||||
this.paramList = paramsList;
|
||||
}
|
||||
|
||||
public List<AnnotationsList> getParamList() {
|
||||
|
@ -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<LocalVar> localVars;
|
||||
private final List<ILocalVar> localVars;
|
||||
|
||||
public LocalVarsDebugInfoAttr(List<LocalVar> localVars) {
|
||||
public LocalVarsDebugInfoAttr(List<ILocalVar> localVars) {
|
||||
this.localVars = localVars;
|
||||
}
|
||||
|
||||
public List<LocalVar> getLocalVars() {
|
||||
public List<ILocalVar> getLocalVars() {
|
||||
return localVars;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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() + ')';
|
||||
}
|
||||
}
|
||||
|
@ -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<ClassInfo> {
|
||||
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));
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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<ArgType, ClassInfo> classes = new HashMap<>();
|
||||
private final Map<FieldInfo, FieldInfo> fields = new HashMap<>();
|
||||
private final Map<Integer, MethodInfo> methods = new HashMap<>();
|
||||
// use only one MethodInfo instance
|
||||
private final Map<MethodInfo, MethodInfo> 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);
|
||||
|
@ -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<MethodInfo> {
|
||||
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<ArgType> 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<ArgType> 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<ArgType> args, ArgType retType) {
|
||||
|
@ -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
|
||||
|
@ -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<LiteralArg> getLiteralArgs(ArgType type) {
|
||||
List<LiteralArg> 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
|
@ -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<LiteralArg> getLiteralArgs(ArgType elType) {
|
||||
return arrayData.getLiteralArgs(elType);
|
||||
}
|
||||
|
||||
public String dataToString() {
|
||||
return Objects.toString(arrayData);
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
@ -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 <T> int getNextInsnOffset(T[] insnArr, int offset, Predicate<T> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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<BlockNode> 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;
|
||||
}
|
||||
}
|
@ -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<BlockNode> 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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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<MethodInfo, MethodNode> 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<FieldNode> fields) {
|
||||
if (fields.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void loadStaticValues(ClassDef cls, List<FieldNode> staticFields) throws DecodeException {
|
||||
List<FieldNode> 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<EncodedValue> 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();
|
||||
|
@ -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<ClassNode> classes = new ArrayList<>();
|
||||
private final Map<ClassInfo, ClassNode> 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<ClassNode> inner = new ArrayList<>();
|
||||
for (ClassNode cls : classes) {
|
||||
if (cls.getClassInfo().isInner()) {
|
||||
inner.add(cls);
|
||||
}
|
||||
}
|
||||
List<ClassNode> 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<ClassNode> 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<ArgType> readParamList(int parametersOffset) {
|
||||
TypeList paramList = dexBuf.readTypeList(parametersOffset);
|
||||
List<ArgType> 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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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<LoopInfo> 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<Integer> addrs = new HashSet<>();
|
||||
List<TryCatchBlock> 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<ArgType> getThrows() {
|
||||
Annotation an = getAnnotation(Consts.DALVIK_THROWS);
|
||||
IAnnotation an = getAnnotation(Consts.DALVIK_THROWS);
|
||||
if (an == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return (List<ArgType>) an.getDefaultValue();
|
||||
List<EncodedValue> types = (List<EncodedValue>) 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();
|
||||
|
@ -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<ClassNode> classes = new ArrayList<>();
|
||||
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<>();
|
||||
|
||||
private ClspGraph clsp;
|
||||
private List<DexNode> 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<InputFile> inputFiles) {
|
||||
dexNodes = new ArrayList<>();
|
||||
for (InputFile input : inputFiles) {
|
||||
for (DexFile dexFile : input.getDexFiles()) {
|
||||
public void loadClasses(List<ILoadResult> 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<ResourceFile> 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<ClassNode> 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<ClassNode> inner = new ArrayList<>();
|
||||
for (ClassNode cls : classes) {
|
||||
if (cls.getClassInfo().isInner()) {
|
||||
inner.add(cls);
|
||||
}
|
||||
}
|
||||
List<ClassNode> 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<ClassNode> getClasses() {
|
||||
return classes;
|
||||
}
|
||||
|
||||
public List<ClassNode> getClasses(boolean includeInner) {
|
||||
List<ClassNode> 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<ClassNode> 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<ClassNode> searchClassByShortName(String shortName) {
|
||||
List<ClassNode> 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<IDexTreeVisitor> getPasses() {
|
||||
@ -262,10 +365,6 @@ public class RootNode {
|
||||
}
|
||||
}
|
||||
|
||||
public List<DexNode> getDexNodes() {
|
||||
return dexNodes;
|
||||
}
|
||||
|
||||
public ClspGraph getClsp() {
|
||||
return clsp;
|
||||
}
|
||||
|
@ -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<Annotation> 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<String, Object> 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;
|
||||
}
|
||||
}
|
@ -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<Object> 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;
|
||||
}
|
||||
}
|
@ -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<String>) a.getDefaultValue());
|
||||
List<EncodedValue> values = (List<EncodedValue>) a.getDefaultValue().getValue();
|
||||
List<String> strings = Utils.collectionMap(values, ev -> ((String) ev.getValue()));
|
||||
return mergeSignature(strings);
|
||||
}
|
||||
|
||||
private char next() {
|
||||
|
@ -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<FieldNode> 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;
|
||||
}
|
||||
}
|
@ -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<InsnNode> 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);
|
||||
}
|
||||
|
@ -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<ITry> tries = codeReader.getTries();
|
||||
if (tries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int handlersCount = 0;
|
||||
Set<Integer> addrs = new HashSet<>();
|
||||
List<TryCatchBlock> 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<Integer> 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<ClassNode> 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<ClassNode> depList) {
|
||||
addDep(dex, depList, cls.getSuperClass());
|
||||
private static void processClass(ClassNode cls, RootNode root, Set<ClassNode> 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<ClassNode> depList, MethodNode methodNode) {
|
||||
addDep(dex, depList, methodNode.getParentClass());
|
||||
addDep(dex, depList, methodNode.getReturnType());
|
||||
private static void processMethod(RootNode root, Set<ClassNode> 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<ClassNode> depList, InsnNode insnNode) {
|
||||
private static void processInsn(RootNode root, Set<ClassNode> 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<ClassNode> depList, InsnNode insn) {
|
||||
private static void processCustomInsn(RootNode root, Set<ClassNode> 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<ClassNode> depList, ArgType type) {
|
||||
private static void addDep(RootNode root, Set<ClassNode> 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<ClassNode> depList, ClassInfo clsInfo) {
|
||||
private static void addDep(RootNode root, Set<ClassNode> 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<ClassNode> depList, ClassNode clsNode) {
|
||||
private static void addDep(RootNode root, Set<ClassNode> depList, ClassNode clsNode) {
|
||||
if (clsNode != null) {
|
||||
// add only top classes
|
||||
depList.add(clsNode.getTopParentClass());
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<FieldInfo> 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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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<String, Object> entry : annotation.getValues().entrySet()) {
|
||||
for (Map.Entry<String, EncodedValue> 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<EncodedValue> listVal = (List<EncodedValue>) 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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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<DexNode> dexNodes = root.getDexNodes();
|
||||
if (dexNodes.isEmpty()) {
|
||||
List<File> 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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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<Integer, Integer> 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<ILocalVar> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<LocalVar> localVars = debugInfoParser.process();
|
||||
attachDebugInfo(mth, localVars, insnArr);
|
||||
setMethodSourceLine(mth, insnArr);
|
||||
}
|
||||
|
||||
private void attachDebugInfo(MethodNode mth, List<LocalVar> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <b>true</b> if local variable was active, else <b>false</b>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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<BlockNode, List<Object>> blocksMap = new LinkedHashMap<>(len);
|
||||
Object[] keysArr = insn.getKeys();
|
||||
BlockNode[] targetBlocksArr = insn.getTargetBlocks();
|
||||
for (int i = 0; i < len; i++) {
|
||||
List<Object> 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;
|
||||
|
@ -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<ClassNode> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 <N extends IDexNode & IAttributeNode> String addError(N node, String error, @Nullable Throwable e) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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<DexNode> 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");
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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<DexFile> dexFiles = new ArrayList<>();
|
||||
|
||||
public static void addFilesFrom(File file, List<InputFile> 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<? extends ZipEntry> 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<DexFile> 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<Path> loadFromJar(Path jar) throws DecodeException {
|
||||
JavaToDex j2d = new JavaToDex();
|
||||
try {
|
||||
LOG.info("converting to dex: {} ...", jar.getFileName());
|
||||
List<Path> 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<Path> 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<DexFile> getDexFiles() {
|
||||
return dexFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
}
|
@ -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<Path> 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<Path> list = new ArrayList<>();
|
||||
try (DirectoryStream<Path> 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();
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<InputFile> 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);
|
||||
}
|
||||
}
|
@ -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<File> 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);
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
@ -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;")));
|
||||
}
|
||||
|
@ -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();"));
|
||||
}
|
||||
|
||||
|
@ -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) {");
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user