mirror of
https://github.com/skylot/jadx.git
synced 2024-10-07 01:53:34 +00:00
Merge branch 'master' into rename
# Conflicts: # jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java # jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java
This commit is contained in:
commit
34692c41f2
@ -13,10 +13,8 @@ assignees: ''
|
||||
- search existing issues by exception message
|
||||
|
||||
**Describe error**
|
||||
- provide full name of method or class with error
|
||||
- provide full java stacktrace
|
||||
|
||||
**Note**: no need to copy method fallback code (commented pseudocode)
|
||||
- attach or provide link to apk file (double check apk version)
|
||||
- full name of method or class with error
|
||||
- full java stacktrace (no need to copy method fallback code (commented pseudocode))
|
||||
- **IMPORTANT!** attach or provide link to apk file (double check apk version)
|
||||
|
||||
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
|
||||
|
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@ -1,5 +1,5 @@
|
||||
:exclamation: Please review the [guidelines for contributing](../CONTRIBUTING.md#Pull-Request-Process)
|
||||
:exclamation: Please review the [guidelines for contributing](https://github.com/skylot/jadx/blob/master/CONTRIBUTING.md#Pull-Request-Process)
|
||||
|
||||
### Description
|
||||
Please describe your pull request.
|
||||
Reference issue it fix.
|
||||
Reference issue it fixes.
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,6 +5,7 @@
|
||||
|
||||
# IntelliJ Idea files
|
||||
.idea/
|
||||
.run/
|
||||
out/
|
||||
*.iml
|
||||
*.ipr
|
||||
@ -24,8 +25,8 @@ node_modules/
|
||||
|
||||
jadx-output/
|
||||
*-tmp/
|
||||
**/tmp/
|
||||
|
||||
*.dex
|
||||
*.class
|
||||
*.dump
|
||||
*.log
|
||||
|
@ -10,11 +10,10 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
|
||||
- search existing issues by exception message
|
||||
|
||||
2. Describe error
|
||||
- provide full name of method or class with error
|
||||
- provide full java stacktrace
|
||||
|
||||
**Note**: no need to copy method fallback code (commented pseudocode)
|
||||
- attach or provide link to apk file (double check apk version)
|
||||
**Describe error**
|
||||
- full name of method or class with error
|
||||
- full java stacktrace (no need to copy method fallback code (commented pseudocode))
|
||||
- **IMPORTANT!:** attach or provide link to apk file (double check apk version)
|
||||
|
||||
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
|
||||
|
||||
@ -23,7 +22,7 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
|
||||
|
||||
1. Please don't submit any code style fixes, dependencies updates or other changes which are not fixing any issues.
|
||||
|
||||
1. Before open a PR please discuss the change you wish to make via issue. PR without corresponding issue will be rejected.
|
||||
1. Before start working on PR please discuss the change you wish to make via issue. PR without corresponding issue will be rejected.
|
||||
|
||||
1. Use only features and API from Java 8 or below.
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
**jadx** - Dex to Java decompiler
|
||||
|
||||
Command line and GUI tools for produce Java source code from Android Dex and Apk files
|
||||
Command line and GUI tools for producing Java source code from Android Dex and Apk files
|
||||
|
||||
**Main features:**
|
||||
- decompile Dalvik bytecode to java classes from APK, dex, aar and zip files
|
||||
@ -121,5 +121,3 @@ To support this project you can:
|
||||
|
||||
---------------------------------------
|
||||
*Licensed under the Apache 2.0 License*
|
||||
|
||||
*Copyright 2019 by Skylot*
|
||||
|
15
build.gradle
15
build.gradle
@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'org.sonarqube' version '2.8'
|
||||
id 'com.github.ben-manes.versions' version '0.27.0'
|
||||
id "com.diffplug.gradle.spotless" version "3.27.1"
|
||||
id 'com.github.ben-manes.versions' version '0.28.0'
|
||||
id "com.diffplug.gradle.spotless" version "3.28.1"
|
||||
}
|
||||
|
||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||
@ -34,16 +34,18 @@ 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'
|
||||
testCompile 'org.mockito:mockito-core:3.2.4'
|
||||
testCompile 'org.mockito:mockito-core:3.3.3'
|
||||
testCompile 'org.assertj:assertj-core:3.15.0'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.0'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2'
|
||||
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'
|
||||
@ -99,7 +102,7 @@ spotless {
|
||||
}
|
||||
format 'misc', {
|
||||
target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties'
|
||||
targetExclude ".gradle/**", ".idea/**"
|
||||
targetExclude ".gradle/**", ".idea/**", "*/build/**"
|
||||
|
||||
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
||||
encoding("UTF-8")
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
3
gradlew.bat
vendored
3
gradlew.bat
vendored
@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
|
@ -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,12 +1,12 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.beust.jcommander.IStringConverter;
|
||||
import com.beust.jcommander.Parameter;
|
||||
@ -353,7 +353,7 @@ public class JadxCLIArgs {
|
||||
}
|
||||
|
||||
public static String enumValuesString(Enum<?>[] values) {
|
||||
return Arrays.stream(values)
|
||||
return Stream.of(values)
|
||||
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
@ -1,18 +1,27 @@
|
||||
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:7.3.1'
|
||||
compile 'org.jetbrains:annotations:18.0.0'
|
||||
compile 'com.google.code.gson:gson:2.8.6'
|
||||
|
||||
compile 'org.smali:baksmali:2.4.0'
|
||||
compile('org.smali:smali:2.4.0') {
|
||||
exclude group: 'com.google.guava'
|
||||
}
|
||||
compile 'com.google.guava:guava:28.2-jre'
|
||||
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 {
|
||||
exclude '**/tmp/*'
|
||||
}
|
||||
|
@ -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,14 +91,22 @@ 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();
|
||||
root.runPreDecompileStage();
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -103,27 +118,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 +254,7 @@ public final class JadxDecompiler {
|
||||
if (root == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
resources = new ResourcesLoader(this).load(inputFiles);
|
||||
resources = new ResourcesLoader(this).load();
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package jadx.core;
|
||||
|
||||
public class Consts {
|
||||
public static final boolean DEBUG = false;
|
||||
public static final boolean DEBUG_USAGE = false;
|
||||
public static final boolean DEBUG_TYPE_INFERENCE = false;
|
||||
|
||||
public static final String CLASS_OBJECT = "java.lang.Object";
|
||||
public static final String CLASS_STRING = "java.lang.String";
|
||||
@ -11,11 +13,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,39 @@ 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.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;
|
||||
@ -28,6 +54,7 @@ import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||
|
||||
public class Jadx {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||
@ -41,70 +68,87 @@ public class Jadx {
|
||||
}
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
||||
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> getPreDecompilePassesList() {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
passes.add(new RenameVisitor());
|
||||
passes.add(new UsageInfoVisitor());
|
||||
return passes;
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
||||
if (args.isFallbackMode()) {
|
||||
passes.add(new FallbackModeVisitor());
|
||||
} else {
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoParseVisitor());
|
||||
}
|
||||
return getFallbackPassesList();
|
||||
}
|
||||
|
||||
passes.add(new FindSuperUsageVisitor());
|
||||
passes.add(new BlockSplitter());
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
}
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoAttachVisitor());
|
||||
}
|
||||
passes.add(new AttachTryCatchVisitor());
|
||||
passes.add(new ProcessInstructionsVisitor());
|
||||
|
||||
passes.add(new BlockProcessor());
|
||||
passes.add(new BlockExceptionHandler());
|
||||
passes.add(new BlockFinish());
|
||||
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 SSATransform());
|
||||
passes.add(new ConstructorVisitor());
|
||||
passes.add(new InitCodeVariables());
|
||||
passes.add(new MarkFinallyVisitor());
|
||||
passes.add(new ConstInlineVisitor());
|
||||
passes.add(new AttachMethodDetails());
|
||||
passes.add(new TypeInferenceVisitor());
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoApplyVisitor());
|
||||
}
|
||||
passes.add(new AttachMethodDetails());
|
||||
passes.add(new OverrideMethodVisitor());
|
||||
|
||||
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 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 RegionMakerVisitor());
|
||||
passes.add(new IfRegionVisitor());
|
||||
passes.add(new ReturnVisitor());
|
||||
passes.add(new CleanRegions());
|
||||
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 CodeShrinkVisitor());
|
||||
passes.add(new MethodInvokeVisitor());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new CheckRegions());
|
||||
passes.add(new RegionMakerVisitor());
|
||||
passes.add(new IfRegionVisitor());
|
||||
passes.add(new ReturnVisitor());
|
||||
passes.add(new CleanRegions());
|
||||
|
||||
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 CodeShrinkVisitor());
|
||||
passes.add(new MethodInvokeVisitor());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new CheckRegions());
|
||||
|
||||
passes.add(new ProcessVariables());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRegions());
|
||||
}
|
||||
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 DependencyCollector());
|
||||
passes.add(new RenameVisitor());
|
||||
passes.add(new ProcessVariables());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRegions());
|
||||
}
|
||||
return passes;
|
||||
}
|
||||
|
@ -7,9 +7,9 @@ import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.dex.nodes.ProcessState.GENERATED;
|
||||
import static jadx.core.dex.nodes.ProcessState.LOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
||||
@ -43,7 +43,7 @@ public final class ProcessClass {
|
||||
cls.setState(PROCESS_COMPLETE);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
|
||||
cls.addError("Class process error: " + e.getClass().getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,11 +54,16 @@ public final class ProcessClass {
|
||||
if (topParentClass != cls) {
|
||||
return generateCode(topParentClass);
|
||||
}
|
||||
if (cls.getState() == GENERATED) {
|
||||
// allow to run code generation again
|
||||
cls.setState(NOT_LOADED);
|
||||
}
|
||||
try {
|
||||
process(cls);
|
||||
cls.getDependencies().forEach(ProcessClass::process);
|
||||
process(cls);
|
||||
|
||||
ICodeInfo code = CodeGen.generate(cls);
|
||||
cls.setState(GENERATED);
|
||||
cls.unload();
|
||||
return code;
|
||||
} catch (Throwable e) {
|
||||
|
@ -13,12 +13,12 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
@ -88,7 +88,7 @@ public class ClsSet {
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
long time = System.currentTimeMillis() - startTime;
|
||||
int methodsCount = Arrays.stream(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
|
||||
int methodsCount = Stream.of(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
|
||||
LOG.debug("Load class set in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount);
|
||||
}
|
||||
}
|
||||
@ -246,7 +246,7 @@ public class ClsSet {
|
||||
writeMethod(out, method, names);
|
||||
}
|
||||
}
|
||||
int methodsCount = Arrays.stream(classes).mapToInt(c -> c.getMethodsMap().size()).sum();
|
||||
int methodsCount = Stream.of(classes).mapToInt(c -> c.getMethodsMap().size()).sum();
|
||||
LOG.info("Classes: {}, methods: {}, file size: {}B", classes.length, methodsCount, out.size());
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -29,7 +30,7 @@ public class ClspGraph {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
||||
|
||||
private final RootNode root;
|
||||
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
private final Map<String, Set<String>> superTypesCache = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
private Map<String, ClspClass> nameMap;
|
||||
|
||||
private final Set<String> missingClasses = new HashSet<>();
|
||||
@ -116,7 +117,7 @@ public class ClspGraph {
|
||||
* @return {@code clsName} instanceof {@code implClsName}
|
||||
*/
|
||||
public boolean isImplements(String clsName, String implClsName) {
|
||||
Set<String> anc = getAncestors(clsName);
|
||||
Set<String> anc = getSuperTypes(clsName);
|
||||
return anc.contains(implClsName);
|
||||
}
|
||||
|
||||
@ -142,7 +143,7 @@ public class ClspGraph {
|
||||
if (isImplements(clsName, implClsName)) {
|
||||
return implClsName;
|
||||
}
|
||||
Set<String> anc = getAncestors(clsName);
|
||||
Set<String> anc = getSuperTypes(clsName);
|
||||
return searchCommonParent(anc, cls);
|
||||
}
|
||||
|
||||
@ -163,35 +164,42 @@ public class ClspGraph {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Set<String> getAncestors(String clsName) {
|
||||
Set<String> result = ancestorCache.get(clsName);
|
||||
if (result != null) {
|
||||
return result;
|
||||
public Set<String> getSuperTypes(String clsName) {
|
||||
Set<String> fromCache = superTypesCache.get(clsName);
|
||||
if (fromCache != null) {
|
||||
return fromCache;
|
||||
}
|
||||
ClspClass cls = nameMap.get(clsName);
|
||||
if (cls == null) {
|
||||
missingClasses.add(clsName);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
result = new HashSet<>();
|
||||
addAncestorsNames(cls, result);
|
||||
Set<String> result = new HashSet<>();
|
||||
addSuperTypes(cls, result);
|
||||
return putInSuperTypesCache(clsName, result);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Set<String> putInSuperTypesCache(String clsName, Set<String> result) {
|
||||
if (result.isEmpty()) {
|
||||
result = Collections.emptySet();
|
||||
Set<String> empty = Collections.emptySet();
|
||||
superTypesCache.put(clsName, result);
|
||||
return empty;
|
||||
}
|
||||
ancestorCache.put(clsName, result);
|
||||
superTypesCache.put(clsName, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addAncestorsNames(ClspClass cls, Set<String> result) {
|
||||
boolean isNew = result.add(cls.getName());
|
||||
if (isNew) {
|
||||
for (ArgType parentType : cls.getParents()) {
|
||||
if (parentType == null) {
|
||||
continue;
|
||||
}
|
||||
ClspClass parentCls = getClspClass(parentType);
|
||||
if (parentCls != null) {
|
||||
addAncestorsNames(parentCls, result);
|
||||
private void addSuperTypes(ClspClass cls, Set<String> result) {
|
||||
for (ArgType parentType : cls.getParents()) {
|
||||
if (parentType == null) {
|
||||
continue;
|
||||
}
|
||||
ClspClass parentCls = getClspClass(parentType);
|
||||
if (parentCls != null) {
|
||||
boolean isNew = result.add(parentCls.getName());
|
||||
if (isNew) {
|
||||
addSuperTypes(parentCls, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
@ -8,32 +9,37 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
import com.google.common.collect.Streams;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
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.Consts;
|
||||
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;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
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;
|
||||
@ -109,6 +115,9 @@ public class ClassGen {
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
if (Consts.DEBUG_USAGE) {
|
||||
addClassUsageInfo(code, cls);
|
||||
}
|
||||
CodeGenUtils.addComments(code, cls);
|
||||
insertDecompilationProblems(code, cls);
|
||||
addClassDeclaration(code);
|
||||
@ -118,17 +127,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);
|
||||
@ -249,7 +258,8 @@ public class ClassGen {
|
||||
}
|
||||
|
||||
private void addInnerClsAndMethods(CodeWriter clsCode) {
|
||||
Streams.concat(cls.getInnerClasses().stream(), cls.getMethods().stream())
|
||||
Stream.of(cls.getInnerClasses(), cls.getMethods())
|
||||
.flatMap(Collection::stream)
|
||||
.filter(node -> !node.contains(AFlag.DONT_GENERATE))
|
||||
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
|
||||
.forEach(node -> {
|
||||
@ -268,7 +278,7 @@ public class ClassGen {
|
||||
inClGen.addClassCode(code);
|
||||
imports.addAll(inClGen.getImports());
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.classError(innerCls, "Inner class code generation error", e);
|
||||
innerCls.addError("Inner class code generation error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,7 +303,7 @@ public class ClassGen {
|
||||
throw new JadxRuntimeException("Method generation error", e);
|
||||
}
|
||||
code.newLine().add("/*");
|
||||
code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e));
|
||||
code.newLine().addMultiLine(ErrorsCounter.error(mth, "Method generation error", e));
|
||||
Utils.appendStackTrace(code, e);
|
||||
code.newLine().add("*/");
|
||||
code.setIndent(savedIndent);
|
||||
@ -312,7 +322,7 @@ public class ClassGen {
|
||||
|
||||
public void addMethodCode(CodeWriter code, MethodNode mth) throws CodegenException {
|
||||
CodeGenUtils.addComments(code, mth);
|
||||
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
|
||||
if (mth.isNoCode()) {
|
||||
MethodGen mthGen = new MethodGen(this, mth);
|
||||
mthGen.addDefinition(code);
|
||||
code.add(';');
|
||||
@ -372,6 +382,9 @@ public class ClassGen {
|
||||
if (f.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
if (Consts.DEBUG_USAGE) {
|
||||
addFieldUsageInfo(code, f);
|
||||
}
|
||||
CodeGenUtils.addComments(code, f);
|
||||
annotationGen.addForField(code, f);
|
||||
|
||||
@ -387,15 +400,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(';');
|
||||
@ -420,12 +434,13 @@ public class ClassGen {
|
||||
EnumField f = it.next();
|
||||
code.startLine(f.getField().getAlias());
|
||||
ConstructorInsn constrInsn = f.getConstrInsn();
|
||||
if (constrInsn.getArgsCount() > f.getStartArg()) {
|
||||
MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth());
|
||||
int skipCount = getEnumCtrSkipArgsCount(callMth);
|
||||
if (constrInsn.getArgsCount() > skipCount) {
|
||||
if (igen == null) {
|
||||
igen = makeInsnGen(enumFields.getStaticMethod());
|
||||
}
|
||||
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth());
|
||||
igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth);
|
||||
igen.generateMethodArguments(code, constrInsn, 0, callMth);
|
||||
}
|
||||
if (f.getCls() != null) {
|
||||
code.add(' ');
|
||||
@ -446,6 +461,16 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
private int getEnumCtrSkipArgsCount(@Nullable MethodNode callMth) {
|
||||
if (callMth != null) {
|
||||
SkipMethodArgsAttr skipArgsAttr = callMth.get(AType.SKIP_MTH_ARGS);
|
||||
if (skipArgsAttr != null) {
|
||||
return skipArgsAttr.getSkipCount();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private InsnGen makeInsnGen(MethodNode mth) {
|
||||
MethodGen mthGen = new MethodGen(this, mth);
|
||||
return new InsnGen(mthGen, false);
|
||||
@ -455,7 +480,7 @@ public class ClassGen {
|
||||
try {
|
||||
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.classError(cls, "Failed to generate init code", e);
|
||||
cls.addError("Failed to generate init code", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -477,6 +502,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) {
|
||||
@ -512,7 +541,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 {
|
||||
@ -552,12 +581,7 @@ public class ClassGen {
|
||||
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
||||
return shortName;
|
||||
}
|
||||
// don't add import if class not public (must be accessed using inheritance)
|
||||
ClassNode classNode = cls.dex().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
|
||||
@ -636,7 +660,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;
|
||||
}
|
||||
@ -644,7 +668,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)
|
||||
@ -653,7 +677,7 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
}
|
||||
return searchCollision(dex, useCls.getParentClass(), searchCls);
|
||||
return searchCollision(root, useCls.getParentClass(), searchCls);
|
||||
}
|
||||
|
||||
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
|
||||
@ -663,6 +687,40 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
private static void addClassUsageInfo(CodeWriter code, ClassNode cls) {
|
||||
List<ClassNode> deps = cls.getDependencies();
|
||||
code.startLine("// deps - ").add(Integer.toString(deps.size()));
|
||||
for (ClassNode depCls : deps) {
|
||||
code.startLine("// ").add(depCls.getFullName());
|
||||
}
|
||||
List<ClassNode> useIn = cls.getUseIn();
|
||||
code.startLine("// use in - ").add(Integer.toString(useIn.size()));
|
||||
for (ClassNode useCls : useIn) {
|
||||
code.startLine("// ").add(useCls.getFullName());
|
||||
}
|
||||
List<MethodNode> useInMths = cls.getUseInMth();
|
||||
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
||||
for (MethodNode useMth : useInMths) {
|
||||
code.startLine("// ").add(useMth.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static void addMthUsageInfo(CodeWriter code, MethodNode mth) {
|
||||
List<MethodNode> useInMths = mth.getUseIn();
|
||||
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
||||
for (MethodNode useMth : useInMths) {
|
||||
code.startLine("// ").add(useMth.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void addFieldUsageInfo(CodeWriter code, FieldNode fieldNode) {
|
||||
List<MethodNode> useInMths = fieldNode.getUseIn();
|
||||
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
||||
for (MethodNode useMth : useInMths) {
|
||||
code.startLine("// ").add(useMth.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public ClassGen getParentGen() {
|
||||
return parentGen == null ? this : parentGen;
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.conditions.Compare;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.dex.regions.conditions.IfCondition.Mode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@ -123,7 +122,7 @@ public class ConditionGen extends InsnGen {
|
||||
wrap(code, firstArg);
|
||||
return;
|
||||
}
|
||||
ErrorsCounter.methodWarn(mth, "Unsupported boolean condition " + op.getSymbol());
|
||||
mth.addWarn("Unsupported boolean condition " + op.getSymbol());
|
||||
}
|
||||
|
||||
addArg(code, firstArg, isArgWrapNeeded(firstArg));
|
||||
|
@ -15,6 +15,7 @@ import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
@ -26,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;
|
||||
@ -35,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;
|
||||
@ -151,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) {
|
||||
@ -189,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);
|
||||
}
|
||||
@ -225,6 +226,9 @@ public class InsnGen {
|
||||
private static final Set<Flags> BODY_ONLY_NOWRAP_FLAGS = EnumSet.of(Flags.BODY_ONLY_NOWRAP);
|
||||
|
||||
protected void makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||
if (insn.getType() == InsnType.REGION_ARG) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
|
||||
makeInsnBody(code, insn, flag == Flags.BODY_ONLY ? BODY_ONLY_FLAG : BODY_ONLY_NOWRAP_FLAGS);
|
||||
@ -260,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:
|
||||
@ -391,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));
|
||||
@ -505,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(';');
|
||||
@ -537,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());
|
||||
}
|
||||
@ -546,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());
|
||||
@ -600,9 +620,8 @@ public class InsnGen {
|
||||
code.add('}');
|
||||
}
|
||||
|
||||
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
|
||||
throws CodegenException {
|
||||
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
||||
private void makeConstructor(ConstructorInsn insn, CodeWriter code) throws CodegenException {
|
||||
ClassNode cls = mth.root().resolveClass(insn.getClassType());
|
||||
if (cls != null && cls.isAnonymous() && !fallback) {
|
||||
cls.ensureProcessed();
|
||||
inlineAnonymousConstructor(code, cls, insn);
|
||||
@ -619,26 +638,24 @@ public class InsnGen {
|
||||
} else {
|
||||
code.add("new ");
|
||||
useClass(code, insn.getClassType());
|
||||
ArgType argType = insn.getResult().getSVar().getCodeVar().getType();
|
||||
boolean genericCls = cls == null || !cls.getGenericTypeParameters().isEmpty();
|
||||
if (argType != null
|
||||
&& argType.getGenericTypes() != null
|
||||
&& genericCls) {
|
||||
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
|
||||
if (genericInfoAttr != null) {
|
||||
code.add('<');
|
||||
if (insn.contains(AFlag.EXPLICIT_GENERICS)) {
|
||||
if (genericInfoAttr.isExplicit()) {
|
||||
boolean first = true;
|
||||
for (ArgType type : argType.getGenericTypes()) {
|
||||
for (ArgType type : genericInfoAttr.getGenericTypes()) {
|
||||
if (!first) {
|
||||
code.add(',');
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
mgen.getClassGen().useType(code, type);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
code.add('>');
|
||||
}
|
||||
}
|
||||
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
|
||||
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
}
|
||||
|
||||
@ -672,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);
|
||||
@ -846,7 +863,7 @@ public class InsnGen {
|
||||
regs[regNums[i]] = arg;
|
||||
}
|
||||
// replace args
|
||||
InsnNode inlCopy = inl.copy();
|
||||
InsnNode inlCopy = inl.copyWithoutResult();
|
||||
List<RegisterArg> inlArgs = new ArrayList<>();
|
||||
inlCopy.getRegisterArgs(inlArgs);
|
||||
for (RegisterArg r : inlArgs) {
|
||||
|
@ -7,25 +7,29 @@ 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;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
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;
|
||||
@ -33,6 +37,10 @@ import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.COMMENTED_DUMP;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.FALLBACK_MODE;
|
||||
|
||||
public class MethodGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
|
||||
|
||||
@ -72,18 +80,22 @@ public class MethodGen {
|
||||
code.attachDefinition(mth);
|
||||
return false;
|
||||
}
|
||||
if (Consts.DEBUG_USAGE) {
|
||||
ClassGen.addMthUsageInfo(code, mth);
|
||||
}
|
||||
addOverrideAnnotation(code, mth);
|
||||
annotationGen.addForMethod(code, mth);
|
||||
|
||||
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
|
||||
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()) {
|
||||
@ -137,15 +149,32 @@ 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;
|
||||
}
|
||||
|
||||
private void addOverrideAnnotation(CodeWriter code, MethodNode mth) {
|
||||
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||
if (overrideAttr == null) {
|
||||
return;
|
||||
}
|
||||
code.startLine("@Override");
|
||||
code.add(" // ");
|
||||
Iterator<IMethodDetails> it = overrideAttr.getOverrideList().iterator();
|
||||
while (it.hasNext()) {
|
||||
IMethodDetails methodDetails = it.next();
|
||||
code.add(methodDetails.getMethodInfo().getDeclClass().getAliasFullName());
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addMethodArguments(CodeWriter code, List<RegisterArg> args) {
|
||||
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
|
||||
int i = 0;
|
||||
@ -201,7 +230,7 @@ public class MethodGen {
|
||||
|
||||
public void addInstructions(CodeWriter code) throws CodegenException {
|
||||
if (mth.root().getArgs().isFallbackMode()) {
|
||||
addFallbackMethodCode(code);
|
||||
addFallbackMethodCode(code, FALLBACK_MODE);
|
||||
} else if (classGen.isFallbackMode()) {
|
||||
dumpInstructions(code);
|
||||
} else {
|
||||
@ -229,7 +258,7 @@ public class MethodGen {
|
||||
|
||||
public void dumpInstructions(CodeWriter code) {
|
||||
code.startLine("/*");
|
||||
addFallbackMethodCode(code);
|
||||
addFallbackMethodCode(code, COMMENTED_DUMP);
|
||||
code.startLine("*/");
|
||||
|
||||
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
|
||||
@ -243,18 +272,18 @@ public class MethodGen {
|
||||
.add("\");");
|
||||
}
|
||||
|
||||
public void addFallbackMethodCode(CodeWriter code) {
|
||||
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;
|
||||
public void addFallbackMethodCode(CodeWriter code, FallbackOption fallbackOption) {
|
||||
// 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) {
|
||||
@ -265,11 +294,17 @@ public class MethodGen {
|
||||
if (mth.getThisArg() != null) {
|
||||
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
|
||||
}
|
||||
addFallbackInsns(code, mth, insnArr, true);
|
||||
addFallbackInsns(code, mth, insnArr, fallbackOption);
|
||||
code.decIndent();
|
||||
}
|
||||
|
||||
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
|
||||
public enum FallbackOption {
|
||||
FALLBACK_MODE,
|
||||
BLOCK_DUMP,
|
||||
COMMENTED_DUMP
|
||||
}
|
||||
|
||||
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
|
||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
||||
boolean attachInsns = mth.root().getArgs().isJsonOutput();
|
||||
InsnNode prevInsn = null;
|
||||
@ -277,7 +312,7 @@ public class MethodGen {
|
||||
if (insn == null) {
|
||||
continue;
|
||||
}
|
||||
if (addLabels && needLabel(insn, prevInsn)) {
|
||||
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
|
||||
code.decIndent();
|
||||
code.startLine(getLabelName(insn.getOffset()) + ':');
|
||||
code.incIndent();
|
||||
@ -286,7 +321,14 @@ public class MethodGen {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
code.startLine();
|
||||
boolean escapeComment = isCommentEscapeNeeded(insn, option);
|
||||
if (escapeComment) {
|
||||
code.decIndent();
|
||||
code.startLine("*/");
|
||||
code.startLine("// ");
|
||||
} else {
|
||||
code.startLine();
|
||||
}
|
||||
if (attachInsns) {
|
||||
code.attachLineAnnotation(insn);
|
||||
}
|
||||
@ -298,6 +340,11 @@ public class MethodGen {
|
||||
}
|
||||
}
|
||||
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
|
||||
if (escapeComment) {
|
||||
code.startLine("/*");
|
||||
code.incIndent();
|
||||
}
|
||||
|
||||
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
|
||||
if (catchAttr != null) {
|
||||
code.add(" // " + catchAttr);
|
||||
@ -310,6 +357,16 @@ public class MethodGen {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCommentEscapeNeeded(InsnNode insn, FallbackOption option) {
|
||||
if (option == COMMENTED_DUMP) {
|
||||
if (insn.getType() == InsnType.CONST_STR) {
|
||||
String str = ((ConstStringNode) insn).getString();
|
||||
return str.contains("*/");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean needLabel(InsnNode insn, InsnNode prevInsn) {
|
||||
if (insn.contains(AType.EXC_HANDLER)) {
|
||||
return true;
|
||||
|
@ -10,11 +10,13 @@ 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;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
@ -25,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;
|
||||
@ -39,7 +40,6 @@ import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@ -182,18 +182,6 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException {
|
||||
BlockNode header = region.getHeader();
|
||||
if (header != null) {
|
||||
List<InsnNode> headerInsns = header.getInstructions();
|
||||
if (headerInsns.size() > 1) {
|
||||
ErrorsCounter.methodWarn(mth, "Found not inlined instructions from loop header");
|
||||
int last = headerInsns.size() - 1;
|
||||
for (int i = 0; i < last; i++) {
|
||||
InsnNode insn = headerInsns.get(i);
|
||||
makeInsn(insn, code);
|
||||
}
|
||||
}
|
||||
}
|
||||
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
|
||||
if (labelAttr != null) {
|
||||
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
|
||||
@ -263,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 (");
|
||||
@ -299,9 +287,9 @@ public class RegionGen extends InsnGen {
|
||||
staticField(code, fn.getFieldInfo());
|
||||
// print original value, sometimes replaced with incorrect field
|
||||
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
|
||||
if (valueAttr != null) {
|
||||
Object value = valueAttr.getValue();
|
||||
if (value != null && valueAttr.getValueType() == FieldInitAttr.InitType.CONST) {
|
||||
if (valueAttr != null && valueAttr.getValueType() == FieldInitAttr.InitType.CONST) {
|
||||
Object value = valueAttr.getEncodedValue();
|
||||
if (value != null) {
|
||||
code.add(" /*").add(value.toString()).add("*/");
|
||||
}
|
||||
}
|
||||
@ -347,7 +335,7 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
code.startLine("} catch (");
|
||||
if (handler.isCatchAll()) {
|
||||
code.add("Throwable");
|
||||
useClass(code, ArgType.THROWABLE);
|
||||
} else {
|
||||
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
|
||||
if (it.hasNext()) {
|
||||
@ -360,11 +348,15 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
code.add(' ');
|
||||
InsnArg arg = handler.getArg();
|
||||
if (arg instanceof RegisterArg) {
|
||||
if (arg == null) {
|
||||
code.add("unknown"); // throwing exception is too late at this point
|
||||
} else if (arg instanceof RegisterArg) {
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
code.add(mgen.getNameGen().assignArg(reg.getSVar().getCodeVar()));
|
||||
} else if (arg instanceof NamedArg) {
|
||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected arg type in catch block: " + arg + ", class: " + arg.getClass().getSimpleName());
|
||||
}
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,14 +31,17 @@ public class PackageNode {
|
||||
public String getFullName() {
|
||||
if (cachedPackageFullName == null) {
|
||||
Deque<PackageNode> pp = getParentPackages();
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(pp.pop().getName());
|
||||
while (!pp.isEmpty()) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
if (pp.isEmpty()) {
|
||||
cachedPackageFullName = "";
|
||||
} else {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(pp.pop().getName());
|
||||
while (!pp.isEmpty()) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
result.append(pp.pop().getName());
|
||||
}
|
||||
cachedPackageFullName = result.toString();
|
||||
}
|
||||
cachedPackageFullName = result.toString();
|
||||
}
|
||||
return cachedPackageFullName;
|
||||
}
|
||||
|
@ -60,7 +60,6 @@ public enum AFlag {
|
||||
|
||||
FALL_THROUGH,
|
||||
|
||||
EXPLICIT_GENERICS,
|
||||
VARARG_CALL,
|
||||
|
||||
/**
|
||||
|
@ -12,6 +12,7 @@ import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr;
|
||||
import jadx.core.dex.attributes.nodes.JadxError;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
@ -19,13 +20,14 @@ import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
|
||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
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;
|
||||
@ -36,6 +38,7 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
*
|
||||
* @param <T> attribute class implementation
|
||||
*/
|
||||
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||
public class AType<T extends IAttribute> {
|
||||
|
||||
// class, method, field
|
||||
@ -61,6 +64,8 @@ public class AType<T extends IAttribute> {
|
||||
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
|
||||
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
|
||||
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
|
||||
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
|
||||
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
|
||||
|
||||
// region
|
||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
|
||||
@ -81,6 +86,7 @@ public class AType<T extends IAttribute> {
|
||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
|
||||
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
|
||||
public static final AType<IMethodDetails> METHOD_DETAILS = new AType<>();
|
||||
public static final AType<GenericInfoAttr> GENERIC_INFO = new AType<>();
|
||||
|
||||
// register
|
||||
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
|
||||
|
@ -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() {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
@ -15,13 +14,11 @@ public class EnumClassAttr implements IAttribute {
|
||||
public static class EnumField {
|
||||
private final FieldNode field;
|
||||
private final ConstructorInsn constrInsn;
|
||||
private final int startArg;
|
||||
private ClassNode cls;
|
||||
|
||||
public EnumField(FieldNode field, ConstructorInsn co, int startArg) {
|
||||
public EnumField(FieldNode field, ConstructorInsn co) {
|
||||
this.field = field;
|
||||
this.constrInsn = co;
|
||||
this.startArg = startArg;
|
||||
}
|
||||
|
||||
public FieldNode getField() {
|
||||
@ -32,10 +29,6 @@ public class EnumClassAttr implements IAttribute {
|
||||
return constrInsn;
|
||||
}
|
||||
|
||||
public int getStartArg() {
|
||||
return startArg;
|
||||
}
|
||||
|
||||
public ClassNode getCls() {
|
||||
return cls;
|
||||
}
|
||||
@ -53,8 +46,8 @@ public class EnumClassAttr implements IAttribute {
|
||||
private final List<EnumField> fields;
|
||||
private MethodNode staticMethod;
|
||||
|
||||
public EnumClassAttr(int fieldsCount) {
|
||||
this.fields = new ArrayList<>(fieldsCount);
|
||||
public EnumClassAttr(List<EnumField> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public List<EnumField> getFields() {
|
||||
|
@ -0,0 +1,38 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
public class GenericInfoAttr implements IAttribute {
|
||||
private final ArgType[] genericTypes;
|
||||
private boolean explicit;
|
||||
|
||||
public GenericInfoAttr(ArgType[] genericTypes) {
|
||||
this.genericTypes = genericTypes;
|
||||
}
|
||||
|
||||
public ArgType[] getGenericTypes() {
|
||||
return genericTypes;
|
||||
}
|
||||
|
||||
public boolean isExplicit() {
|
||||
return explicit;
|
||||
}
|
||||
|
||||
public void setExplicit(boolean explicit) {
|
||||
this.explicit = explicit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<GenericInfoAttr> getType() {
|
||||
return AType.GENERIC_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GenericInfoAttr{" + Arrays.toString(genericTypes) + ", explicit=" + explicit + '}';
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,10 @@ public class LoopInfo {
|
||||
return edges;
|
||||
}
|
||||
|
||||
public BlockNode getPreHeader() {
|
||||
return BlockUtils.selectOther(end, start.getPredecessors());
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
|
||||
public class MethodOverrideAttr implements IAttribute {
|
||||
|
||||
private final List<IMethodDetails> overrideList;
|
||||
|
||||
public MethodOverrideAttr(List<IMethodDetails> overrideList) {
|
||||
this.overrideList = overrideList;
|
||||
}
|
||||
|
||||
public List<IMethodDetails> getOverrideList() {
|
||||
return overrideList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<MethodOverrideAttr> getType() {
|
||||
return AType.METHOD_OVERRIDE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "METHOD_OVERRIDE: " + overrideList;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
/**
|
||||
* Set of known type variables at current method
|
||||
*/
|
||||
public class MethodTypeVarsAttr implements IAttribute {
|
||||
|
||||
private final Set<ArgType> typeVars;
|
||||
|
||||
public MethodTypeVarsAttr(Set<ArgType> typeVars) {
|
||||
this.typeVars = typeVars;
|
||||
}
|
||||
|
||||
public Set<ArgType> getTypeVars() {
|
||||
return typeVars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<MethodTypeVarsAttr> getType() {
|
||||
return AType.METHOD_TYPE_VARS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TYPE_VARS: " + typeVars;
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.nodes.ICodeNode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
|
||||
public abstract class NotificationAttrNode extends LineAttrNode implements ICodeNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NotificationAttrNode.class);
|
||||
|
||||
public void addError(String errStr, Throwable e) {
|
||||
ErrorsCounter.error(this, errStr, e);
|
||||
}
|
||||
|
||||
public void addWarn(String warnStr) {
|
||||
ErrorsCounter.warning(this, warnStr);
|
||||
}
|
||||
|
||||
public void addWarnComment(String warn) {
|
||||
addWarnComment(warn, null);
|
||||
}
|
||||
|
||||
public void addWarnComment(String warn, @Nullable Throwable exc) {
|
||||
String commentStr = "JADX WARN: " + warn;
|
||||
addAttr(AType.COMMENTS, commentStr);
|
||||
if (exc != null) {
|
||||
LOG.warn("{} in {}", warn, this, exc);
|
||||
} else {
|
||||
LOG.warn("{} in {}", warn, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void addComment(String commentStr) {
|
||||
addAttr(AType.COMMENTS, commentStr);
|
||||
LOG.info("{} in {}", commentStr, this);
|
||||
}
|
||||
|
||||
public void addDebugComment(String commentStr) {
|
||||
addAttr(AType.COMMENTS, "JADX DEBUG: " + commentStr);
|
||||
LOG.debug("{} in {}", commentStr, this);
|
||||
}
|
||||
}
|
@ -3,12 +3,11 @@ package jadx.core.dex.attributes.nodes;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
|
||||
import static com.google.common.base.Ascii.NL;
|
||||
|
||||
public class PhiListAttr implements IAttribute {
|
||||
|
||||
private final List<PhiInsn> list = new LinkedList<>();
|
||||
@ -30,7 +29,7 @@ public class PhiListAttr implements IAttribute {
|
||||
sb.append('r').append(phiInsn.getResult().getRegNum()).append(' ');
|
||||
}
|
||||
for (PhiInsn phiInsn : list) {
|
||||
sb.append(NL).append(" ").append(phiInsn).append(' ').append(phiInsn.getAttributesString());
|
||||
sb.append(CodeWriter.NL).append(" ").append(phiInsn).append(' ').append(phiInsn.getAttributesString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -44,7 +44,7 @@ public class SkipMethodArgsAttr implements IAttribute {
|
||||
private final BitSet skipArgs;
|
||||
|
||||
private SkipMethodArgsAttr(MethodNode mth) {
|
||||
this.skipArgs = new BitSet(mth.getArgRegs().size());
|
||||
this.skipArgs = new BitSet(mth.getMethodInfo().getArgsCount());
|
||||
}
|
||||
|
||||
public void skip(int argNum) {
|
||||
@ -55,6 +55,10 @@ public class SkipMethodArgsAttr implements IAttribute {
|
||||
return skipArgs.get(argNum);
|
||||
}
|
||||
|
||||
public int getSkipCount() {
|
||||
return skipArgs.cardinality();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<SkipMethodArgsAttr> getType() {
|
||||
return AType.SKIP_MTH_ARGS;
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -13,13 +13,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.utils.ErrorsCounter;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public class ConstStorage {
|
||||
|
||||
@ -87,10 +86,10 @@ public class ConstStorage {
|
||||
if (accFlags.isStatic() && accFlags.isFinal()) {
|
||||
FieldInitAttr fv = f.get(AType.FIELD_INIT);
|
||||
if (fv != null
|
||||
&& fv.getValue() != null
|
||||
&& fv.getValueType() == FieldInitAttr.InitType.CONST
|
||||
&& fv != FieldInitAttr.NULL_VALUE) {
|
||||
addConstField(cls, f, fv.getValue(), accFlags.isPublic());
|
||||
&& fv != FieldInitAttr.NULL_VALUE
|
||||
&& fv.getEncodedValue() != null) {
|
||||
addConstField(cls, f, fv.getEncodedValue().getValue(), accFlags.isPublic());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -118,9 +117,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;
|
||||
}
|
||||
@ -145,7 +144,7 @@ public class ConstStorage {
|
||||
if (parentClass == null) {
|
||||
break;
|
||||
}
|
||||
current = dex.resolveClass(parentClass);
|
||||
current = root.resolveClass(parentClass);
|
||||
}
|
||||
if (searchGlobal) {
|
||||
return globalValues.get(value);
|
||||
@ -154,12 +153,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;
|
||||
}
|
||||
@ -174,7 +173,7 @@ public class ConstStorage {
|
||||
return innerClass.searchFieldByName(fieldName);
|
||||
}
|
||||
}
|
||||
ErrorsCounter.classWarn(appResClass, "Not found resource field with id: " + value + ", name: " + str.replace('/', '.'));
|
||||
appResClass.addWarn("Not found resource field with id: " + value + ", name: " + str.replace('/', '.'));
|
||||
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
|
||||
@ -51,6 +50,10 @@ public class ArithNode extends InsnNode {
|
||||
addArg(b);
|
||||
}
|
||||
|
||||
public ArithNode(ArithOp op, InsnArg a, InsnArg b) {
|
||||
this(op, null, a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create one argument arithmetic instructions (a+=2).
|
||||
* Result is not set (null).
|
||||
@ -58,7 +61,7 @@ public class ArithNode extends InsnNode {
|
||||
* @param res argument to change
|
||||
*/
|
||||
public static ArithNode oneArgOp(ArithOp op, InsnArg res, InsnArg a) {
|
||||
ArithNode insn = new ArithNode(op, null, res, a);
|
||||
ArithNode insn = new ArithNode(op, res, a);
|
||||
insn.add(AFlag.ARITH_ONEARG);
|
||||
return insn;
|
||||
}
|
||||
@ -97,10 +100,7 @@ public class ArithNode extends InsnNode {
|
||||
|
||||
@Override
|
||||
public InsnNode copy() {
|
||||
ArithNode copy = new ArithNode(op,
|
||||
getResult().duplicate(),
|
||||
getArg(0).duplicate(),
|
||||
getArg(1).duplicate());
|
||||
ArithNode copy = new ArithNode(op, getArg(0).duplicate(), getArg(1).duplicate());
|
||||
return copyCommonParams(copy);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
|
||||
public final class ConstStringNode extends InsnNode {
|
||||
|
||||
@ -34,6 +35,6 @@ public final class ConstStringNode extends InsnNode {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " \"" + str + '"';
|
||||
return super.toString() + ' ' + StringUtils.getInstance().unescapeString(str);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -41,10 +41,15 @@ public class IndexInsnNode extends InsnNode {
|
||||
switch (insnType) {
|
||||
case CAST:
|
||||
case CHECK_CAST:
|
||||
return InsnUtils.formatOffset(offset) + ": "
|
||||
+ InsnUtils.insnTypeToString(insnType)
|
||||
+ getResult() + " = (" + InsnUtils.indexToString(index) + ") "
|
||||
+ Utils.listToString(getArguments());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(InsnUtils.formatOffset(offset)).append(": ");
|
||||
sb.append(insnType).append(' ');
|
||||
if (getResult() != null) {
|
||||
sb.append(getResult()).append(" = ");
|
||||
}
|
||||
sb.append('(').append(InsnUtils.indexToString(index)).append(") ");
|
||||
sb.append(Utils.listToString(getArguments()));
|
||||
return sb.toString();
|
||||
|
||||
default:
|
||||
return super.toString() + ' ' + InsnUtils.indexToString(index);
|
||||
|
@ -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 ***
|
||||
|
||||
@ -66,6 +69,6 @@ public enum InsnType {
|
||||
ONE_ARG,
|
||||
PHI,
|
||||
|
||||
// TODO: now multidimensional arrays created using Array.newInstance function
|
||||
NEW_MULTIDIM_ARRAY
|
||||
// fake insn to keep arguments which will be used in regions codegen
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import jadx.core.dex.attributes.AFlag;
|
||||
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.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.Utils;
|
||||
@ -83,6 +84,20 @@ public final class PhiInsn extends InsnNode {
|
||||
return reg;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public RegisterArg getArgBySsaVar(SSAVar ssaVar) {
|
||||
if (getArgsCount() == 0) {
|
||||
return null;
|
||||
}
|
||||
for (InsnArg insnArg : getArguments()) {
|
||||
RegisterArg reg = (RegisterArg) insnArg;
|
||||
if (reg.getSVar() == ssaVar) {
|
||||
return reg;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceArg(InsnArg from, InsnArg to) {
|
||||
if (!(from instanceof RegisterArg) || !(to instanceof RegisterArg)) {
|
||||
|
@ -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,133 +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());
|
||||
for (int i = 0; i < targets.length; i++) {
|
||||
sb.append(CodeWriter.NL);
|
||||
sb.append(" case ").append(keys[i]);
|
||||
sb.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();
|
||||
}
|
||||
}
|
@ -4,13 +4,17 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
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;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public abstract class ArgType {
|
||||
public static final ArgType INT = primitive(PrimitiveType.INT);
|
||||
@ -80,6 +84,8 @@ public abstract class ArgType {
|
||||
return STRING;
|
||||
case Consts.CLASS_CLASS:
|
||||
return CLASS;
|
||||
case Consts.CLASS_THROWABLE:
|
||||
return THROWABLE;
|
||||
default:
|
||||
return new ObjectType(cleanObjectName);
|
||||
}
|
||||
@ -113,7 +119,7 @@ public abstract class ArgType {
|
||||
return new OuterGenericObject((GenericObject) genericOuterType, (ObjectType) innerType);
|
||||
}
|
||||
|
||||
public static ArgType array(ArgType vtype) {
|
||||
public static ArgType array(@NotNull ArgType vtype) {
|
||||
return new ArrayArg(vtype);
|
||||
}
|
||||
|
||||
@ -578,11 +584,8 @@ public abstract class ArgType {
|
||||
if (from.equals(to)) {
|
||||
return false;
|
||||
}
|
||||
if (from.isObject() && to.isObject()
|
||||
&& root.getClsp().isImplements(from.getObject(), to.getObject())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
TypeCompareEnum result = root.getTypeUpdate().getTypeCompare().compareTypes(from, to);
|
||||
return !result.isNarrow();
|
||||
}
|
||||
|
||||
public static boolean isInstanceOf(RootNode root, ArgType type, ArgType of) {
|
||||
@ -644,6 +647,9 @@ public abstract class ArgType {
|
||||
}
|
||||
|
||||
public static ArgType parse(String type) {
|
||||
if (type == null || type.isEmpty()) {
|
||||
throw new JadxRuntimeException("Failed to parse type string: " + type);
|
||||
}
|
||||
char f = type.charAt(0);
|
||||
switch (f) {
|
||||
case 'L':
|
||||
@ -739,12 +745,42 @@ public abstract class ArgType {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static ArgType tryToResolveClassAlias(DexNode dex, ArgType type) {
|
||||
/**
|
||||
* Recursively visit all subtypes of this type.
|
||||
* To exit return non-null value.
|
||||
*/
|
||||
public <R> R visitTypes(Function<ArgType, R> visitor) {
|
||||
R r = visitor.apply(this);
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
ArgType wildcardType = getWildcardType();
|
||||
if (wildcardType != null) {
|
||||
return wildcardType.visitTypes(visitor);
|
||||
}
|
||||
if (isArray()) {
|
||||
ArgType arrayElement = getArrayElement();
|
||||
if (arrayElement != null) {
|
||||
return arrayElement.visitTypes(visitor);
|
||||
}
|
||||
}
|
||||
if (isGeneric()) {
|
||||
ArgType[] genericTypes = getGenericTypes();
|
||||
if (genericTypes != null) {
|
||||
for (ArgType genericType : genericTypes) {
|
||||
return genericType.visitTypes(visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -1,17 +1,16 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -29,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) {
|
||||
@ -60,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);
|
||||
}
|
||||
|
||||
@ -96,6 +95,11 @@ public abstract class InsnArg extends Typed {
|
||||
|
||||
@Nullable("if wrap failed")
|
||||
public InsnArg wrapInstruction(MethodNode mth, InsnNode insn) {
|
||||
return wrapInstruction(mth, insn, true);
|
||||
}
|
||||
|
||||
@Nullable("if wrap failed")
|
||||
public InsnArg wrapInstruction(MethodNode mth, InsnNode insn, boolean unbind) {
|
||||
InsnNode parent = parentInsn;
|
||||
if (parent == null) {
|
||||
return null;
|
||||
@ -116,14 +120,24 @@ public abstract class InsnArg extends Typed {
|
||||
if (arg.isRegister()) {
|
||||
((RegisterArg) arg).setNameIfUnknown(name);
|
||||
} else if (arg.isInsnWrap()) {
|
||||
((InsnWrapArg) arg).getWrapInsn().getResult().setNameIfUnknown(name);
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
RegisterArg registerArg = wrapInsn.getResult();
|
||||
if (registerArg != null) {
|
||||
registerArg.setNameIfUnknown(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
InsnArg arg = wrapInsnIntoArg(insn);
|
||||
InsnArg oldArg = parent.getArg(i);
|
||||
parent.setArg(i, arg);
|
||||
InsnRemover.unbindArgUsage(mth, this);
|
||||
InsnRemover.unbindResult(mth, insn);
|
||||
InsnRemover.unbindArgUsage(mth, oldArg);
|
||||
if (unbind) {
|
||||
InsnRemover.unbindArgUsage(mth, this);
|
||||
// result not needed in wrapped insn
|
||||
InsnRemover.unbindResult(mth, insn);
|
||||
insn.setResult(null);
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
@ -137,29 +151,29 @@ public abstract class InsnArg extends Typed {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static InsnArg wrapInsnIntoArg(InsnNode insn) {
|
||||
InsnArg arg;
|
||||
InsnType type = insn.getType();
|
||||
if (type == InsnType.CONST || type == InsnType.MOVE) {
|
||||
if (insn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
|
||||
RegisterArg resArg = insn.getResult();
|
||||
arg = wrap(insn);
|
||||
InsnArg arg = wrap(insn);
|
||||
if (resArg != null) {
|
||||
arg.setType(resArg.getType());
|
||||
}
|
||||
return arg;
|
||||
} else {
|
||||
arg = insn.getArg(0);
|
||||
insn.add(AFlag.REMOVE);
|
||||
InsnArg arg = insn.getArg(0);
|
||||
insn.add(AFlag.DONT_GENERATE);
|
||||
return arg;
|
||||
}
|
||||
} else {
|
||||
arg = wrapArg(insn);
|
||||
}
|
||||
return arg;
|
||||
return wrapArg(insn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link InsnArg#wrapInsnIntoArg}.
|
||||
* Prefer {@link InsnArg#wrapInsnIntoArg(InsnNode)}.
|
||||
* <p>
|
||||
* This method don't support MOVE and CONST insns!
|
||||
*/
|
||||
public static InsnArg wrapArg(InsnNode insn) {
|
||||
|
@ -36,7 +36,7 @@ public final class InsnWrapArg extends InsnArg {
|
||||
|
||||
@Override
|
||||
public InsnArg duplicate() {
|
||||
InsnWrapArg copy = new InsnWrapArg(wrappedInsn.copy());
|
||||
InsnWrapArg copy = new InsnWrapArg(wrappedInsn.copyWithoutResult());
|
||||
copy.setType(type);
|
||||
return copyCommonParams(copy);
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@ -72,12 +71,10 @@ public final class LiteralArg extends InsnArg {
|
||||
return literal == that.literal && getType().equals(that.getType());
|
||||
}
|
||||
|
||||
private static final StringUtils DEF_STRING_UTILS = new StringUtils(new JadxArgs());
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
String value = TypeGen.literalToString(literal, getType(), DEF_STRING_UTILS, true, false);
|
||||
String value = TypeGen.literalToString(literal, getType(), StringUtils.getInstance(), true, false);
|
||||
if (getType().equals(ArgType.BOOLEAN) && (value.equals("true") || value.equals("false"))) {
|
||||
return value;
|
||||
}
|
||||
|
@ -130,8 +130,20 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
return duplicate(getRegNum(), sVar);
|
||||
}
|
||||
|
||||
public RegisterArg duplicate(ArgType initType) {
|
||||
return duplicate(getRegNum(), initType, sVar);
|
||||
}
|
||||
|
||||
public RegisterArg duplicate(@Nullable SSAVar ssaVar) {
|
||||
return duplicate(getRegNum(), ssaVar);
|
||||
}
|
||||
|
||||
public RegisterArg duplicate(int regNum, @Nullable SSAVar sVar) {
|
||||
RegisterArg dup = new RegisterArg(regNum, getInitType());
|
||||
return duplicate(regNum, getInitType(), sVar);
|
||||
}
|
||||
|
||||
public RegisterArg duplicate(int regNum, ArgType initType, @Nullable SSAVar sVar) {
|
||||
RegisterArg dup = new RegisterArg(regNum, initType);
|
||||
if (sVar != null) {
|
||||
// only 'set' here, 'assign' or 'use' will binds later
|
||||
dup.setSVar(sVar);
|
||||
|
@ -1,31 +1,34 @@
|
||||
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;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.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.nodes.LineAttrNode;
|
||||
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;
|
||||
import jadx.core.dex.info.AccessInfo.AFType;
|
||||
@ -34,24 +37,24 @@ 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.dex.visitors.ProcessAnonymous;
|
||||
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;
|
||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||
|
||||
public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
|
||||
|
||||
private final DexNode dex;
|
||||
private final ClassDef cls;
|
||||
private final RootNode root;
|
||||
private final IClassData cls;
|
||||
private final int clsDefOffset;
|
||||
@Nullable
|
||||
private final Path inputPath;
|
||||
|
||||
private final ClassInfo clsInfo;
|
||||
private AccessInfo accessFlags;
|
||||
private ArgType superClass;
|
||||
@ -70,73 +73,54 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
private ClassNode parentClass;
|
||||
|
||||
private volatile ProcessState state = ProcessState.NOT_LOADED;
|
||||
|
||||
/** Top level classes used in this class (only for top level classes, empty for inners) */
|
||||
private List<ClassNode> dependencies = Collections.emptyList();
|
||||
/** Classes which uses this class */
|
||||
private List<ClassNode> useIn = Collections.emptyList();
|
||||
/** Methods which uses this class (by instructions only, definition is excluded) */
|
||||
private List<MethodNode> useInMth = Collections.emptyList();
|
||||
|
||||
// cache maps
|
||||
private Map<MethodInfo, MethodNode> mthInfoMap = Collections.emptyMap();
|
||||
|
||||
public ClassNode(DexNode dex, ClassDef cls) {
|
||||
this.dex = dex;
|
||||
this.cls = cls;
|
||||
this.clsDefOffset = cls.getOffset();
|
||||
this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex());
|
||||
initialLoad();
|
||||
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()));
|
||||
initialLoad(cls);
|
||||
this.cls = cls.copy(); // TODO: need only for rename feature
|
||||
}
|
||||
|
||||
private void initialLoad() {
|
||||
private void initialLoad(IClassData cls) {
|
||||
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);
|
||||
@ -146,62 +130,66 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
|
||||
}
|
||||
|
||||
// empty synthetic class
|
||||
public ClassNode(DexNode dex, String name, int accessFlags) {
|
||||
this.dex = dex;
|
||||
public static ClassNode addSyntheticClass(RootNode root, String name, int accessFlags) {
|
||||
ClassNode cls = new ClassNode(root, name, accessFlags);
|
||||
cls.add(AFlag.SYNTHETIC);
|
||||
cls.setState(ProcessState.PROCESS_COMPLETE);
|
||||
root.addClassNode(cls);
|
||||
return cls;
|
||||
}
|
||||
|
||||
// Create empty class
|
||||
private ClassNode(RootNode root, String name, int accessFlags) {
|
||||
this.root = root;
|
||||
this.cls = null;
|
||||
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;
|
||||
this.cls = null;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class signature format:
|
||||
* https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.9.1
|
||||
*/
|
||||
private void parseClassSignature() {
|
||||
SignatureParser sp = SignatureParser.fromNode(this);
|
||||
if (sp == null) {
|
||||
@ -211,7 +199,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
// parse class generic map
|
||||
generics = sp.consumeGenericTypeParameters();
|
||||
// parse super class signature
|
||||
superClass = sp.consumeType();
|
||||
superClass = validateSuperCls(sp.consumeType(), superClass);
|
||||
// parse interfaces signatures
|
||||
for (int i = 0; i < interfaces.size(); i++) {
|
||||
ArgType type = sp.consumeType();
|
||||
@ -226,6 +214,18 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
}
|
||||
}
|
||||
|
||||
private ArgType validateSuperCls(ArgType candidateType, ArgType currentType) {
|
||||
if (!candidateType.isObject()) {
|
||||
this.addComment("Incorrect class signature, super class is not object: " + SignatureParser.getSignature(this));
|
||||
return currentType;
|
||||
}
|
||||
if (Objects.equals(candidateType.getObject(), this.getClassInfo().getType().getObject())) {
|
||||
this.addComment("Incorrect class signature, super class is equals to this class: " + SignatureParser.getSignature(this));
|
||||
return currentType;
|
||||
}
|
||||
return candidateType;
|
||||
}
|
||||
|
||||
private void setFieldsTypesFromSignature() {
|
||||
for (FieldNode field : fields) {
|
||||
try {
|
||||
@ -305,7 +305,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
}
|
||||
clearAttributes();
|
||||
root().getConstValues().removeForClass(this);
|
||||
initialLoad();
|
||||
initialLoad(cls);
|
||||
ProcessAnonymous.runForClass(this);
|
||||
|
||||
for (ClassNode innerClass : innerClasses) {
|
||||
@ -395,10 +395,6 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
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)) {
|
||||
@ -454,14 +450,10 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
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;
|
||||
@ -475,6 +467,16 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return parent == this ? this : parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public void visitParentClasses(Consumer<ClassNode> consumer) {
|
||||
ClassNode currentCls = this;
|
||||
ClassNode parentCls = currentCls.getParentClass();
|
||||
while (parentCls != currentCls) {
|
||||
consumer.accept(parentCls);
|
||||
currentCls = parentCls;
|
||||
parentCls = currentCls.getParentClass();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasNotGeneratedParent() {
|
||||
if (contains(AFlag.DONT_GENERATE)) {
|
||||
return true;
|
||||
@ -533,6 +535,10 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return contains(AFlag.ANONYMOUS_CLASS);
|
||||
}
|
||||
|
||||
public boolean isInner() {
|
||||
return parentClass != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MethodNode getClassInitMth() {
|
||||
return searchMethodByShortId("<clinit>()V");
|
||||
@ -558,14 +564,9 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
this.accessFlags = accessFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DexNode dex() {
|
||||
return dex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootNode root() {
|
||||
return dex.root();
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -613,9 +614,14 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -634,6 +640,27 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
public List<ClassNode> getUseIn() {
|
||||
return useIn;
|
||||
}
|
||||
|
||||
public void setUseIn(List<ClassNode> useIn) {
|
||||
this.useIn = useIn;
|
||||
}
|
||||
|
||||
public List<MethodNode> getUseInMth() {
|
||||
return useInMth;
|
||||
}
|
||||
|
||||
public void setUseInMth(List<MethodNode> useInMth) {
|
||||
this.useInMth = useInMth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getInputPath() {
|
||||
return inputPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return clsInfo.hashCode();
|
||||
@ -651,9 +678,13 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ClassNode o) {
|
||||
return this.getFullName().compareTo(o.getFullName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return clsInfo.getFullName();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.classError(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,11 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import com.android.dex.ClassData.Field;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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 +14,23 @@ 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());
|
||||
private List<MethodNode> useIn = Collections.emptyList();
|
||||
|
||||
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 +50,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 +71,15 @@ public class FieldNode extends LineAttrNode implements ICodeNode {
|
||||
}
|
||||
|
||||
public ClassNode getParentClass() {
|
||||
return parent;
|
||||
return parentClass;
|
||||
}
|
||||
|
||||
public List<MethodNode> getUseIn() {
|
||||
return useIn;
|
||||
}
|
||||
|
||||
public void setUseIn(List<MethodNode> useIn) {
|
||||
this.useIn = useIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -68,13 +88,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;
|
||||
@ -51,6 +50,7 @@ public class InsnNode extends LineAttrNode {
|
||||
}
|
||||
|
||||
public void setResult(@Nullable RegisterArg res) {
|
||||
this.result = res;
|
||||
if (res != null) {
|
||||
res.setParentInsn(this);
|
||||
SSAVar ssaVar = res.getSVar();
|
||||
@ -58,7 +58,6 @@ public class InsnNode extends LineAttrNode {
|
||||
ssaVar.setAssign(res);
|
||||
}
|
||||
}
|
||||
this.result = res;
|
||||
}
|
||||
|
||||
public void addArg(InsnArg arg) {
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -217,6 +216,17 @@ public class InsnNode extends LineAttrNode {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canRemoveResult() {
|
||||
switch (getType()) {
|
||||
case INVOKE:
|
||||
case CONSTRUCTOR:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canReorder() {
|
||||
if (contains(AFlag.DONT_GENERATE)) {
|
||||
if (getType() == InsnType.MONITOR_EXIT) {
|
||||
@ -248,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;
|
||||
|
||||
@ -272,6 +281,15 @@ public class InsnNode extends LineAttrNode {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean containsWrappedInsn() {
|
||||
for (InsnArg arg : this.getArguments()) {
|
||||
if (arg.isInsnWrap()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'Soft' equals, don't compare arguments, only instruction specific parameters.
|
||||
*/
|
||||
@ -317,17 +335,9 @@ public class InsnNode extends LineAttrNode {
|
||||
}
|
||||
|
||||
protected final <T extends InsnNode> T copyCommonParams(T copy) {
|
||||
if (copy.getResult() == null && result != null) {
|
||||
copy.setResult(result.duplicate());
|
||||
}
|
||||
if (copy.getArgsCount() == 0) {
|
||||
for (InsnArg arg : this.getArguments()) {
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
copy.addArg(InsnArg.wrapInsnIntoArg(wrapInsn.copy()));
|
||||
} else {
|
||||
copy.addArg(arg.duplicate());
|
||||
}
|
||||
copy.addArg(arg.duplicate());
|
||||
}
|
||||
}
|
||||
copy.copyAttributesFrom(this);
|
||||
@ -338,6 +348,17 @@ public class InsnNode extends LineAttrNode {
|
||||
|
||||
/**
|
||||
* Make copy of InsnNode object.
|
||||
* <p>
|
||||
* NOTE: can't copy instruction with result argument
|
||||
* (SSA variable can't be used in two different assigns).
|
||||
* <p>
|
||||
* Prefer use next methods:
|
||||
* <ul>
|
||||
* <li>{@link #copyWithoutResult()} to explicitly state that result not needed
|
||||
* <li>{@link #copy(RegisterArg)} to provide new result arg
|
||||
* <li>{@link #copyWithNewSsaVar(MethodNode)} to make new SSA variable for result arg
|
||||
* </ul>
|
||||
* <p>
|
||||
*/
|
||||
public InsnNode copy() {
|
||||
if (this.getClass() != InsnNode.class) {
|
||||
@ -346,6 +367,49 @@ public class InsnNode extends LineAttrNode {
|
||||
return copyCommonParams(new InsnNode(insnType, getArgsCount()));
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #copy()}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends InsnNode> T copyWithoutResult() {
|
||||
return (T) copy();
|
||||
}
|
||||
|
||||
public InsnNode copyWithoutSsa() {
|
||||
InsnNode copy = copyWithoutResult();
|
||||
if (result != null) {
|
||||
if (result.getSVar() == null) {
|
||||
copy.setResult(result.duplicate());
|
||||
} else {
|
||||
throw new JadxRuntimeException("Can't copy if SSA var is set");
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #copy()}
|
||||
*/
|
||||
public InsnNode copy(RegisterArg newReturnArg) {
|
||||
InsnNode copy = copy();
|
||||
copy.setResult(newReturnArg);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #copy()}
|
||||
*/
|
||||
public InsnNode copyWithNewSsaVar(MethodNode mth) {
|
||||
RegisterArg result = getResult();
|
||||
if (result == null) {
|
||||
throw new JadxRuntimeException("Result in null");
|
||||
}
|
||||
int regNum = result.getRegNum();
|
||||
RegisterArg resDupArg = result.duplicate(regNum, null);
|
||||
mth.makeNewSVar(resDupArg);
|
||||
return copy(resDupArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix SSAVar info in register arguments.
|
||||
* Must be used after altering instructions.
|
||||
@ -404,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,68 +1,57 @@
|
||||
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.nodes.LineAttrNode;
|
||||
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.ErrorsCounter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.utils.Utils.lockList;
|
||||
|
||||
public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadable, ICodeNode {
|
||||
public class MethodNode extends NotificationAttrNode implements IMethodDetails, ILoadable, ICodeNode, Comparable<MethodNode> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
|
||||
|
||||
private final MethodInfo mthInfo;
|
||||
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;
|
||||
|
||||
@ -83,13 +72,22 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
private List<LoopInfo> loops;
|
||||
private Region region;
|
||||
|
||||
public MethodNode(ClassNode classNode, Method mthData, boolean isVirtual) {
|
||||
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex());
|
||||
private List<MethodNode> useIn = Collections.emptyList();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@ -123,26 +121,15 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
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();
|
||||
@ -190,8 +177,9 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
if (types == null) {
|
||||
this.retType = mthInfo.getReturnType();
|
||||
this.argTypes = mthInfo.getArgumentsTypes();
|
||||
this.typeParameters = Collections.emptyList();
|
||||
} else {
|
||||
this.argTypes = types;
|
||||
this.argTypes = Collections.unmodifiableList(types);
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,9 +200,7 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
return null;
|
||||
}
|
||||
if (!tryFixArgsCounts(argsTypes, mthArgs)) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Incorrect method signature, types: ({}), method: {}", Utils.listToString(argsTypes), this);
|
||||
}
|
||||
addComment("Incorrect method signature, types: " + Utils.listToString(argsTypes));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -285,6 +271,12 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
public void updateArgTypes(List<ArgType> newArgTypes, String comment) {
|
||||
this.addDebugComment(comment + ", original types: " + getArgTypes());
|
||||
this.argTypes = Collections.unmodifiableList(newArgTypes);
|
||||
initArguments(newArgTypes);
|
||||
}
|
||||
|
||||
public boolean containsGenericArgs() {
|
||||
return !Objects.equals(mthInfo.getArgumentsTypes(), getArgTypes());
|
||||
}
|
||||
@ -295,6 +287,10 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
return retType;
|
||||
}
|
||||
|
||||
public void updateReturnType(ArgType type) {
|
||||
this.retType = type;
|
||||
}
|
||||
|
||||
public boolean isVoidReturn() {
|
||||
return mthInfo.getReturnType().equals(ArgType.VOID);
|
||||
}
|
||||
@ -332,130 +328,6 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
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();
|
||||
}
|
||||
@ -472,10 +344,6 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
return noCode;
|
||||
}
|
||||
|
||||
public int getCodeSize() {
|
||||
return codeSize;
|
||||
}
|
||||
|
||||
public InsnNode[] getInstructions() {
|
||||
return instructions;
|
||||
}
|
||||
@ -602,11 +470,12 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
@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()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -655,11 +524,8 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
return regsCount;
|
||||
}
|
||||
|
||||
public int getDebugInfoOffset() {
|
||||
return debugInfoOffset;
|
||||
}
|
||||
|
||||
public SSAVar makeNewSVar(int regNum, @NotNull RegisterArg assignArg) {
|
||||
public SSAVar makeNewSVar(@NotNull RegisterArg assignArg) {
|
||||
int regNum = assignArg.getRegNum();
|
||||
return makeNewSVar(regNum, getNextSVarVersion(regNum), assignArg);
|
||||
}
|
||||
|
||||
@ -709,14 +575,9 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
this.region = region;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DexNode dex() {
|
||||
return parentClass.dex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootNode root() {
|
||||
return dex().root();
|
||||
return parentClass.root();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -724,31 +585,9 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
return "method";
|
||||
}
|
||||
|
||||
public void addWarn(String warnStr) {
|
||||
ErrorsCounter.methodWarn(this, warnStr);
|
||||
}
|
||||
|
||||
public void addWarnComment(String warn) {
|
||||
addWarnComment(warn, null);
|
||||
}
|
||||
|
||||
public void addWarnComment(String warn, @Nullable Throwable exc) {
|
||||
String commentStr = "JADX WARN: " + warn;
|
||||
addAttr(AType.COMMENTS, commentStr);
|
||||
if (exc != null) {
|
||||
LOG.warn("{} in {}", warn, this, exc);
|
||||
} else {
|
||||
LOG.warn("{} in {}", warn, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void addComment(String commentStr) {
|
||||
addAttr(AType.COMMENTS, commentStr);
|
||||
LOG.info("{} in {}", commentStr, this);
|
||||
}
|
||||
|
||||
public void addError(String errStr, Throwable e) {
|
||||
ErrorsCounter.methodError(this, errStr, e);
|
||||
@Override
|
||||
public Path getInputPath() {
|
||||
return parentClass.getInputPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -757,7 +596,12 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
}
|
||||
|
||||
public long getMethodCodeOffset() {
|
||||
return noCode ? 0 : methodData.getCodeOffset();
|
||||
return noCode ? 0 : codeReader.getCodeOffset();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public IDebugInfo getDebugInfo() {
|
||||
return noCode ? null : codeReader.getDebugInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -783,6 +627,18 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public ICodeReader getCodeReader() {
|
||||
return codeReader;
|
||||
}
|
||||
|
||||
public List<MethodNode> getUseIn() {
|
||||
return useIn;
|
||||
}
|
||||
|
||||
public void setUseIn(List<MethodNode> useIn) {
|
||||
this.useIn = useIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mthInfo.hashCode();
|
||||
@ -800,6 +656,11 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl
|
||||
return mthInfo.equals(other.mthInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull MethodNode o) {
|
||||
return mthInfo.compareTo(o.mthInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return parentClass + "." + mthInfo.getName()
|
||||
|
@ -4,13 +4,10 @@ public enum ProcessState {
|
||||
NOT_LOADED,
|
||||
LOADED,
|
||||
PROCESS_STARTED,
|
||||
PROCESS_COMPLETE;
|
||||
|
||||
public boolean isLoaded() {
|
||||
return this != NOT_LOADED;
|
||||
}
|
||||
PROCESS_COMPLETE,
|
||||
GENERATED;
|
||||
|
||||
public boolean isProcessed() {
|
||||
return this == PROCESS_COMPLETE;
|
||||
return this == PROCESS_COMPLETE || this == GENERATED;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -24,15 +28,15 @@ import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.utils.MethodUtils;
|
||||
import jadx.core.dex.nodes.utils.TypeUtils;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.typeinference.TypeCompare;
|
||||
import jadx.core.dex.visitors.typeinference.TypeUpdate;
|
||||
import jadx.core.utils.CacheStorage;
|
||||
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 +57,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 +75,46 @@ 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 = ClassNode.addSyntheticClass(this, name, classData.getAccessFlags());
|
||||
ErrorsCounter.error(clsNode, "Load error", exc);
|
||||
}
|
||||
|
||||
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 +133,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 +152,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 +162,71 @@ 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(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);
|
||||
}
|
||||
}
|
||||
public void runPreDecompileStage() {
|
||||
for (IDexTreeVisitor pass : Jadx.getPreDecompilePassesList()) {
|
||||
try {
|
||||
pass.init(this);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
|
||||
}
|
||||
for (ClassNode cls : classes) {
|
||||
DepthTraversal.visit(pass, cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<ClassNode> getClasses() {
|
||||
return classes;
|
||||
}
|
||||
|
||||
public List<ClassNode> getClassesWithoutInner() {
|
||||
return getClasses(false);
|
||||
}
|
||||
|
||||
public List<ClassNode> getClasses(boolean includeInner) {
|
||||
if (includeInner) {
|
||||
return classes;
|
||||
}
|
||||
List<ClassNode> notInnerClasses = new ArrayList<>();
|
||||
for (ClassNode cls : classes) {
|
||||
if (!cls.getClassInfo().isInner()) {
|
||||
notInnerClasses.add(cls);
|
||||
}
|
||||
}
|
||||
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 +240,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 +263,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 +290,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 +338,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 +383,6 @@ public class RootNode {
|
||||
}
|
||||
}
|
||||
|
||||
public List<DexNode> getDexNodes() {
|
||||
return dexNodes;
|
||||
}
|
||||
|
||||
public ClspGraph getClsp() {
|
||||
return clsp;
|
||||
}
|
||||
@ -307,6 +424,10 @@ public class RootNode {
|
||||
return typeUpdate;
|
||||
}
|
||||
|
||||
public TypeCompare getTypeCompare() {
|
||||
return typeUpdate.getTypeCompare();
|
||||
}
|
||||
|
||||
public ICodeCache getCodeCache() {
|
||||
return codeCache;
|
||||
}
|
||||
|
@ -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 {
|
||||
@ -33,14 +35,25 @@ public class SignatureParser {
|
||||
mark = 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public static SignatureParser fromNode(IAttributeNode node) {
|
||||
Annotation a = node.getAnnotation(Consts.DALVIK_SIGNATURE);
|
||||
String signature = getSignature(node);
|
||||
if (signature == null) {
|
||||
return null;
|
||||
}
|
||||
return new SignatureParser(signature);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public static String getSignature(IAttributeNode node) {
|
||||
IAnnotation a = node.getAnnotation(Consts.DALVIK_SIGNATURE);
|
||||
if (a == null) {
|
||||
return null;
|
||||
}
|
||||
String signature = mergeSignature((List<String>) a.getDefaultValue());
|
||||
return new SignatureParser(signature);
|
||||
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,19 +2,23 @@ package jadx.core.dex.nodes.utils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.clsp.ClspClass;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public class TypeUtils {
|
||||
@ -24,7 +28,6 @@ public class TypeUtils {
|
||||
this.root = rootNode;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<GenericTypeParameter> getClassGenerics(ArgType type) {
|
||||
ClassNode classNode = root.resolveClass(type);
|
||||
if (classNode != null) {
|
||||
@ -38,6 +41,38 @@ public class TypeUtils {
|
||||
return generics == null ? Collections.emptyList() : generics;
|
||||
}
|
||||
|
||||
public Set<ArgType> getKnownTypeVarsAtMethod(MethodNode mth) {
|
||||
MethodTypeVarsAttr typeVarsAttr = mth.get(AType.METHOD_TYPE_VARS);
|
||||
if (typeVarsAttr != null) {
|
||||
return typeVarsAttr.getTypeVars();
|
||||
}
|
||||
Set<ArgType> typeVars = collectKnownTypeVarsAtMethod(mth);
|
||||
mth.addAttr(new MethodTypeVarsAttr(typeVars));
|
||||
return typeVars;
|
||||
}
|
||||
|
||||
private static Set<ArgType> collectKnownTypeVarsAtMethod(MethodNode mth) {
|
||||
Set<ArgType> typeVars = new HashSet<>();
|
||||
ClassNode declCls = mth.getParentClass();
|
||||
addTypeVarsFromCls(typeVars, declCls);
|
||||
declCls.visitParentClasses(parent -> addTypeVarsFromCls(typeVars, parent));
|
||||
|
||||
for (GenericTypeParameter typeParameter : mth.getTypeParameters()) {
|
||||
typeVars.add(typeParameter.getTypeVariable());
|
||||
}
|
||||
return typeVars.isEmpty() ? Collections.emptySet() : typeVars;
|
||||
}
|
||||
|
||||
private static void addTypeVarsFromCls(Set<ArgType> typeVars, ClassNode parentCls) {
|
||||
List<GenericTypeParameter> typeParameters = parentCls.getGenericTypeParameters();
|
||||
if (typeParameters.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (GenericTypeParameter typeParameter : typeParameters) {
|
||||
typeVars.add(typeParameter.getTypeVariable());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace generic types in {@code typeWithGeneric} using instance types
|
||||
* <br>
|
||||
@ -50,13 +85,14 @@ public class TypeUtils {
|
||||
*/
|
||||
@Nullable
|
||||
public ArgType replaceClassGenerics(ArgType instanceType, ArgType typeWithGeneric) {
|
||||
if (typeWithGeneric != null) {
|
||||
Map<ArgType, ArgType> replaceMap = getTypeVariablesMapping(instanceType);
|
||||
if (!replaceMap.isEmpty()) {
|
||||
return replaceTypeVariablesUsingMap(typeWithGeneric, replaceMap);
|
||||
}
|
||||
if (typeWithGeneric == null) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
Map<ArgType, ArgType> replaceMap = getTypeVariablesMapping(instanceType);
|
||||
if (replaceMap.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return replaceTypeVariablesUsingMap(typeWithGeneric, replaceMap);
|
||||
}
|
||||
|
||||
public Map<ArgType, ArgType> getTypeVariablesMapping(ArgType clsType) {
|
||||
@ -110,9 +146,19 @@ public class TypeUtils {
|
||||
|
||||
@Nullable
|
||||
public ArgType replaceTypeVariablesUsingMap(ArgType replaceType, Map<ArgType, ArgType> replaceMap) {
|
||||
if (replaceMap.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (replaceType.isGenericType()) {
|
||||
return replaceMap.get(replaceType);
|
||||
}
|
||||
if (replaceType.isArray()) {
|
||||
ArgType replaced = replaceTypeVariablesUsingMap(replaceType.getArrayElement(), replaceMap);
|
||||
if (replaced == null) {
|
||||
return null;
|
||||
}
|
||||
return ArgType.array(replaced);
|
||||
}
|
||||
|
||||
ArgType wildcardType = replaceType.getWildcardType();
|
||||
if (wildcardType != null && wildcardType.containsTypeVariable()) {
|
||||
|
@ -1,25 +1,40 @@
|
||||
package jadx.core.dex.regions.loops;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
public final class ForEachLoop extends LoopType {
|
||||
private final RegisterArg varArg;
|
||||
private final InsnArg iterableArg;
|
||||
private final InsnNode varArgInsn;
|
||||
private final InsnNode iterableArgInsn;
|
||||
|
||||
public ForEachLoop(RegisterArg varArg, InsnArg iterableArg) {
|
||||
this.varArg = varArg;
|
||||
this.iterableArg = iterableArg;
|
||||
// store for-each args in fake instructions to
|
||||
// save code semantics and allow args manipulations like args inlining
|
||||
varArgInsn = new InsnNode(InsnType.REGION_ARG, 1);
|
||||
varArgInsn.add(AFlag.DONT_INLINE);
|
||||
varArgInsn.setResult(varArg.duplicate());
|
||||
|
||||
iterableArgInsn = new InsnNode(InsnType.REGION_ARG, 1);
|
||||
iterableArgInsn.add(AFlag.DONT_INLINE);
|
||||
iterableArgInsn.addArg(iterableArg.duplicate());
|
||||
|
||||
// will be declared at codegen
|
||||
varArg.getSVar().getCodeVar().setDeclared(true);
|
||||
getVarArg().getSVar().getCodeVar().setDeclared(true);
|
||||
}
|
||||
|
||||
public void injectFakeInsns(LoopRegion loopRegion) {
|
||||
loopRegion.getInfo().getPreHeader().getInstructions().add(iterableArgInsn);
|
||||
loopRegion.getHeader().getInstructions().add(0, varArgInsn);
|
||||
}
|
||||
|
||||
public RegisterArg getVarArg() {
|
||||
return varArg;
|
||||
return varArgInsn.getResult();
|
||||
}
|
||||
|
||||
public InsnArg getIterableArg() {
|
||||
return iterableArg;
|
||||
return iterableArgInsn.getArg(0);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -8,13 +8,17 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.nodes.utils.MethodUtils;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "Attach Method Details",
|
||||
desc = "Attach method details for invoke instructions",
|
||||
runBefore = {
|
||||
CodeShrinkVisitor.class
|
||||
CodeShrinkVisitor.class,
|
||||
TypeInferenceVisitor.class,
|
||||
MethodInvokeVisitor.class,
|
||||
OverrideMethodVisitor.class
|
||||
}
|
||||
)
|
||||
public class AttachMethodDetails extends AbstractVisitor {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -93,14 +93,19 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
String s = ((ConstStringNode) insn).getString();
|
||||
FieldNode f = mth.getParentClass().getConstField(s);
|
||||
if (f == null) {
|
||||
InsnNode copy = insn.copy();
|
||||
copy.setResult(null);
|
||||
InsnNode copy = insn.copyWithoutResult();
|
||||
constArg = InsnArg.wrapArg(copy);
|
||||
} else {
|
||||
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||
constArg = InsnArg.wrapArg(constGet);
|
||||
constArg.setType(ArgType.STRING);
|
||||
}
|
||||
} else if (insnType == InsnType.CONST_CLASS) {
|
||||
if (sVar.isUsedInPhi()) {
|
||||
return;
|
||||
}
|
||||
constArg = InsnArg.wrapArg(insn.copyWithoutResult());
|
||||
constArg.setType(ArgType.CLASS);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ import jadx.core.utils.InsnRemover;
|
||||
@JadxVisitor(
|
||||
name = "ConstructorVisitor",
|
||||
desc = "Replace invoke with constructor call",
|
||||
runAfter = SSATransform.class,
|
||||
runAfter = { SSATransform.class, MoveInlineVisitor.class },
|
||||
runBefore = TypeInferenceVisitor.class
|
||||
)
|
||||
public class ConstructorVisitor extends AbstractVisitor {
|
||||
@ -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;
|
||||
}
|
||||
|
@ -1,144 +0,0 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
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.utils.exceptions.JadxException;
|
||||
|
||||
public class DependencyCollector extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
DexNode dex = cls.dex();
|
||||
Set<ClassNode> depSet = new HashSet<>();
|
||||
processClass(cls, dex, depSet);
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
processClass(inner, dex, depSet);
|
||||
}
|
||||
depSet.remove(cls);
|
||||
|
||||
List<ClassNode> depList = new ArrayList<>(depSet);
|
||||
depList.sort(Comparator.comparing(c -> c.getClassInfo().getFullName()));
|
||||
cls.setDependencies(depList);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void processClass(ClassNode cls, DexNode dex, Set<ClassNode> depList) {
|
||||
addDep(dex, depList, cls.getSuperClass());
|
||||
for (ArgType iType : cls.getInterfaces()) {
|
||||
addDep(dex, depList, iType);
|
||||
}
|
||||
for (FieldNode fieldNode : cls.getFields()) {
|
||||
addDep(dex, 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());
|
||||
}
|
||||
}
|
||||
// TODO: process annotations and generics
|
||||
for (MethodNode methodNode : cls.getMethods()) {
|
||||
if (methodNode.isNoCode() || methodNode.contains(AType.JADX_ERROR)) {
|
||||
continue;
|
||||
}
|
||||
processMethod(dex, depList, methodNode);
|
||||
}
|
||||
}
|
||||
|
||||
private static void processMethod(DexNode dex, Set<ClassNode> depList, MethodNode methodNode) {
|
||||
addDep(dex, depList, methodNode.getParentClass());
|
||||
addDep(dex, depList, methodNode.getReturnType());
|
||||
for (ArgType arg : methodNode.getMethodInfo().getArgumentsTypes()) {
|
||||
addDep(dex, depList, arg);
|
||||
}
|
||||
for (BlockNode block : methodNode.getBasicBlocks()) {
|
||||
for (InsnNode insnNode : block.getInstructions()) {
|
||||
processInsn(dex, depList, insnNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add custom instructions processing
|
||||
private static void processInsn(DexNode dex, Set<ClassNode> depList, InsnNode insnNode) {
|
||||
RegisterArg result = insnNode.getResult();
|
||||
if (result != null) {
|
||||
addDep(dex, depList, result.getType());
|
||||
}
|
||||
for (InsnArg arg : insnNode.getArguments()) {
|
||||
if (arg.isInsnWrap()) {
|
||||
processInsn(dex, depList, ((InsnWrapArg) arg).getWrapInsn());
|
||||
} else {
|
||||
addDep(dex, depList, arg.getType());
|
||||
}
|
||||
}
|
||||
processCustomInsn(dex, depList, insnNode);
|
||||
}
|
||||
|
||||
private static void processCustomInsn(DexNode dex, Set<ClassNode> depList, InsnNode insn) {
|
||||
if (insn instanceof IndexInsnNode) {
|
||||
Object index = ((IndexInsnNode) insn).getIndex();
|
||||
if (index instanceof FieldInfo) {
|
||||
addDep(dex, depList, ((FieldInfo) index).getDeclClass());
|
||||
} else if (index instanceof ArgType) {
|
||||
addDep(dex, 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);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addDep(DexNode dex, Set<ClassNode> depList, ArgType type) {
|
||||
if (type != null) {
|
||||
if (type.isObject() && !type.isGenericType()) {
|
||||
addDep(dex, depList, ClassInfo.fromType(dex.root(), type));
|
||||
ArgType[] genericTypes = type.getGenericTypes();
|
||||
if (type.isGeneric() && genericTypes != null) {
|
||||
for (ArgType argType : genericTypes) {
|
||||
addDep(dex, depList, argType);
|
||||
}
|
||||
}
|
||||
} else if (type.isArray()) {
|
||||
addDep(dex, depList, type.getArrayRootElement());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addDep(DexNode dex, Set<ClassNode> depList, ClassInfo clsInfo) {
|
||||
if (clsInfo != null) {
|
||||
ClassNode node = dex.resolveClass(clsInfo);
|
||||
addDep(dex, depList, node);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addDep(DexNode dex, Set<ClassNode> depList, ClassNode clsNode) {
|
||||
if (clsNode != null) {
|
||||
// add only top classes
|
||||
depList.add(clsNode.getTopParentClass());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,8 @@
|
||||
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;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
|
||||
public class DepthTraversal {
|
||||
|
||||
@ -15,28 +12,19 @@ public class DepthTraversal {
|
||||
cls.getInnerClasses().forEach(inCls -> visit(visitor, inCls));
|
||||
cls.getMethods().forEach(mth -> visit(visitor, mth));
|
||||
}
|
||||
} catch (StackOverflowError e) {
|
||||
ErrorsCounter.classError(cls, "StackOverflow in pass: " + visitor.getClass().getSimpleName(), new JadxOverflowException(""));
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.classError(cls,
|
||||
e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
|
||||
} catch (StackOverflowError | Exception e) {
|
||||
cls.addError(e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void visit(IDexTreeVisitor visitor, MethodNode mth) {
|
||||
if (mth.contains(AType.JADX_ERROR)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
visitor.visit(mth);
|
||||
if (DebugChecks.checksEnabled) {
|
||||
DebugChecks.runChecksAfterVisitor(mth, visitor);
|
||||
}
|
||||
} catch (StackOverflowError e) {
|
||||
ErrorsCounter.methodError(mth, "StackOverflow in pass: " + visitor.getClass().getSimpleName(), new JadxOverflowException(""));
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.methodError(mth,
|
||||
e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
|
||||
} catch (StackOverflowError | Exception e) {
|
||||
mth.addError(e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,8 @@ import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP;
|
||||
|
||||
public class DotGraphVisitor extends AbstractVisitor {
|
||||
|
||||
private static final String NL = "\\l";
|
||||
@ -272,7 +274,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
} else {
|
||||
CodeWriter code = new CodeWriter();
|
||||
List<InsnNode> instructions = block.getInstructions();
|
||||
MethodGen.addFallbackInsns(code, mth, instructions.toArray(new InsnNode[0]), false);
|
||||
MethodGen.addFallbackInsns(code, mth, instructions.toArray(new InsnNode[0]), BLOCK_DUMP);
|
||||
String str = escape(code.newLine().toString());
|
||||
if (str.startsWith(NL)) {
|
||||
str = str.substring(NL.length());
|
||||
@ -297,6 +299,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
.replace("\"", "\\\"")
|
||||
.replace("-", "\\-")
|
||||
.replace("|", "\\|")
|
||||
.replace(CodeWriter.NL, NL)
|
||||
.replace("\n", NL);
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,31 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
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;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
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;
|
||||
@ -32,16 +35,20 @@ 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;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.BlockInsnPair;
|
||||
import jadx.core.utils.InsnRemover;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import static jadx.core.utils.InsnUtils.checkInsnType;
|
||||
import static jadx.core.utils.InsnUtils.getSingleArg;
|
||||
import static jadx.core.utils.InsnUtils.getWrappedInsn;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "EnumVisitor",
|
||||
desc = "Restore enum classes",
|
||||
@ -50,12 +57,24 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
)
|
||||
public class EnumVisitor extends AbstractVisitor {
|
||||
|
||||
private MethodInfo enumValueOfMth;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
enumValueOfMth = MethodInfo.fromDetails(
|
||||
root,
|
||||
ClassInfo.fromType(root, ArgType.ENUM),
|
||||
"valueOf",
|
||||
Arrays.asList(ArgType.CLASS, ArgType.STRING),
|
||||
ArgType.ENUM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
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");
|
||||
}
|
||||
}
|
||||
@ -132,41 +151,56 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
toRemove.add(valuesInitInsn);
|
||||
|
||||
// all checks complete, perform transform
|
||||
EnumClassAttr attr = new EnumClassAttr(enumFields.size());
|
||||
EnumClassAttr attr = new EnumClassAttr(enumFields);
|
||||
attr.setStaticMethod(classInitMth);
|
||||
attr.getFields().addAll(enumFields);
|
||||
cls.addAttr(attr);
|
||||
|
||||
for (EnumField field : attr.getFields()) {
|
||||
ConstructorInsn co = field.getConstrInsn();
|
||||
FieldNode fieldNode = field.getField();
|
||||
for (EnumField enumField : attr.getFields()) {
|
||||
ConstructorInsn co = enumField.getConstrInsn();
|
||||
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)
|
||||
&& cls.root().getArgs().isRenameValid()) {
|
||||
fieldNode.getFieldInfo().setAlias(name);
|
||||
}
|
||||
if (!co.getClassType().equals(cls.getClassInfo())) {
|
||||
// enum contains additional methods
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
processEnumInnerCls(co, field, innerCls);
|
||||
}
|
||||
}
|
||||
fieldNode.add(AFlag.DONT_GENERATE);
|
||||
processConstructorInsn(cls, enumField, classInitMth, staticBlock);
|
||||
}
|
||||
|
||||
valuesField.add(AFlag.DONT_GENERATE);
|
||||
enumFields.forEach(f -> f.getField().add(AFlag.DONT_GENERATE));
|
||||
InsnRemover.removeAllAndUnbind(classInitMth, staticBlock, toRemove);
|
||||
if (classInitMth.countInsns() == 0) {
|
||||
classInitMth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
removeEnumMethods(cls, clsType);
|
||||
removeEnumMethods(cls, clsType, valuesField);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void processConstructorInsn(ClassNode cls, EnumField enumField, MethodNode classInitMth, BlockNode staticBlock) {
|
||||
ConstructorInsn co = enumField.getConstrInsn();
|
||||
ClassInfo enumClsInfo = co.getClassType();
|
||||
if (!enumClsInfo.equals(cls.getClassInfo())) {
|
||||
ClassNode enumCls = cls.root().resolveClass(enumClsInfo);
|
||||
if (enumCls != null) {
|
||||
processEnumCls(enumField, enumCls);
|
||||
cls.addInlinedClass(enumCls);
|
||||
}
|
||||
}
|
||||
List<RegisterArg> regs = new ArrayList<>();
|
||||
co.getRegisterArgs(regs);
|
||||
if (!regs.isEmpty()) {
|
||||
cls.addWarnComment("Init of enum " + enumField.getField().getName() + " can be incorrect");
|
||||
}
|
||||
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
|
||||
if (ctrMth != null) {
|
||||
markArgsForSkip(ctrMth);
|
||||
}
|
||||
InsnRemover.removeWithoutUnbind(classInitMth, staticBlock, co);
|
||||
}
|
||||
|
||||
private BlockInsnPair getValuesInitInsn(MethodNode classInitMth, FieldNode valuesField) {
|
||||
FieldInfo searchField = valuesField.getFieldInfo();
|
||||
for (BlockNode blockNode : classInitMth.getBasicBlocks()) {
|
||||
@ -222,7 +256,6 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
}
|
||||
toRemove.add(sgetInsn);
|
||||
toRemove.add(sputInsn);
|
||||
toRemove.add(co);
|
||||
return createEnumFieldByConstructor(cls, enumFieldNode, co);
|
||||
}
|
||||
|
||||
@ -232,7 +265,7 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
if (ssaVar.getUseCount() == 1) {
|
||||
return null;
|
||||
}
|
||||
final InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn();
|
||||
InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn();
|
||||
if (sputInsn == null || sputInsn.getType() != InsnType.SPUT) {
|
||||
return null;
|
||||
}
|
||||
@ -258,15 +291,18 @@ 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;
|
||||
}
|
||||
int startArg = co.getArgsCount() == 1 ? 1 : 2;
|
||||
return new EnumField(enumFieldNode, co, startArg);
|
||||
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
|
||||
if (ctrMth == null) {
|
||||
return null;
|
||||
}
|
||||
return new EnumField(enumFieldNode, co);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -283,38 +319,68 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: detect these methods by analyzing method instructions
|
||||
private void removeEnumMethods(ClassNode cls, ArgType clsType) {
|
||||
String enumConstructor = "<init>(Ljava/lang/String;I)V";
|
||||
String enumConstructorAlt = "<init>(Ljava/lang/String;)V";
|
||||
String valuesOfMethod = "valueOf(Ljava/lang/String;)" + TypeGen.signature(clsType);
|
||||
private void removeEnumMethods(ClassNode cls, ArgType clsType, FieldNode valuesField) {
|
||||
String valuesMethod = "values()" + TypeGen.signature(ArgType.array(clsType));
|
||||
FieldInfo valuesFieldInfo = valuesField.getFieldInfo();
|
||||
|
||||
// remove synthetic methods
|
||||
// remove compiler generated methods
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
MethodInfo mi = mth.getMethodInfo();
|
||||
if (mi.isClassInit()) {
|
||||
continue;
|
||||
}
|
||||
String shortId = mi.getShortId();
|
||||
boolean isSynthetic = mth.getAccessFlags().isSynthetic();
|
||||
if (mi.isConstructor() && !isSynthetic) {
|
||||
if (shortId.equals(enumConstructor)
|
||||
|| shortId.equals(enumConstructorAlt)) {
|
||||
if (mi.isConstructor()) {
|
||||
if (isDefaultConstructor(mth, shortId)) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
} else if (isSynthetic
|
||||
|| shortId.equals(valuesMethod)
|
||||
|| shortId.equals(valuesOfMethod)) {
|
||||
markArgsForSkip(mth);
|
||||
} else if (shortId.equals(valuesMethod)
|
||||
|| usesValuesField(mth, valuesFieldInfo)
|
||||
|| simpleValueOfMth(mth, clsType)) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void processEnumInnerCls(ConstructorInsn co, EnumField field, ClassNode innerCls) {
|
||||
if (!innerCls.getClassInfo().equals(co.getClassType())) {
|
||||
return;
|
||||
private void markArgsForSkip(MethodNode mth) {
|
||||
// skip first and second args
|
||||
SkipMethodArgsAttr.skipArg(mth, 0);
|
||||
if (mth.getMethodInfo().getArgsCount() > 1) {
|
||||
SkipMethodArgsAttr.skipArg(mth, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDefaultConstructor(MethodNode mth, String shortId) {
|
||||
boolean defaultId = shortId.equals("<init>(Ljava/lang/String;I)V")
|
||||
|| shortId.equals("<init>(Ljava/lang/String;)V");
|
||||
if (defaultId) {
|
||||
// check content
|
||||
return mth.countInsns() == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean simpleValueOfMth(MethodNode mth, ArgType clsType) {
|
||||
InsnNode returnInsn = InsnUtils.searchSingleReturnInsn(mth, insn -> insn.getArgsCount() == 1);
|
||||
if (returnInsn == null) {
|
||||
return false;
|
||||
}
|
||||
InsnNode wrappedInsn = getWrappedInsn(getSingleArg(returnInsn));
|
||||
IndexInsnNode castInsn = (IndexInsnNode) checkInsnType(wrappedInsn, InsnType.CHECK_CAST);
|
||||
if (castInsn != null && Objects.equals(castInsn.getIndex(), clsType)) {
|
||||
InvokeNode invokeInsn = (InvokeNode) checkInsnType(getWrappedInsn(getSingleArg(castInsn)), InsnType.INVOKE);
|
||||
return invokeInsn != null && invokeInsn.getCallMth().equals(enumValueOfMth);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean usesValuesField(MethodNode mth, FieldInfo valuesFieldInfo) {
|
||||
Predicate<InsnNode> insnTest = insn -> Objects.equals(((IndexInsnNode) insn).getIndex(), valuesFieldInfo);
|
||||
return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null;
|
||||
}
|
||||
|
||||
private static void processEnumCls(EnumField field, ClassNode innerCls) {
|
||||
// remove constructor, because it is anonymous class
|
||||
for (MethodNode innerMth : innerCls.getMethods()) {
|
||||
if (innerMth.getAccessFlags().isConstructor()) {
|
||||
@ -347,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;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user