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:
Skylot 2020-07-03 16:39:02 +01:00
commit 34692c41f2
313 changed files with 11035 additions and 3972 deletions

View File

@ -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 :)

View File

@ -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
View File

@ -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

View File

@ -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.

View File

@ -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*

View File

@ -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")

Binary file not shown.

View File

@ -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
View File

@ -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"

View File

@ -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'
}

View File

@ -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;
}

View File

@ -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(", "));
}

View File

@ -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/*'
}

View File

@ -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;
}

View File

@ -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

View File

@ -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() {

View File

@ -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;
}

View File

@ -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";

View File

@ -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;
}

View File

@ -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) {

View File

@ -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());
}

View File

@ -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);
}
}
}

View File

@ -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());
}
}
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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));

View File

@ -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) {

View File

@ -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;

View File

@ -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);

View File

@ -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());

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -60,7 +60,6 @@ public enum AFlag {
FALL_THROUGH,
EXPLICIT_GENERICS,
VARARG_CALL,
/**

View File

@ -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<>();

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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() {

View File

@ -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);

View File

@ -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 + ']';
}
}

View File

@ -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();
}

View File

@ -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() {

View File

@ -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() {

View File

@ -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 + '}';
}
}

View File

@ -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;
}

View File

@ -72,6 +72,10 @@ public class LoopInfo {
return edges;
}
public BlockNode getPreHeader() {
return BlockUtils.selectOther(end, start.getPredecessors());
}
public int getId() {
return id;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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;

View File

@ -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;

View File

@ -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() + ')';
}
}

View File

@ -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));
}

View File

@ -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;
}

View File

@ -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() {

View File

@ -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);

View File

@ -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) {

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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));

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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();
}
}

View File

@ -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)) {

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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) {

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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;
}
}

View 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

View File

@ -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();
}

View File

@ -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);

View File

@ -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()

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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() {

View File

@ -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;
}
}

View File

@ -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()) {

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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());
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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