Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ec127c3cb | |||
| 7a3b7c55c9 | |||
| b66293a2f7 | |||
| abcaafa89a | |||
| cf25cc4faa | |||
| b57001d4a7 | |||
| 83decc2473 | |||
| 92faa569be | |||
| c5b731169d | |||
| f0a8ef81d3 | |||
| 994973ac01 | |||
| c9622c0771 | |||
| 8551c6c903 | |||
| 9a9ac4308e | |||
| e784cbdd09 | |||
| 2744c4bfb6 | |||
| e4f4c1b84a | |||
| e5fa818b5c | |||
| b22b554a69 | |||
| e9b8060889 | |||
| 1c2b2c072c | |||
| 3d451912ee | |||
| fe91d774fa | |||
| d8306cb1c0 | |||
| 909cf0a576 | |||
| 8fe1ee11e4 | |||
| d2bef108f5 | |||
| ba8ba504b1 | |||
| 481b5abf85 | |||
| c4e1d9445a | |||
| cb03532b76 | |||
| c93e9eea14 | |||
| 9a67b19973 | |||
| 95c75bed1e | |||
| b008568a5c | |||
| 94fb91cec6 | |||
| c54dd77f35 | |||
| 17fbc99f29 | |||
| 21dd17290b | |||
| dc73fc92be | |||
| 592215db66 | |||
| fb318e3bd9 | |||
| 5f3c8816a3 | |||
| 6016b902c7 | |||
| 5852da1e3d | |||
| 502fd069be | |||
| fad9e7b827 | |||
| 35116d0b1a | |||
| 3b781e41ad | |||
| a3e9744364 | |||
| 7030daeccd | |||
| e7151ad7b2 | |||
| ed2a3c8458 | |||
| 779f75cd52 | |||
| 54683e3198 |
@@ -0,0 +1,5 @@
|
|||||||
|
jdk:
|
||||||
|
- openjdk11
|
||||||
|
install:
|
||||||
|
- echo "Jitpack is not supported. Use artifacts from Maven Central (https://search.maven.org/search?q=jadx), check usage help at https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library"
|
||||||
|
- ./gradlew intentional-fail
|
||||||
@@ -84,6 +84,11 @@ options:
|
|||||||
--output-format - can be 'java' or 'json', default: java
|
--output-format - can be 'java' or 'json', default: java
|
||||||
-e, --export-gradle - save as android gradle project
|
-e, --export-gradle - save as android gradle project
|
||||||
-j, --threads-count - processing threads count, default: 4
|
-j, --threads-count - processing threads count, default: 4
|
||||||
|
-m, --decompilation-mode - code output mode:
|
||||||
|
'auto' - trying best options (default)
|
||||||
|
'restructure' - restore code structure (normal java code)
|
||||||
|
'simple' - simplified instructions (linear, with goto's)
|
||||||
|
'fallback' - raw instructions without modifications
|
||||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||||
--no-imports - disable use of imports, always write entire package name
|
--no-imports - disable use of imports, always write entire package name
|
||||||
--no-debug-info - disable debug info
|
--no-debug-info - disable debug info
|
||||||
@@ -115,7 +120,7 @@ options:
|
|||||||
--fs-case-sensitive - treat filesystem as case sensitive, false by default
|
--fs-case-sensitive - treat filesystem as case sensitive, false by default
|
||||||
--cfg - save methods control flow graph to dot file
|
--cfg - save methods control flow graph to dot file
|
||||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
|
||||||
--use-dx - use dx/d8 to convert java bytecode
|
--use-dx - use dx/d8 to convert java bytecode
|
||||||
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
|
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
|
||||||
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
|
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
|
||||||
@@ -123,11 +128,20 @@ options:
|
|||||||
-q, --quiet - turn off output (set --log-level to QUIET)
|
-q, --quiet - turn off output (set --log-level to QUIET)
|
||||||
--version - print jadx version
|
--version - print jadx version
|
||||||
-h, --help - print this help
|
-h, --help - print this help
|
||||||
|
|
||||||
|
Plugin options (-P<name>=<value>):
|
||||||
|
1) dex-input (Load .dex and .apk files)
|
||||||
|
-Pdex-input.verify-checksum - Verify dex file checksum before load, values: [yes, no], default: yes
|
||||||
|
2) java-convert (Convert .jar and .class files to dex)
|
||||||
|
-Pjava-convert.mode - Convert mode, values: [dx, d8, both], default: both
|
||||||
|
-Pjava-convert.d8-desugar - Use desugar in d8, values: [yes, no], default: no
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
jadx -d out classes.dex
|
jadx -d out classes.dex
|
||||||
jadx --rename-flags "none" classes.dex
|
jadx --rename-flags "none" classes.dex
|
||||||
jadx --rename-flags "valid, printable" classes.dex
|
jadx --rename-flags "valid, printable" classes.dex
|
||||||
jadx --log-level ERROR app.apk
|
jadx --log-level ERROR app.apk
|
||||||
|
jadx -Pdex-input.verify-checksum=no app.apk
|
||||||
```
|
```
|
||||||
These options also worked on jadx-gui running from command line and override options from preferences dialog
|
These options also worked on jadx-gui running from command line and override options from preferences dialog
|
||||||
|
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<component type="desktop">
|
||||||
|
<id>com.github.skylot.jadx</id>
|
||||||
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
|
<project_license>Apache-2.0</project_license>
|
||||||
|
<name>JADX</name>
|
||||||
|
<summary>Dex to Java decompiler</summary>
|
||||||
|
<description>
|
||||||
|
<p>Command line and GUI tools for producing Java source code from Android Dex and Apk files</p>
|
||||||
|
<ul>
|
||||||
|
<li>decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files</li>
|
||||||
|
<li>decode AndroidManifest.xml and other resources from resources.arsc</li>
|
||||||
|
<li>deobfuscator included</li>
|
||||||
|
<li>view decompiled code with highlighted syntax</li>
|
||||||
|
<li>jump to declaration</li>
|
||||||
|
<li>find usage</li>
|
||||||
|
<li>full text search</li>
|
||||||
|
<li>smali debugger</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<image>https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png</image>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
<content_rating type="oars-1.1" />
|
||||||
|
<launchable type="desktop-id">com.github.skylot.jadx.desktop</launchable>
|
||||||
|
<url type="homepage">https://github.com/skylot/jadx</url>
|
||||||
|
<url type="bugtracker">https://github.com/skylot/jadx/issues</url>
|
||||||
|
<releases>
|
||||||
|
<release version="1.3.4" date="2022-03-20" />
|
||||||
|
</releases>
|
||||||
|
</component>
|
||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||||
id 'com.diffplug.spotless' version '6.2.2'
|
id 'com.diffplug.spotless' version '6.4.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||||
@@ -30,20 +30,20 @@ allprojects {
|
|||||||
implementation 'org.slf4j:slf4j-api:1.7.36'
|
implementation 'org.slf4j:slf4j-api:1.7.36'
|
||||||
compileOnly 'org.jetbrains:annotations:23.0.0'
|
compileOnly 'org.jetbrains:annotations:23.0.0'
|
||||||
|
|
||||||
testImplementation 'ch.qos.logback:logback-classic:1.2.10'
|
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||||
testImplementation 'org.mockito:mockito-core:4.3.1'
|
testImplementation 'org.mockito:mockito-core:4.4.0'
|
||||||
testImplementation 'org.assertj:assertj-core:3.22.0'
|
testImplementation 'org.assertj:assertj-core:3.22.0'
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
|
||||||
|
|
||||||
testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
|
|
||||||
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
|
maxParallelForks = Runtime.runtime.availableProcessors()
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ publishing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
signing {
|
signing {
|
||||||
|
required { gradle.taskGraph.hasTask("publish") }
|
||||||
sign publishing.publications.mavenJava
|
sign publishing.publications.mavenJava
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
org.gradle.warning.mode=all
|
org.gradle.warning.mode=all
|
||||||
|
org.gradle.parallel=true
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=8cc27038d5dbd815759851ba53e70cf62e481b87494cc97cfd97982ada5ba634
|
distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ dependencies {
|
|||||||
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||||
|
|
||||||
implementation 'com.beust:jcommander:1.82'
|
implementation 'com.beust:jcommander:1.82'
|
||||||
implementation 'ch.qos.logback:logback-classic:1.2.10'
|
implementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
applicationName = 'jadx'
|
applicationName = 'jadx'
|
||||||
mainClass.set('jadx.cli.JadxCLI')
|
mainClass.set('jadx.cli.JadxCLI')
|
||||||
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
|
applicationDefaultJvmArgs = ['-Xms128M', '-XX:MaxRAMPercentage=70.0', '-XX:+UseG1GC']
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationDistribution.with {
|
applicationDistribution.with {
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ import com.beust.jcommander.ParameterException;
|
|||||||
import com.beust.jcommander.Parameterized;
|
import com.beust.jcommander.Parameterized;
|
||||||
|
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.api.plugins.JadxPlugin;
|
||||||
|
import jadx.api.plugins.JadxPluginInfo;
|
||||||
|
import jadx.api.plugins.JadxPluginManager;
|
||||||
|
import jadx.api.plugins.options.JadxPluginOptions;
|
||||||
|
import jadx.api.plugins.options.OptionDescription;
|
||||||
|
|
||||||
public class JCommanderWrapper<T> {
|
public class JCommanderWrapper<T> {
|
||||||
private final JCommander jc;
|
private final JCommander jc;
|
||||||
@@ -70,24 +75,25 @@ public class JCommanderWrapper<T> {
|
|||||||
maxNamesLen = len;
|
maxNamesLen = len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
maxNamesLen += 3;
|
||||||
|
|
||||||
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
|
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
|
||||||
for (Field f : getFields(args.getClass())) {
|
for (Field f : getFields(args.getClass())) {
|
||||||
String name = f.getName();
|
String name = f.getName();
|
||||||
ParameterDescription p = paramsMap.get(name);
|
ParameterDescription p = paramsMap.get(name);
|
||||||
if (p == null) {
|
if (p == null || p.getParameter().hidden()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
StringBuilder opt = new StringBuilder();
|
StringBuilder opt = new StringBuilder();
|
||||||
opt.append(" ").append(p.getNames());
|
opt.append(" ").append(p.getNames());
|
||||||
String description = p.getDescription();
|
String description = p.getDescription();
|
||||||
addSpaces(opt, maxNamesLen - opt.length() + 3);
|
addSpaces(opt, maxNamesLen - opt.length());
|
||||||
if (description.contains("\n")) {
|
if (description.contains("\n")) {
|
||||||
String[] lines = description.split("\n");
|
String[] lines = description.split("\n");
|
||||||
opt.append("- ").append(lines[0]);
|
opt.append("- ").append(lines[0]);
|
||||||
for (int i = 1; i < lines.length; i++) {
|
for (int i = 1; i < lines.length; i++) {
|
||||||
opt.append('\n');
|
opt.append('\n');
|
||||||
addSpaces(opt, maxNamesLen + 5);
|
addSpaces(opt, maxNamesLen + 2);
|
||||||
opt.append(lines[i]);
|
opt.append(lines[i]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -99,11 +105,14 @@ public class JCommanderWrapper<T> {
|
|||||||
}
|
}
|
||||||
out.println(opt);
|
out.println(opt);
|
||||||
}
|
}
|
||||||
|
out.println(appendPluginOptions(maxNamesLen));
|
||||||
|
out.println();
|
||||||
out.println("Examples:");
|
out.println("Examples:");
|
||||||
out.println(" jadx -d out classes.dex");
|
out.println(" jadx -d out classes.dex");
|
||||||
out.println(" jadx --rename-flags \"none\" classes.dex");
|
out.println(" jadx --rename-flags \"none\" classes.dex");
|
||||||
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
|
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
|
||||||
out.println(" jadx --log-level ERROR app.apk");
|
out.println(" jadx --log-level ERROR app.apk");
|
||||||
|
out.println(" jadx -Pdex-input.verify-checksum=no app.apk");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,4 +154,46 @@ public class JCommanderWrapper<T> {
|
|||||||
str.append(' ');
|
str.append(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String appendPluginOptions(int maxNamesLen) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
JadxPluginManager pluginManager = new JadxPluginManager();
|
||||||
|
pluginManager.load();
|
||||||
|
int k = 1;
|
||||||
|
for (JadxPlugin plugin : pluginManager.getAllPlugins()) {
|
||||||
|
if (plugin instanceof JadxPluginOptions) {
|
||||||
|
if (appendPlugin(((JadxPluginOptions) plugin), sb, maxNamesLen, k)) {
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sb.length() == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return "\nPlugin options (-P<name>=<value>):" + sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean appendPlugin(JadxPluginOptions plugin, StringBuilder out, int maxNamesLen, int k) {
|
||||||
|
List<OptionDescription> descs = plugin.getOptionsDescriptions();
|
||||||
|
if (descs.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
JadxPluginInfo pluginInfo = plugin.getPluginInfo();
|
||||||
|
out.append("\n ").append(k).append(") ");
|
||||||
|
out.append(pluginInfo.getPluginId()).append(" (").append(pluginInfo.getDescription()).append(") ");
|
||||||
|
for (OptionDescription desc : descs) {
|
||||||
|
StringBuilder opt = new StringBuilder();
|
||||||
|
opt.append(" -P").append(desc.name());
|
||||||
|
addSpaces(opt, maxNamesLen - opt.length());
|
||||||
|
opt.append("- ").append(desc.description());
|
||||||
|
if (!desc.values().isEmpty()) {
|
||||||
|
opt.append(", values: ").append(desc.values());
|
||||||
|
}
|
||||||
|
if (desc.defaultValue() != null) {
|
||||||
|
opt.append(", default: ").append(desc.defaultValue());
|
||||||
|
}
|
||||||
|
out.append("\n").append(opt);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import jadx.api.JadxArgs;
|
|||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.impl.NoOpCodeCache;
|
import jadx.api.impl.NoOpCodeCache;
|
||||||
import jadx.api.impl.SimpleCodeWriter;
|
import jadx.api.impl.SimpleCodeWriter;
|
||||||
|
import jadx.cli.LogHelper.LogLevelEnum;
|
||||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ public class JadxCLI {
|
|||||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||||
result = 1;
|
result = 1;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("jadx error: {}", e.getMessage(), e);
|
LOG.error("Process error:", e);
|
||||||
result = 1;
|
result = 1;
|
||||||
} finally {
|
} finally {
|
||||||
FileUtils.deleteTempRootDir();
|
FileUtils.deleteTempRootDir();
|
||||||
@@ -38,11 +39,17 @@ public class JadxCLI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int processAndSave(JadxCLIArgs cliArgs) {
|
private static int processAndSave(JadxCLIArgs cliArgs) {
|
||||||
|
LogHelper.initLogLevel(cliArgs);
|
||||||
|
LogHelper.setLogLevelsForLoadingStage();
|
||||||
JadxArgs jadxArgs = cliArgs.toJadxArgs();
|
JadxArgs jadxArgs = cliArgs.toJadxArgs();
|
||||||
jadxArgs.setCodeCache(new NoOpCodeCache());
|
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||||
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
|
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
|
||||||
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
||||||
jadx.load();
|
jadx.load();
|
||||||
|
if (checkForErrors(jadx)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
LogHelper.setLogLevelsForDecompileStage();
|
||||||
if (!SingleClassMode.process(jadx, cliArgs)) {
|
if (!SingleClassMode.process(jadx, cliArgs)) {
|
||||||
save(jadx);
|
save(jadx);
|
||||||
}
|
}
|
||||||
@@ -57,14 +64,29 @@ public class JadxCLI {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean checkForErrors(JadxDecompiler jadx) {
|
||||||
|
if (jadx.getRoot().getClasses().isEmpty()) {
|
||||||
|
LOG.error("Load failed! No classes for decompile!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (jadx.getErrorsCount() > 0) {
|
||||||
|
LOG.error("Load with errors! Check log for details");
|
||||||
|
// continue processing
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static void save(JadxDecompiler jadx) {
|
private static void save(JadxDecompiler jadx) {
|
||||||
if (LogHelper.getLogLevel() == LogHelper.LogLevelEnum.QUIET) {
|
if (LogHelper.getLogLevel() == LogLevelEnum.QUIET) {
|
||||||
jadx.save();
|
jadx.save();
|
||||||
} else {
|
} else {
|
||||||
jadx.save(500, (done, total) -> {
|
jadx.save(500, (done, total) -> {
|
||||||
int progress = (int) (done * 100.0 / total);
|
int progress = (int) (done * 100.0 / total);
|
||||||
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
|
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
|
||||||
});
|
});
|
||||||
|
// dumb line clear :)
|
||||||
|
System.out.print(" \r");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,20 @@ package jadx.cli;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.beust.jcommander.DynamicParameter;
|
||||||
import com.beust.jcommander.IStringConverter;
|
import com.beust.jcommander.IStringConverter;
|
||||||
import com.beust.jcommander.Parameter;
|
import com.beust.jcommander.Parameter;
|
||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
|
import jadx.api.DecompilationMode;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.JadxArgs.RenameEnum;
|
import jadx.api.JadxArgs.RenameEnum;
|
||||||
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
||||||
@@ -55,6 +59,17 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
||||||
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "-m", "--decompilation-mode" },
|
||||||
|
description = "code output mode:"
|
||||||
|
+ "\n 'auto' - trying best options (default)"
|
||||||
|
+ "\n 'restructure' - restore code structure (normal java code)"
|
||||||
|
+ "\n 'simple' - simplified instructions (linear, with goto's)"
|
||||||
|
+ "\n 'fallback' - raw instructions without modifications",
|
||||||
|
converter = DecompilationModeConverter.class
|
||||||
|
)
|
||||||
|
protected DecompilationMode decompilationMode = DecompilationMode.AUTO;
|
||||||
|
|
||||||
@Parameter(names = { "--show-bad-code" }, description = "show inconsistent code (incorrectly decompiled)")
|
@Parameter(names = { "--show-bad-code" }, description = "show inconsistent code (incorrectly decompiled)")
|
||||||
protected boolean showInconsistentCode = false;
|
protected boolean showInconsistentCode = false;
|
||||||
|
|
||||||
@@ -145,7 +160,7 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
|
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
|
||||||
protected boolean rawCfgOutput = false;
|
protected boolean rawCfgOutput = false;
|
||||||
|
|
||||||
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
|
@Parameter(names = { "-f", "--fallback" }, description = "set '--decompilation-mode' to 'fallback' (deprecated)")
|
||||||
protected boolean fallbackMode = false;
|
protected boolean fallbackMode = false;
|
||||||
|
|
||||||
@Parameter(names = { "--use-dx" }, description = "use dx/d8 to convert java bytecode")
|
@Parameter(names = { "--use-dx" }, description = "use dx/d8 to convert java bytecode")
|
||||||
@@ -177,6 +192,9 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
||||||
protected boolean printHelp = false;
|
protected boolean printHelp = false;
|
||||||
|
|
||||||
|
@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
|
||||||
|
protected Map<String, String> pluginOptions = new HashMap<>();
|
||||||
|
|
||||||
public boolean processArgs(String[] args) {
|
public boolean processArgs(String[] args) {
|
||||||
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
|
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
|
||||||
return jcw.parse(args) && process(jcw);
|
return jcw.parse(args) && process(jcw);
|
||||||
@@ -212,7 +230,6 @@ public class JadxCLIArgs {
|
|||||||
if (threadsCount <= 0) {
|
if (threadsCount <= 0) {
|
||||||
throw new JadxException("Threads count must be positive, got: " + threadsCount);
|
throw new JadxException("Threads count must be positive, got: " + threadsCount);
|
||||||
}
|
}
|
||||||
LogHelper.setLogLevelFromArgs(this);
|
|
||||||
} catch (JadxException e) {
|
} catch (JadxException e) {
|
||||||
System.err.println("ERROR: " + e.getMessage());
|
System.err.println("ERROR: " + e.getMessage());
|
||||||
jcw.printUsage();
|
jcw.printUsage();
|
||||||
@@ -231,7 +248,11 @@ public class JadxCLIArgs {
|
|||||||
args.setThreadsCount(threadsCount);
|
args.setThreadsCount(threadsCount);
|
||||||
args.setSkipSources(skipSources);
|
args.setSkipSources(skipSources);
|
||||||
args.setSkipResources(skipResources);
|
args.setSkipResources(skipResources);
|
||||||
args.setFallbackMode(fallbackMode);
|
if (fallbackMode) {
|
||||||
|
args.setDecompilationMode(DecompilationMode.FALLBACK);
|
||||||
|
} else {
|
||||||
|
args.setDecompilationMode(decompilationMode);
|
||||||
|
}
|
||||||
args.setShowInconsistentCode(showInconsistentCode);
|
args.setShowInconsistentCode(showInconsistentCode);
|
||||||
args.setCfgOutput(cfgOutput);
|
args.setCfgOutput(cfgOutput);
|
||||||
args.setRawCFGOutput(rawCfgOutput);
|
args.setRawCFGOutput(rawCfgOutput);
|
||||||
@@ -260,6 +281,7 @@ public class JadxCLIArgs {
|
|||||||
args.setFsCaseSensitive(fsCaseSensitive);
|
args.setFsCaseSensitive(fsCaseSensitive);
|
||||||
args.setCommentsLevel(commentsLevel);
|
args.setCommentsLevel(commentsLevel);
|
||||||
args.setUseDxInput(useDx);
|
args.setUseDxInput(useDx);
|
||||||
|
args.setPluginOptions(pluginOptions);
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,6 +329,10 @@ public class JadxCLIArgs {
|
|||||||
return useDx;
|
return useDx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DecompilationMode getDecompilationMode() {
|
||||||
|
return decompilationMode;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isShowInconsistentCode() {
|
public boolean isShowInconsistentCode() {
|
||||||
return showInconsistentCode;
|
return showInconsistentCode;
|
||||||
}
|
}
|
||||||
@@ -411,6 +437,14 @@ public class JadxCLIArgs {
|
|||||||
return commentsLevel;
|
return commentsLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LogHelper.LogLevelEnum getLogLevel() {
|
||||||
|
return logLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getPluginOptions() {
|
||||||
|
return pluginOptions;
|
||||||
|
}
|
||||||
|
|
||||||
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
||||||
private final String paramName;
|
private final String paramName;
|
||||||
|
|
||||||
@@ -479,6 +513,19 @@ public class JadxCLIArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class DecompilationModeConverter implements IStringConverter<DecompilationMode> {
|
||||||
|
@Override
|
||||||
|
public DecompilationMode convert(String value) {
|
||||||
|
try {
|
||||||
|
return DecompilationMode.valueOf(value.toUpperCase());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
'\'' + value + "' is unknown, possible values are: "
|
||||||
|
+ JadxCLIArgs.enumValuesString(DecompilationMode.values()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static String enumValuesString(Enum<?>[] values) {
|
public static String enumValuesString(Enum<?>[] values) {
|
||||||
return Stream.of(values)
|
return Stream.of(values)
|
||||||
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
|
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jadx.cli;
|
package jadx.cli;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -32,34 +33,61 @@ public class LogHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable("For disable log level control")
|
||||||
private static LogLevelEnum logLevelValue;
|
private static LogLevelEnum logLevelValue;
|
||||||
|
|
||||||
public static void setLogLevelFromArgs(JadxCLIArgs args) {
|
public static void initLogLevel(JadxCLIArgs args) {
|
||||||
if (isCustomLogConfig()) {
|
logLevelValue = getLogLevelFromArgs(args);
|
||||||
return;
|
|
||||||
}
|
|
||||||
LogLevelEnum logLevel = args.logLevel;
|
|
||||||
if (args.quiet) {
|
|
||||||
logLevel = LogLevelEnum.QUIET;
|
|
||||||
} else if (args.verbose) {
|
|
||||||
logLevel = LogLevelEnum.DEBUG;
|
|
||||||
}
|
|
||||||
|
|
||||||
applyLogLevel(logLevel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void applyLogLevel(LogLevelEnum logLevel) {
|
private static LogLevelEnum getLogLevelFromArgs(JadxCLIArgs args) {
|
||||||
logLevelValue = logLevel;
|
if (isCustomLogConfig()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (args.quiet) {
|
||||||
|
return LogLevelEnum.QUIET;
|
||||||
|
}
|
||||||
|
if (args.verbose) {
|
||||||
|
return LogLevelEnum.DEBUG;
|
||||||
|
}
|
||||||
|
return args.logLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLogLevelsForLoadingStage() {
|
||||||
|
if (logLevelValue == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (logLevelValue == LogLevelEnum.PROGRESS) {
|
||||||
|
// show load errors
|
||||||
|
LogHelper.applyLogLevel(LogLevelEnum.ERROR);
|
||||||
|
fixForShowProgress();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyLogLevel(logLevelValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLogLevelsForDecompileStage() {
|
||||||
|
if (logLevelValue == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyLogLevel(logLevelValue);
|
||||||
|
if (logLevelValue == LogLevelEnum.PROGRESS) {
|
||||||
|
fixForShowProgress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show progress: change to 'INFO' for control classes
|
||||||
|
*/
|
||||||
|
private static void fixForShowProgress() {
|
||||||
|
setLevelForClass(JadxCLI.class, Level.INFO);
|
||||||
|
setLevelForClass(JadxDecompiler.class, Level.INFO);
|
||||||
|
setLevelForClass(SingleClassMode.class, Level.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyLogLevel(@NotNull LogLevelEnum logLevel) {
|
||||||
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||||
rootLogger.setLevel(logLevel.getLevel());
|
rootLogger.setLevel(logLevel.getLevel());
|
||||||
|
|
||||||
if (logLevel == LogLevelEnum.PROGRESS) {
|
|
||||||
// show progress for all levels except quiet
|
|
||||||
setLevelForClass(JadxCLI.class, Level.INFO);
|
|
||||||
setLevelForClass(JadxDecompiler.class, Level.INFO);
|
|
||||||
setLevelForClass(SingleClassMode.class, Level.INFO);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ dependencies {
|
|||||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||||
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
|
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
|
||||||
|
|
||||||
testImplementation('tools.profiler:async-profiler:1.8.3')
|
testImplementation 'org.eclipse.jdt:ecj:3.29.0'
|
||||||
|
testImplementation 'tools.profiler:async-profiler:1.8.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
public enum DecompilationMode {
|
||||||
|
/**
|
||||||
|
* Trying best options (default)
|
||||||
|
*/
|
||||||
|
AUTO,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore code structure (normal java code)
|
||||||
|
*/
|
||||||
|
RESTRUCTURE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplified instructions (linear with goto's)
|
||||||
|
*/
|
||||||
|
SIMPLE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw instructions without modifications
|
||||||
|
*/
|
||||||
|
FALLBACK
|
||||||
|
}
|
||||||
@@ -4,7 +4,9 @@ import java.io.File;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
@@ -36,7 +38,6 @@ public class JadxArgs {
|
|||||||
private boolean cfgOutput = false;
|
private boolean cfgOutput = false;
|
||||||
private boolean rawCFGOutput = false;
|
private boolean rawCFGOutput = false;
|
||||||
|
|
||||||
private boolean fallbackMode = false;
|
|
||||||
private boolean showInconsistentCode = false;
|
private boolean showInconsistentCode = false;
|
||||||
|
|
||||||
private boolean useImports = true;
|
private boolean useImports = true;
|
||||||
@@ -83,6 +84,8 @@ public class JadxArgs {
|
|||||||
|
|
||||||
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
|
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
|
||||||
|
|
||||||
|
private DecompilationMode decompilationMode = DecompilationMode.AUTO;
|
||||||
|
|
||||||
private ICodeData codeData;
|
private ICodeData codeData;
|
||||||
|
|
||||||
private CommentsLevel commentsLevel = CommentsLevel.INFO;
|
private CommentsLevel commentsLevel = CommentsLevel.INFO;
|
||||||
@@ -100,6 +103,8 @@ public class JadxArgs {
|
|||||||
*/
|
*/
|
||||||
private boolean skipFilesSave = false;
|
private boolean skipFilesSave = false;
|
||||||
|
|
||||||
|
private Map<String, String> pluginOptions = new HashMap<>();
|
||||||
|
|
||||||
public JadxArgs() {
|
public JadxArgs() {
|
||||||
// use default options
|
// use default options
|
||||||
}
|
}
|
||||||
@@ -171,11 +176,17 @@ public class JadxArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFallbackMode() {
|
public boolean isFallbackMode() {
|
||||||
return fallbackMode;
|
return decompilationMode == DecompilationMode.FALLBACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deprecated: use 'decompilation mode' property
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setFallbackMode(boolean fallbackMode) {
|
public void setFallbackMode(boolean fallbackMode) {
|
||||||
this.fallbackMode = fallbackMode;
|
if (fallbackMode) {
|
||||||
|
this.decompilationMode = DecompilationMode.FALLBACK;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isShowInconsistentCode() {
|
public boolean isShowInconsistentCode() {
|
||||||
@@ -418,6 +429,14 @@ public class JadxArgs {
|
|||||||
this.outputFormat = outputFormat;
|
this.outputFormat = outputFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DecompilationMode getDecompilationMode() {
|
||||||
|
return decompilationMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDecompilationMode(DecompilationMode decompilationMode) {
|
||||||
|
this.decompilationMode = decompilationMode;
|
||||||
|
}
|
||||||
|
|
||||||
public ICodeCache getCodeCache() {
|
public ICodeCache getCodeCache() {
|
||||||
return codeCache;
|
return codeCache;
|
||||||
}
|
}
|
||||||
@@ -474,6 +493,14 @@ public class JadxArgs {
|
|||||||
this.skipFilesSave = skipFilesSave;
|
this.skipFilesSave = skipFilesSave;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getPluginOptions() {
|
||||||
|
return pluginOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPluginOptions(Map<String, String> pluginOptions) {
|
||||||
|
this.pluginOptions = pluginOptions;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "JadxArgs{" + "inputFiles=" + inputFiles
|
return "JadxArgs{" + "inputFiles=" + inputFiles
|
||||||
@@ -481,9 +508,7 @@ public class JadxArgs {
|
|||||||
+ ", outDirSrc=" + outDirSrc
|
+ ", outDirSrc=" + outDirSrc
|
||||||
+ ", outDirRes=" + outDirRes
|
+ ", outDirRes=" + outDirRes
|
||||||
+ ", threadsCount=" + threadsCount
|
+ ", threadsCount=" + threadsCount
|
||||||
+ ", cfgOutput=" + cfgOutput
|
+ ", decompilationMode=" + decompilationMode
|
||||||
+ ", rawCFGOutput=" + rawCFGOutput
|
|
||||||
+ ", fallbackMode=" + fallbackMode
|
|
||||||
+ ", showInconsistentCode=" + showInconsistentCode
|
+ ", showInconsistentCode=" + showInconsistentCode
|
||||||
+ ", useImports=" + useImports
|
+ ", useImports=" + useImports
|
||||||
+ ", skipResources=" + skipResources
|
+ ", skipResources=" + skipResources
|
||||||
@@ -507,6 +532,9 @@ public class JadxArgs {
|
|||||||
+ ", codeCache=" + codeCache
|
+ ", codeCache=" + codeCache
|
||||||
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
|
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
|
||||||
+ ", useDxInput=" + useDxInput
|
+ ", useDxInput=" + useDxInput
|
||||||
|
+ ", pluginOptions=" + pluginOptions
|
||||||
|
+ ", cfgOutput=" + cfgOutput
|
||||||
|
+ ", rawCFGOutput=" + rawCFGOutput
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,11 @@ import jadx.api.plugins.JadxPlugin;
|
|||||||
import jadx.api.plugins.JadxPluginManager;
|
import jadx.api.plugins.JadxPluginManager;
|
||||||
import jadx.api.plugins.input.JadxInputPlugin;
|
import jadx.api.plugins.input.JadxInputPlugin;
|
||||||
import jadx.api.plugins.input.data.ILoadResult;
|
import jadx.api.plugins.input.data.ILoadResult;
|
||||||
|
import jadx.api.plugins.options.JadxPluginOptions;
|
||||||
import jadx.core.Jadx;
|
import jadx.core.Jadx;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
@@ -122,13 +125,16 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
loadedInputs.clear();
|
loadedInputs.clear();
|
||||||
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
||||||
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
|
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||||
ILoadResult loadResult = inputPlugin.loadFiles(inputFiles);
|
ILoadResult loadResult = inputPlugin.loadFiles(inputFiles);
|
||||||
if (loadResult != null && !loadResult.isEmpty()) {
|
if (loadResult != null && !loadResult.isEmpty()) {
|
||||||
loadedInputs.add(loadResult);
|
loadedInputs.add(loadResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG.debug("Loaded using {} inputs plugin", loadedInputs.size());
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
private void reset() {
|
||||||
@@ -168,6 +174,18 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
|
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
|
||||||
p -> p.getPluginInfo().getPluginId()));
|
p -> p.getPluginInfo().getPluginId()));
|
||||||
}
|
}
|
||||||
|
Map<String, String> pluginOptions = args.getPluginOptions();
|
||||||
|
if (!pluginOptions.isEmpty()) {
|
||||||
|
LOG.debug("Applying plugin options: {}", pluginOptions);
|
||||||
|
for (JadxPluginOptions plugin : pluginManager.getPluginsWithOptions()) {
|
||||||
|
try {
|
||||||
|
plugin.setOptions(pluginOptions);
|
||||||
|
} catch (Exception e) {
|
||||||
|
String pluginId = plugin.getPluginInfo().getPluginId();
|
||||||
|
throw new JadxRuntimeException("Failed to apply options for plugin: " + pluginId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerPlugin(JadxPlugin plugin) {
|
public void registerPlugin(JadxPlugin plugin) {
|
||||||
@@ -467,12 +485,12 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
if (parentClass.contains(AFlag.DONT_GENERATE)) {
|
if (parentClass.contains(AFlag.DONT_GENERATE)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (parentClass != cls) {
|
JavaClass parentJavaClass = classesMap.get(parentClass);
|
||||||
JavaClass parentJavaClass = classesMap.get(parentClass);
|
if (parentJavaClass == null) {
|
||||||
if (parentJavaClass == null) {
|
getClasses();
|
||||||
getClasses();
|
parentJavaClass = classesMap.get(parentClass);
|
||||||
parentJavaClass = classesMap.get(parentClass);
|
}
|
||||||
}
|
if (parentJavaClass != null) {
|
||||||
loadJavaClass(parentJavaClass);
|
loadJavaClass(parentJavaClass);
|
||||||
javaClass = classesMap.get(cls);
|
javaClass = classesMap.get(cls);
|
||||||
if (javaClass != null) {
|
if (javaClass != null) {
|
||||||
@@ -496,7 +514,9 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// parent class not loaded yet
|
// parent class not loaded yet
|
||||||
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
|
ClassNode parentClass = mth.getParentClass();
|
||||||
|
ClassNode codeCls = getCodeParentClass(parentClass);
|
||||||
|
JavaClass javaClass = getJavaClassByNode(codeCls);
|
||||||
if (javaClass == null) {
|
if (javaClass == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -505,12 +525,26 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
if (javaMethod != null) {
|
if (javaMethod != null) {
|
||||||
return javaMethod;
|
return javaMethod;
|
||||||
}
|
}
|
||||||
if (mth.getParentClass().hasNotGeneratedParent()) {
|
if (parentClass.hasNotGeneratedParent()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
|
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ClassNode getCodeParentClass(ClassNode cls) {
|
||||||
|
ClassNode codeCls;
|
||||||
|
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
|
||||||
|
if (inlinedAttr != null) {
|
||||||
|
codeCls = inlinedAttr.getInlineCls().getTopParentClass();
|
||||||
|
} else {
|
||||||
|
codeCls = cls.getTopParentClass();
|
||||||
|
}
|
||||||
|
if (codeCls == cls) {
|
||||||
|
return codeCls;
|
||||||
|
}
|
||||||
|
return getCodeParentClass(codeCls);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private JavaField getJavaFieldByNode(FieldNode fld) {
|
private JavaField getJavaFieldByNode(FieldNode fld) {
|
||||||
JavaField javaField = fieldsMap.get(fld);
|
JavaField javaField = fieldsMap.get(fld);
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package jadx.api;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
@@ -14,6 +17,7 @@ import jadx.core.dex.nodes.MethodNode;
|
|||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public final class JavaMethod implements JavaNode {
|
public final class JavaMethod implements JavaNode {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class);
|
||||||
private final MethodNode mth;
|
private final MethodNode mth;
|
||||||
private final JavaClass parent;
|
private final JavaClass parent;
|
||||||
|
|
||||||
@@ -73,7 +77,14 @@ public final class JavaMethod implements JavaNode {
|
|||||||
}
|
}
|
||||||
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
||||||
return ovrdAttr.getRelatedMthNodes().stream()
|
return ovrdAttr.getRelatedMthNodes().stream()
|
||||||
.map(m -> ((JavaMethod) decompiler.convertNode(m)))
|
.map(m -> {
|
||||||
|
JavaMethod javaMth = (JavaMethod) decompiler.convertNode(m);
|
||||||
|
if (javaMth == null) {
|
||||||
|
LOG.warn("Failed convert to java method: {}", m);
|
||||||
|
}
|
||||||
|
return javaMth;
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.visitors.AnonymousClassVisitor;
|
import jadx.core.dex.visitors.AnonymousClassVisitor;
|
||||||
import jadx.core.dex.visitors.AttachCommentsVisitor;
|
import jadx.core.dex.visitors.AttachCommentsVisitor;
|
||||||
import jadx.core.dex.visitors.AttachMethodDetails;
|
import jadx.core.dex.visitors.AttachMethodDetails;
|
||||||
@@ -32,6 +33,7 @@ import jadx.core.dex.visitors.InitCodeVariables;
|
|||||||
import jadx.core.dex.visitors.InlineMethods;
|
import jadx.core.dex.visitors.InlineMethods;
|
||||||
import jadx.core.dex.visitors.MarkMethodsForInline;
|
import jadx.core.dex.visitors.MarkMethodsForInline;
|
||||||
import jadx.core.dex.visitors.MethodInvokeVisitor;
|
import jadx.core.dex.visitors.MethodInvokeVisitor;
|
||||||
|
import jadx.core.dex.visitors.MethodVisitor;
|
||||||
import jadx.core.dex.visitors.ModVisitor;
|
import jadx.core.dex.visitors.ModVisitor;
|
||||||
import jadx.core.dex.visitors.MoveInlineVisitor;
|
import jadx.core.dex.visitors.MoveInlineVisitor;
|
||||||
import jadx.core.dex.visitors.OverrideMethodVisitor;
|
import jadx.core.dex.visitors.OverrideMethodVisitor;
|
||||||
@@ -60,8 +62,10 @@ import jadx.core.dex.visitors.rename.CodeRenameVisitor;
|
|||||||
import jadx.core.dex.visitors.rename.RenameVisitor;
|
import jadx.core.dex.visitors.rename.RenameVisitor;
|
||||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||||
|
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
|
||||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||||
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public class Jadx {
|
public class Jadx {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||||
@@ -69,21 +73,20 @@ public class Jadx {
|
|||||||
private Jadx() {
|
private Jadx() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
||||||
if (Consts.DEBUG) {
|
switch (args.getDecompilationMode()) {
|
||||||
LOG.info("debug enabled");
|
case AUTO:
|
||||||
|
case RESTRUCTURE:
|
||||||
|
return getRegionsModePasses(args);
|
||||||
|
case SIMPLE:
|
||||||
|
return getSimpleModePasses(args);
|
||||||
|
case FALLBACK:
|
||||||
|
return getFallbackPassesList();
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unknown decompilation mode: " + args.getDecompilationMode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<IDexTreeVisitor> getFallbackPassesList() {
|
|
||||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
|
||||||
passes.add(new AttachTryCatchVisitor());
|
|
||||||
passes.add(new AttachCommentsVisitor());
|
|
||||||
passes.add(new ProcessInstructionsVisitor());
|
|
||||||
passes.add(new FallbackModeVisitor());
|
|
||||||
return passes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
|
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
|
||||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
passes.add(new SignatureProcessor());
|
passes.add(new SignatureProcessor());
|
||||||
@@ -95,12 +98,8 @@ public class Jadx {
|
|||||||
return passes;
|
return passes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
public static List<IDexTreeVisitor> getRegionsModePasses(JadxArgs args) {
|
||||||
if (args.isFallbackMode()) {
|
|
||||||
return getFallbackPassesList();
|
|
||||||
}
|
|
||||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
|
|
||||||
// instructions IR
|
// instructions IR
|
||||||
passes.add(new CheckCode());
|
passes.add(new CheckCode());
|
||||||
if (args.isDebugInfo()) {
|
if (args.isDebugInfo()) {
|
||||||
@@ -132,6 +131,7 @@ public class Jadx {
|
|||||||
if (args.isDebugInfo()) {
|
if (args.isDebugInfo()) {
|
||||||
passes.add(new DebugInfoApplyVisitor());
|
passes.add(new DebugInfoApplyVisitor());
|
||||||
}
|
}
|
||||||
|
passes.add(new FinishTypeInference());
|
||||||
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
|
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
|
||||||
passes.add(new ProcessKotlinInternals());
|
passes.add(new ProcessKotlinInternals());
|
||||||
}
|
}
|
||||||
@@ -178,7 +178,69 @@ public class Jadx {
|
|||||||
return passes;
|
return passes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<IDexTreeVisitor> getSimpleModePasses(JadxArgs args) {
|
||||||
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
|
if (args.isDebugInfo()) {
|
||||||
|
passes.add(new DebugInfoAttachVisitor());
|
||||||
|
}
|
||||||
|
passes.add(new AttachTryCatchVisitor());
|
||||||
|
if (args.getCommentsLevel() != CommentsLevel.NONE) {
|
||||||
|
passes.add(new AttachCommentsVisitor());
|
||||||
|
}
|
||||||
|
passes.add(new AttachMethodDetails());
|
||||||
|
passes.add(new ProcessInstructionsVisitor());
|
||||||
|
|
||||||
|
passes.add(new BlockSplitter());
|
||||||
|
passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK)));
|
||||||
|
if (args.isRawCFGOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dumpRaw());
|
||||||
|
}
|
||||||
|
passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK)));
|
||||||
|
passes.add(new BlockProcessor());
|
||||||
|
passes.add(new SSATransform());
|
||||||
|
passes.add(new MoveInlineVisitor());
|
||||||
|
passes.add(new ConstructorVisitor());
|
||||||
|
passes.add(new InitCodeVariables());
|
||||||
|
passes.add(new ConstInlineVisitor());
|
||||||
|
passes.add(new TypeInferenceVisitor());
|
||||||
|
if (args.isDebugInfo()) {
|
||||||
|
passes.add(new DebugInfoApplyVisitor());
|
||||||
|
}
|
||||||
|
passes.add(new FinishTypeInference());
|
||||||
|
passes.add(new CodeRenameVisitor());
|
||||||
|
passes.add(new DeboxingVisitor());
|
||||||
|
passes.add(new ModVisitor());
|
||||||
|
passes.add(new CodeShrinkVisitor());
|
||||||
|
passes.add(new ReSugarCode());
|
||||||
|
passes.add(new CodeShrinkVisitor());
|
||||||
|
passes.add(new SimplifyVisitor());
|
||||||
|
passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE)));
|
||||||
|
if (args.isRawCFGOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dumpRaw());
|
||||||
|
}
|
||||||
|
if (args.isCfgOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dump());
|
||||||
|
}
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<IDexTreeVisitor> getFallbackPassesList() {
|
||||||
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
|
passes.add(new AttachTryCatchVisitor());
|
||||||
|
passes.add(new AttachCommentsVisitor());
|
||||||
|
passes.add(new ProcessInstructionsVisitor());
|
||||||
|
passes.add(new FallbackModeVisitor());
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String VERSION_DEV = "dev";
|
||||||
|
|
||||||
|
private static String version;
|
||||||
|
|
||||||
public static String getVersion() {
|
public static String getVersion() {
|
||||||
|
if (version != null) {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
ClassLoader classLoader = Jadx.class.getClassLoader();
|
ClassLoader classLoader = Jadx.class.getClassLoader();
|
||||||
if (classLoader != null) {
|
if (classLoader != null) {
|
||||||
@@ -188,6 +250,7 @@ public class Jadx {
|
|||||||
Manifest manifest = new Manifest(is);
|
Manifest manifest = new Manifest(is);
|
||||||
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
||||||
if (ver != null) {
|
if (ver != null) {
|
||||||
|
version = ver;
|
||||||
return ver;
|
return ver;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,6 +259,6 @@ public class Jadx {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Can't get manifest file", e);
|
LOG.error("Can't get manifest file", e);
|
||||||
}
|
}
|
||||||
return "dev";
|
return VERSION_DEV;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
package jadx.core;
|
package jadx.core;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
import jadx.core.codegen.CodeGen;
|
import jadx.core.codegen.CodeGen;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.LoadStage;
|
import jadx.core.dex.nodes.LoadStage;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.visitors.DepthTraversal;
|
import jadx.core.dex.visitors.DepthTraversal;
|
||||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
@@ -18,13 +24,17 @@ import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
|||||||
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
||||||
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
|
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
|
||||||
|
|
||||||
public final class ProcessClass {
|
public class ProcessClass {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
|
||||||
|
|
||||||
private ProcessClass() {
|
private final List<IDexTreeVisitor> passes;
|
||||||
|
|
||||||
|
public ProcessClass(JadxArgs args) {
|
||||||
|
this.passes = Jadx.getPassesList(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static ICodeInfo process(ClassNode cls, boolean codegen) {
|
private ICodeInfo process(ClassNode cls, boolean codegen) {
|
||||||
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
|
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return null;
|
return null;
|
||||||
@@ -58,7 +68,7 @@ public final class ProcessClass {
|
|||||||
}
|
}
|
||||||
if (cls.getState() == LOADED) {
|
if (cls.getState() == LOADED) {
|
||||||
cls.setState(PROCESS_STARTED);
|
cls.setState(PROCESS_STARTED);
|
||||||
for (IDexTreeVisitor visitor : cls.root().getPasses()) {
|
for (IDexTreeVisitor visitor : passes) {
|
||||||
DepthTraversal.visit(visitor, cls);
|
DepthTraversal.visit(visitor, cls);
|
||||||
}
|
}
|
||||||
cls.setState(PROCESS_COMPLETE);
|
cls.setState(PROCESS_COMPLETE);
|
||||||
@@ -83,7 +93,7 @@ public final class ProcessClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static ICodeInfo generateCode(ClassNode cls) {
|
public ICodeInfo generateCode(ClassNode cls) {
|
||||||
ClassNode topParentClass = cls.getTopParentClass();
|
ClassNode topParentClass = cls.getTopParentClass();
|
||||||
if (topParentClass != cls) {
|
if (topParentClass != cls) {
|
||||||
return generateCode(topParentClass);
|
return generateCode(topParentClass);
|
||||||
@@ -107,4 +117,19 @@ public final class ProcessClass {
|
|||||||
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
|
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void initPasses(RootNode root) {
|
||||||
|
for (IDexTreeVisitor pass : passes) {
|
||||||
|
try {
|
||||||
|
pass.init(root);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make passes list private and not visible
|
||||||
|
public List<IDexTreeVisitor> getPasses() {
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import jadx.api.plugins.input.data.annotations.EncodedValue;
|
|||||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr;
|
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr;
|
||||||
|
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
|
||||||
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
|
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
|
||||||
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
|
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.attributes.IAttributeNode;
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
@@ -48,7 +48,7 @@ public class AnnotationGen {
|
|||||||
add(field, code);
|
add(field, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addForParameter(ICodeWriter code, MethodParamsAttr paramsAnnotations, int n) {
|
public void addForParameter(ICodeWriter code, AnnotationMethodParamsAttr paramsAnnotations, int n) {
|
||||||
List<AnnotationsAttr> paramList = paramsAnnotations.getParamList();
|
List<AnnotationsAttr> paramList = paramsAnnotations.getParamList();
|
||||||
if (n >= paramList.size()) {
|
if (n >= paramList.size()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ public class ClassGen {
|
|||||||
badCode = false;
|
badCode = false;
|
||||||
}
|
}
|
||||||
MethodGen mthGen;
|
MethodGen mthGen;
|
||||||
if (badCode || fallback || mth.contains(AType.JADX_ERROR) || mth.getRegion() == null) {
|
if (badCode || fallback || mth.contains(AType.JADX_ERROR)) {
|
||||||
mthGen = MethodGen.getFallbackMethodGen(mth);
|
mthGen = MethodGen.getFallbackMethodGen(mth);
|
||||||
} else {
|
} else {
|
||||||
mthGen = new MethodGen(this, mth);
|
mthGen = new MethodGen(this, mth);
|
||||||
@@ -611,6 +611,9 @@ public class ClassGen {
|
|||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
String shortName = extClsInfo.getAliasShortName();
|
String shortName = extClsInfo.getAliasShortName();
|
||||||
|
if (useCls.equals(extClsInfo)) {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
@@ -620,6 +623,9 @@ public class ClassGen {
|
|||||||
if (extClsInfo.isInner()) {
|
if (extClsInfo.isInner()) {
|
||||||
return expandInnerClassName(useCls, extClsInfo);
|
return expandInnerClassName(useCls, extClsInfo);
|
||||||
}
|
}
|
||||||
|
if (searchCollision(cls.root(), useCls, extClsInfo)) {
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
@@ -627,9 +633,6 @@ public class ClassGen {
|
|||||||
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
if (searchCollision(cls.root(), useCls, extClsInfo)) {
|
|
||||||
return fullName;
|
|
||||||
}
|
|
||||||
// ignore classes from default package
|
// ignore classes from default package
|
||||||
if (extClsInfo.isDefaultPackage()) {
|
if (extClsInfo.isDefaultPackage()) {
|
||||||
return shortName;
|
return shortName;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import jadx.core.dex.attributes.AType;
|
|||||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
@@ -50,6 +51,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
|
|||||||
import jadx.core.dex.instructions.args.SSAVar;
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
@@ -517,7 +519,7 @@ public class InsnGen {
|
|||||||
code.add(' ');
|
code.add(' ');
|
||||||
code.add(ifInsn.getOp().getSymbol()).add(' ');
|
code.add(ifInsn.getOp().getSymbol()).add(' ');
|
||||||
addArg(code, insn.getArg(1));
|
addArg(code, insn.getArg(1));
|
||||||
code.add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget()));
|
code.add(") goto ").add(MethodGen.getLabelName(ifInsn));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GOTO:
|
case GOTO:
|
||||||
@@ -538,13 +540,24 @@ public class InsnGen {
|
|||||||
code.add(") {");
|
code.add(") {");
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
int[] keys = sw.getKeys();
|
int[] keys = sw.getKeys();
|
||||||
int[] targets = sw.getTargets();
|
int size = keys.length;
|
||||||
for (int i = 0; i < keys.length; i++) {
|
BlockNode[] targetBlocks = sw.getTargetBlocks();
|
||||||
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
|
if (targetBlocks != null) {
|
||||||
code.add(MethodGen.getLabelName(targets[i])).add(';');
|
for (int i = 0; i < size; i++) {
|
||||||
|
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
|
||||||
|
code.add(MethodGen.getLabelName(targetBlocks[i])).add(';');
|
||||||
|
}
|
||||||
|
code.startLine("default: goto ");
|
||||||
|
code.add(MethodGen.getLabelName(sw.getDefTargetBlock())).add(';');
|
||||||
|
} else {
|
||||||
|
int[] targets = sw.getTargets();
|
||||||
|
for (int i = 0; i < size; 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(';');
|
||||||
}
|
}
|
||||||
code.startLine("default: goto ");
|
|
||||||
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
|
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
break;
|
break;
|
||||||
@@ -682,19 +695,27 @@ public class InsnGen {
|
|||||||
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
|
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
|
||||||
}
|
}
|
||||||
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
||||||
|
MethodNode refMth = callMth;
|
||||||
|
if (callMth != null) {
|
||||||
|
MethodReplaceAttr replaceAttr = callMth.get(AType.METHOD_REPLACE);
|
||||||
|
if (replaceAttr != null) {
|
||||||
|
refMth = replaceAttr.getReplaceMth();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (insn.isSuper()) {
|
if (insn.isSuper()) {
|
||||||
code.attachAnnotation(callMth);
|
code.attachAnnotation(refMth);
|
||||||
code.add("super");
|
code.add("super");
|
||||||
} else if (insn.isThis()) {
|
} else if (insn.isThis()) {
|
||||||
code.attachAnnotation(callMth);
|
code.attachAnnotation(refMth);
|
||||||
code.add("this");
|
code.add("this");
|
||||||
} else {
|
} else {
|
||||||
code.add("new ");
|
code.add("new ");
|
||||||
if (callMth == null || callMth.contains(AFlag.DONT_GENERATE)) {
|
if (refMth == null || refMth.contains(AFlag.DONT_GENERATE)) {
|
||||||
// use class reference if constructor method is missing (default constructor)
|
// use class reference if constructor method is missing (default constructor)
|
||||||
code.attachAnnotation(mth.root().resolveClass(insn.getCallMth().getDeclClass()));
|
code.attachAnnotation(mth.root().resolveClass(insn.getCallMth().getDeclClass()));
|
||||||
} else {
|
} else {
|
||||||
code.attachAnnotation(callMth);
|
code.attachAnnotation(refMth);
|
||||||
}
|
}
|
||||||
mgen.getClassGen().addClsName(code, insn.getClassType());
|
mgen.getClassGen().addClsName(code, insn.getClassType());
|
||||||
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
|
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
|
||||||
|
|||||||
@@ -6,17 +6,19 @@ import java.util.List;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.data.annotations.InsnCodeOffset;
|
import jadx.api.data.annotations.InsnCodeOffset;
|
||||||
import jadx.api.data.annotations.VarDeclareRef;
|
import jadx.api.data.annotations.VarDeclareRef;
|
||||||
import jadx.api.plugins.input.data.AccessFlags;
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
|
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.Jadx;
|
import jadx.core.Jadx;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
@@ -32,13 +34,14 @@ import jadx.core.dex.instructions.args.ArgType;
|
|||||||
import jadx.core.dex.instructions.args.CodeVar;
|
import jadx.core.dex.instructions.args.CodeVar;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
import jadx.core.dex.instructions.args.SSAVar;
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.trycatch.CatchAttr;
|
import jadx.core.dex.trycatch.CatchAttr;
|
||||||
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
import jadx.core.dex.visitors.DepthTraversal;
|
import jadx.core.dex.visitors.DepthTraversal;
|
||||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
import jadx.core.utils.CodeGenUtils;
|
import jadx.core.utils.CodeGenUtils;
|
||||||
import jadx.core.utils.InsnUtils;
|
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||||
@@ -195,7 +198,7 @@ public class MethodGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addMethodArguments(ICodeWriter code, List<RegisterArg> args) {
|
private void addMethodArguments(ICodeWriter code, List<RegisterArg> args) {
|
||||||
MethodParamsAttr paramsAnnotation = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS);
|
AnnotationMethodParamsAttr paramsAnnotation = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
Iterator<RegisterArg> it = args.iterator();
|
Iterator<RegisterArg> it = args.iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
@@ -252,12 +255,28 @@ public class MethodGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addInstructions(ICodeWriter code) throws CodegenException {
|
public void addInstructions(ICodeWriter code) throws CodegenException {
|
||||||
if (mth.root().getArgs().isFallbackMode()) {
|
JadxArgs args = mth.root().getArgs();
|
||||||
addFallbackMethodCode(code, FALLBACK_MODE);
|
switch (args.getDecompilationMode()) {
|
||||||
} else if (classGen.isFallbackMode()) {
|
case AUTO:
|
||||||
dumpInstructions(code);
|
if (classGen.isFallbackMode()) {
|
||||||
} else {
|
// TODO: try simple mode first
|
||||||
addRegionInsns(code);
|
dumpInstructions(code);
|
||||||
|
} else {
|
||||||
|
addRegionInsns(code);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RESTRUCTURE:
|
||||||
|
addRegionInsns(code);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SIMPLE:
|
||||||
|
addSimpleMethodCode(code);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FALLBACK:
|
||||||
|
addFallbackMethodCode(code, FALLBACK_MODE);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,6 +298,59 @@ public class MethodGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addSimpleMethodCode(ICodeWriter code) {
|
||||||
|
if (mth.getBasicBlocks() == null) {
|
||||||
|
code.startLine("// Blocks not ready for simple mode, using fallback");
|
||||||
|
addFallbackMethodCode(code, FALLBACK_MODE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JadxArgs args = mth.root().getArgs();
|
||||||
|
ICodeWriter tmpCode = args.getCodeWriterProvider().apply(args);
|
||||||
|
try {
|
||||||
|
tmpCode.setIndent(code.getIndent());
|
||||||
|
generateSimpleCode(tmpCode);
|
||||||
|
code.add(tmpCode);
|
||||||
|
} catch (Exception e) {
|
||||||
|
mth.addError("Simple mode code generation failed", e);
|
||||||
|
CodeGenUtils.addError(code, "Simple mode code generation failed", e);
|
||||||
|
dumpInstructions(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateSimpleCode(ICodeWriter code) throws CodegenException {
|
||||||
|
SimpleModeHelper helper = new SimpleModeHelper(mth);
|
||||||
|
List<BlockNode> blocks = helper.prepareBlocks();
|
||||||
|
InsnGen insnGen = new InsnGen(this, true);
|
||||||
|
for (BlockNode block : blocks) {
|
||||||
|
if (block.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (helper.isNeedStartLabel(block)) {
|
||||||
|
code.decIndent();
|
||||||
|
code.startLine(getLabelName(block)).add(':');
|
||||||
|
code.incIndent();
|
||||||
|
}
|
||||||
|
for (InsnNode insn : block.getInstructions()) {
|
||||||
|
if (!insn.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
if (insn.getResult() != null) {
|
||||||
|
CodeVar codeVar = insn.getResult().getSVar().getCodeVar();
|
||||||
|
if (!codeVar.isDeclared()) {
|
||||||
|
insn.add(AFlag.DECLARE_VAR);
|
||||||
|
codeVar.setDeclared(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InsnCodeOffset.attach(code, insn);
|
||||||
|
insnGen.makeInsn(insn, code);
|
||||||
|
addCatchComment(code, insn, false);
|
||||||
|
CodeGenUtils.addCodeComments(code, mth, insn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (helper.isNeedEndGoto(block)) {
|
||||||
|
code.startLine("goto ").add(getLabelName(block.getSuccessors().get(0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void dumpInstructions(ICodeWriter code) {
|
public void dumpInstructions(ICodeWriter code) {
|
||||||
if (mth.checkCommentsLevel(CommentsLevel.ERROR)) {
|
if (mth.checkCommentsLevel(CommentsLevel.ERROR)) {
|
||||||
code.startLine("/*");
|
code.startLine("/*");
|
||||||
@@ -353,60 +425,81 @@ public class MethodGen {
|
|||||||
|
|
||||||
public static void addFallbackInsns(ICodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
|
public static void addFallbackInsns(ICodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
|
||||||
int startIndent = code.getIndent();
|
int startIndent = code.getIndent();
|
||||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
MethodGen methodGen = getFallbackMethodGen(mth);
|
||||||
|
InsnGen insnGen = new InsnGen(methodGen, true);
|
||||||
InsnNode prevInsn = null;
|
InsnNode prevInsn = null;
|
||||||
for (InsnNode insn : insnArr) {
|
for (InsnNode insn : insnArr) {
|
||||||
if (insn == null) {
|
if (insn == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (insn.contains(AType.JADX_ERROR)) {
|
methodGen.dumpInsn(code, insnGen, option, startIndent, prevInsn, insn);
|
||||||
for (JadxError error : insn.getAll(AType.JADX_ERROR)) {
|
prevInsn = insn;
|
||||||
code.startLine("// ").add(error.getError());
|
}
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
|
private boolean dumpInsn(ICodeWriter code, InsnGen insnGen, FallbackOption option, int startIndent,
|
||||||
|
@Nullable InsnNode prevInsn, InsnNode insn) {
|
||||||
|
if (insn.contains(AType.JADX_ERROR)) {
|
||||||
|
for (JadxError error : insn.getAll(AType.JADX_ERROR)) {
|
||||||
|
code.startLine("// ").add(error.getError());
|
||||||
}
|
}
|
||||||
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
|
return true;
|
||||||
|
}
|
||||||
|
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
|
||||||
|
code.decIndent();
|
||||||
|
code.startLine(getLabelName(insn.getOffset()) + ':');
|
||||||
|
code.incIndent();
|
||||||
|
}
|
||||||
|
if (insn.getType() == InsnType.NOP) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
boolean escapeComment = isCommentEscapeNeeded(insn, option);
|
||||||
|
if (escapeComment) {
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
code.startLine(getLabelName(insn.getOffset()) + ':');
|
code.startLine("*/");
|
||||||
|
code.startLine("// ");
|
||||||
|
} else {
|
||||||
|
code.startLineWithNum(insn.getSourceLine());
|
||||||
|
}
|
||||||
|
InsnCodeOffset.attach(code, insn);
|
||||||
|
RegisterArg resArg = insn.getResult();
|
||||||
|
if (resArg != null) {
|
||||||
|
ArgType varType = resArg.getInitType();
|
||||||
|
if (varType.isTypeKnown()) {
|
||||||
|
code.add(varType.toString()).add(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
|
||||||
|
if (escapeComment) {
|
||||||
|
code.startLine("/*");
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
}
|
}
|
||||||
if (insn.getType() == InsnType.NOP) {
|
addCatchComment(code, insn, true);
|
||||||
continue;
|
CodeGenUtils.addCodeComments(code, mth, insn);
|
||||||
}
|
} catch (Exception e) {
|
||||||
try {
|
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
||||||
boolean escapeComment = isCommentEscapeNeeded(insn, option);
|
code.setIndent(startIndent);
|
||||||
if (escapeComment) {
|
code.startLine("// error: " + insn);
|
||||||
code.decIndent();
|
}
|
||||||
code.startLine("*/");
|
return false;
|
||||||
code.startLine("// ");
|
}
|
||||||
} else {
|
|
||||||
code.startLineWithNum(insn.getSourceLine());
|
|
||||||
}
|
|
||||||
InsnCodeOffset.attach(code, insn);
|
|
||||||
RegisterArg resArg = insn.getResult();
|
|
||||||
if (resArg != null) {
|
|
||||||
ArgType varType = resArg.getInitType();
|
|
||||||
if (varType.isTypeKnown()) {
|
|
||||||
code.add(varType.toString()).add(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
|
|
||||||
if (escapeComment) {
|
|
||||||
code.startLine("/*");
|
|
||||||
code.incIndent();
|
|
||||||
}
|
|
||||||
|
|
||||||
CatchAttr catchAttr = insn.get(AType.EXC_CATCH);
|
private void addCatchComment(ICodeWriter code, InsnNode insn, boolean raw) {
|
||||||
if (catchAttr != null) {
|
CatchAttr catchAttr = insn.get(AType.EXC_CATCH);
|
||||||
code.add(" // " + catchAttr);
|
if (catchAttr == null) {
|
||||||
}
|
return;
|
||||||
CodeGenUtils.addCodeComments(code, mth, insn);
|
}
|
||||||
} catch (Exception e) {
|
code.add(" // Catch:");
|
||||||
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
for (ExceptionHandler handler : catchAttr.getHandlers()) {
|
||||||
code.setIndent(startIndent);
|
code.add(' ');
|
||||||
code.startLine("// error: " + insn);
|
classGen.useClass(code, handler.getArgType());
|
||||||
|
code.add(" -> ");
|
||||||
|
if (raw) {
|
||||||
|
code.add(getLabelName(handler.getHandlerOffset()));
|
||||||
|
} else {
|
||||||
|
code.add(getLabelName(handler.getHandlerBlock()));
|
||||||
}
|
}
|
||||||
prevInsn = insn;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,7 +542,22 @@ public class MethodGen {
|
|||||||
return new MethodGen(clsGen, mth);
|
return new MethodGen(clsGen, mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getLabelName(BlockNode block) {
|
||||||
|
return String.format("L%d", block.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLabelName(IfNode insn) {
|
||||||
|
BlockNode thenBlock = insn.getThenBlock();
|
||||||
|
if (thenBlock != null) {
|
||||||
|
return getLabelName(thenBlock);
|
||||||
|
}
|
||||||
|
return getLabelName(insn.getTarget());
|
||||||
|
}
|
||||||
|
|
||||||
public static String getLabelName(int offset) {
|
public static String getLabelName(int offset) {
|
||||||
return "L_" + InsnUtils.formatOffset(offset);
|
if (offset < 0) {
|
||||||
|
return String.format("LB_%x", -offset);
|
||||||
|
}
|
||||||
|
return String.format("L%x", offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,13 +268,17 @@ public class NameGen {
|
|||||||
|
|
||||||
private String makeNameFromInvoke(MethodInfo callMth) {
|
private String makeNameFromInvoke(MethodInfo callMth) {
|
||||||
String name = callMth.getName();
|
String name = callMth.getName();
|
||||||
|
ArgType declType = callMth.getDeclClass().getType();
|
||||||
|
if ("getInstance".equals(name)) {
|
||||||
|
// e.g. Cipher.getInstance
|
||||||
|
return makeNameForType(declType);
|
||||||
|
}
|
||||||
if (name.startsWith("get") || name.startsWith("set")) {
|
if (name.startsWith("get") || name.startsWith("set")) {
|
||||||
return fromName(name.substring(3));
|
return fromName(name.substring(3));
|
||||||
}
|
}
|
||||||
if ("iterator".equals(name)) {
|
if ("iterator".equals(name)) {
|
||||||
return "it";
|
return "it";
|
||||||
}
|
}
|
||||||
ArgType declType = callMth.getDeclClass().getType();
|
|
||||||
if ("toString".equals(name)) {
|
if ("toString".equals(name)) {
|
||||||
return makeNameForType(declType);
|
return makeNameForType(declType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.instructions.IfNode;
|
||||||
|
import jadx.core.dex.instructions.TargetInsnNode;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
|
import jadx.core.dex.visitors.blocks.BlockProcessor;
|
||||||
|
import jadx.core.dex.visitors.blocks.BlockSplitter;
|
||||||
|
import jadx.core.utils.BlockUtils;
|
||||||
|
|
||||||
|
public class SimpleModeHelper {
|
||||||
|
|
||||||
|
private final MethodNode mth;
|
||||||
|
|
||||||
|
private final BitSet startLabel;
|
||||||
|
private final BitSet endGoto;
|
||||||
|
|
||||||
|
public SimpleModeHelper(MethodNode mth) {
|
||||||
|
this.mth = mth;
|
||||||
|
this.startLabel = BlockUtils.newBlocksBitSet(mth);
|
||||||
|
this.endGoto = BlockUtils.newBlocksBitSet(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BlockNode> prepareBlocks() {
|
||||||
|
removeEmptyBlocks();
|
||||||
|
List<BlockNode> blocksList = getSortedBlocks();
|
||||||
|
blocksList.removeIf(b -> b.equals(mth.getEnterBlock()) || b.equals(mth.getExitBlock()));
|
||||||
|
unbindExceptionHandlers();
|
||||||
|
if (blocksList.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
@Nullable
|
||||||
|
BlockNode prev = null;
|
||||||
|
int blocksCount = blocksList.size();
|
||||||
|
for (int i = 0; i < blocksCount; i++) {
|
||||||
|
BlockNode block = blocksList.get(i);
|
||||||
|
BlockNode nextBlock = i + 1 == blocksCount ? null : blocksList.get(i + 1);
|
||||||
|
List<BlockNode> preds = block.getPredecessors();
|
||||||
|
int predsCount = preds.size();
|
||||||
|
if (predsCount > 1) {
|
||||||
|
startLabel.set(block.getId());
|
||||||
|
} else if (predsCount == 1 && prev != null) {
|
||||||
|
if (!prev.equals(preds.get(0))) {
|
||||||
|
startLabel.set(block.getId());
|
||||||
|
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) {
|
||||||
|
endGoto.set(prev.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InsnNode lastInsn = BlockUtils.getLastInsn(block);
|
||||||
|
if (lastInsn instanceof TargetInsnNode) {
|
||||||
|
processTargetInsn(block, lastInsn, nextBlock);
|
||||||
|
}
|
||||||
|
if (block.contains(AType.EXC_HANDLER)) {
|
||||||
|
startLabel.set(block.getId());
|
||||||
|
}
|
||||||
|
if (nextBlock == null && !mth.isPreExitBlocks(block)) {
|
||||||
|
endGoto.set(block.getId());
|
||||||
|
}
|
||||||
|
prev = block;
|
||||||
|
}
|
||||||
|
if (mth.isVoidReturn()) {
|
||||||
|
int last = blocksList.size() - 1;
|
||||||
|
if (blocksList.get(last).contains(AFlag.RETURN)) {
|
||||||
|
// remove trailing return
|
||||||
|
blocksList.remove(last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return blocksList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeEmptyBlocks() {
|
||||||
|
for (BlockNode block : mth.getBasicBlocks()) {
|
||||||
|
if (block.getInstructions().isEmpty()
|
||||||
|
&& block.getPredecessors().size() > 0
|
||||||
|
&& block.getSuccessors().size() == 1) {
|
||||||
|
BlockNode successor = block.getSuccessors().get(0);
|
||||||
|
List<BlockNode> predecessors = block.getPredecessors();
|
||||||
|
BlockSplitter.removeConnection(block, successor);
|
||||||
|
if (predecessors.size() == 1) {
|
||||||
|
BlockSplitter.replaceConnection(predecessors.get(0), block, successor);
|
||||||
|
} else {
|
||||||
|
for (BlockNode pred : new ArrayList<>(predecessors)) {
|
||||||
|
BlockSplitter.replaceConnection(pred, block, successor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block.add(AFlag.REMOVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BlockProcessor.removeMarkedBlocks(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unbindExceptionHandlers() {
|
||||||
|
if (mth.isNoExceptionHandlers()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (ExceptionHandler handler : mth.getExceptionHandlers()) {
|
||||||
|
BlockNode handlerBlock = handler.getHandlerBlock();
|
||||||
|
if (handlerBlock != null) {
|
||||||
|
BlockSplitter.removePredecessors(handlerBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processTargetInsn(BlockNode block, InsnNode lastInsn, @Nullable BlockNode next) {
|
||||||
|
if (lastInsn instanceof IfNode) {
|
||||||
|
IfNode ifInsn = (IfNode) lastInsn;
|
||||||
|
BlockNode thenBlock = ifInsn.getThenBlock();
|
||||||
|
if (Objects.equals(next, thenBlock)) {
|
||||||
|
ifInsn.invertCondition();
|
||||||
|
startLabel.set(ifInsn.getThenBlock().getId());
|
||||||
|
} else {
|
||||||
|
startLabel.set(thenBlock.getId());
|
||||||
|
}
|
||||||
|
ifInsn.normalize();
|
||||||
|
} else {
|
||||||
|
for (BlockNode successor : block.getSuccessors()) {
|
||||||
|
startLabel.set(successor.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNeedStartLabel(BlockNode block) {
|
||||||
|
return startLabel.get(block.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNeedEndGoto(BlockNode block) {
|
||||||
|
return endGoto.get(block.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// DFS sort blocks to reduce goto count
|
||||||
|
private List<BlockNode> getSortedBlocks() {
|
||||||
|
List<BlockNode> list = new ArrayList<>(mth.getBasicBlocks().size());
|
||||||
|
BlockUtils.dfsVisit(mth, list::add);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -38,31 +37,21 @@ public class DeobfPresets {
|
|||||||
private final Map<String, String> fldPresetMap = new HashMap<>();
|
private final Map<String, String> fldPresetMap = new HashMap<>();
|
||||||
private final Map<String, String> mthPresetMap = new HashMap<>();
|
private final Map<String, String> mthPresetMap = new HashMap<>();
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static DeobfPresets build(RootNode root) {
|
public static DeobfPresets build(RootNode root) {
|
||||||
Path deobfMapPath = getPathDeobfMapPath(root);
|
Path deobfMapPath = getPathDeobfMapPath(root);
|
||||||
if (deobfMapPath == null) {
|
if (root.getArgs().getDeobfuscationMapFileMode() != DeobfuscationMapFileMode.IGNORE) {
|
||||||
return null;
|
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
|
||||||
}
|
}
|
||||||
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
|
|
||||||
return new DeobfPresets(deobfMapPath);
|
return new DeobfPresets(deobfMapPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static Path getPathDeobfMapPath(RootNode root) {
|
private static Path getPathDeobfMapPath(RootNode root) {
|
||||||
JadxArgs jadxArgs = root.getArgs();
|
JadxArgs jadxArgs = root.getArgs();
|
||||||
if (jadxArgs.getDeobfuscationMapFileMode() == DeobfuscationMapFileMode.IGNORE) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
File deobfMapFile = jadxArgs.getDeobfuscationMapFile();
|
File deobfMapFile = jadxArgs.getDeobfuscationMapFile();
|
||||||
if (deobfMapFile != null) {
|
if (deobfMapFile != null) {
|
||||||
return deobfMapFile.toPath();
|
return deobfMapFile.toPath();
|
||||||
}
|
}
|
||||||
List<File> inputFiles = jadxArgs.getInputFiles();
|
Path inputFilePath = jadxArgs.getInputFiles().get(0).toPath().toAbsolutePath();
|
||||||
if (inputFiles.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Path inputFilePath = inputFiles.get(0).toPath().toAbsolutePath();
|
|
||||||
String baseName = FileUtils.getPathBaseName(inputFilePath);
|
String baseName = FileUtils.getPathBaseName(inputFilePath);
|
||||||
return inputFilePath.getParent().resolve(baseName + ".jobf");
|
return inputFilePath.getParent().resolve(baseName + ".jobf");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ public class Deobfuscator {
|
|||||||
for (MethodNode mth : cls.getMethods()) {
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
MethodInfo methodInfo = mth.getMethodInfo();
|
MethodInfo methodInfo = mth.getMethodInfo();
|
||||||
if (methodInfo.hasAlias()) {
|
if (methodInfo.hasAlias()) {
|
||||||
deobfPresets.getFldPresetMap().put(methodInfo.getRawFullId(), methodInfo.getAlias());
|
deobfPresets.getMthPresetMap().put(methodInfo.getRawFullId(), methodInfo.getAlias());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ public enum AFlag {
|
|||||||
|
|
||||||
METHOD_CANDIDATE_FOR_INLINE,
|
METHOD_CANDIDATE_FOR_INLINE,
|
||||||
|
|
||||||
|
DISABLE_BLOCKS_LOCK,
|
||||||
|
|
||||||
// Class processing flags
|
// Class processing flags
|
||||||
RESTART_CODEGEN, // codegen must be executed again
|
RESTART_CODEGEN, // codegen must be executed again
|
||||||
RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage
|
RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
|||||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
||||||
import jadx.core.dex.attributes.nodes.JadxCommentsAttr;
|
import jadx.core.dex.attributes.nodes.JadxCommentsAttr;
|
||||||
import jadx.core.dex.attributes.nodes.JadxError;
|
import jadx.core.dex.attributes.nodes.JadxError;
|
||||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||||
@@ -20,6 +21,7 @@ import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
|||||||
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
|
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
|
||||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
|
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
|
||||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||||
@@ -55,6 +57,7 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
|
|||||||
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
|
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
|
||||||
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
|
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
|
||||||
public static final AType<AnonymousClassAttr> ANONYMOUS_CLASS = new AType<>();
|
public static final AType<AnonymousClassAttr> ANONYMOUS_CLASS = new AType<>();
|
||||||
|
public static final AType<InlinedAttr> INLINED = new AType<>();
|
||||||
|
|
||||||
// field
|
// field
|
||||||
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
|
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
|
||||||
@@ -63,11 +66,12 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
|
|||||||
// method
|
// method
|
||||||
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
|
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
|
||||||
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
|
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
|
||||||
|
public static final AType<MethodReplaceAttr> METHOD_REPLACE = new AType<>();
|
||||||
|
public static final AType<MethodBridgeAttr> BRIDGED_BY = new AType<>();
|
||||||
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = 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<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
|
||||||
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
|
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
|
||||||
public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>();
|
public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>();
|
||||||
public static final AType<MethodBridgeAttr> BRIDGED_BY = new AType<>();
|
|
||||||
|
|
||||||
// region
|
// region
|
||||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
|
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
|
||||||
|
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
|
||||||
|
public class InlinedAttr implements IJadxAttribute {
|
||||||
|
|
||||||
|
private final ClassNode inlineCls;
|
||||||
|
|
||||||
|
public InlinedAttr(ClassNode inlineCls) {
|
||||||
|
this.inlineCls = inlineCls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassNode getInlineCls() {
|
||||||
|
return inlineCls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IJadxAttrType<InlinedAttr> getAttrType() {
|
||||||
|
return AType.INLINED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "INLINED: " + inlineCls;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,11 +50,7 @@ public class JadxError implements Comparable<JadxError> {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder str = new StringBuilder();
|
StringBuilder str = new StringBuilder();
|
||||||
str.append("JadxError: ");
|
str.append("JadxError: ").append(error).append(' ');
|
||||||
if (error != null) {
|
|
||||||
str.append(error);
|
|
||||||
str.append(' ');
|
|
||||||
}
|
|
||||||
if (cause != null) {
|
if (cause != null) {
|
||||||
str.append(cause.getClass());
|
str.append(cause.getClass());
|
||||||
str.append(':');
|
str.append(':');
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls of method should be replaced by provided method (used for synthetic methods redirect)
|
||||||
|
*/
|
||||||
|
public class MethodReplaceAttr extends PinnedAttribute {
|
||||||
|
|
||||||
|
private final MethodNode replaceMth;
|
||||||
|
|
||||||
|
public MethodReplaceAttr(MethodNode replaceMth) {
|
||||||
|
this.replaceMth = replaceMth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodNode getReplaceMth() {
|
||||||
|
return replaceMth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<MethodReplaceAttr> getAttrType() {
|
||||||
|
return AType.METHOD_REPLACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "REPLACED_BY: " + replaceMth;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import java.util.List;
|
|||||||
import jadx.api.plugins.input.insns.InsnData;
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
@@ -70,6 +71,15 @@ public class IfNode extends GotoNode {
|
|||||||
elseBlock = tmp;
|
elseBlock = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change 'a != false' to 'a == true'
|
||||||
|
*/
|
||||||
|
public void normalize() {
|
||||||
|
if (getOp() == IfOp.NE && getArg(1).isFalse()) {
|
||||||
|
changeCondition(IfOp.EQ, getArg(0), LiteralArg.litTrue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void changeCondition(IfOp op, InsnArg arg1, InsnArg arg2) {
|
public void changeCondition(IfOp op, InsnArg arg1, InsnArg arg2) {
|
||||||
this.op = op;
|
this.op = op;
|
||||||
setArg(0, arg1);
|
setArg(0, arg1);
|
||||||
|
|||||||
@@ -389,11 +389,11 @@ public class InsnDecoder {
|
|||||||
return arrLenInsn;
|
return arrLenInsn;
|
||||||
|
|
||||||
case AGET:
|
case AGET:
|
||||||
return arrayGet(insn, ArgType.INT_FLOAT);
|
return arrayGet(insn, ArgType.INT_FLOAT, ArgType.NARROW_NUMBERS_NO_BOOL);
|
||||||
case AGET_BOOLEAN:
|
case AGET_BOOLEAN:
|
||||||
return arrayGet(insn, ArgType.BOOLEAN);
|
return arrayGet(insn, ArgType.BOOLEAN);
|
||||||
case AGET_BYTE:
|
case AGET_BYTE:
|
||||||
return arrayGet(insn, ArgType.BYTE);
|
return arrayGet(insn, ArgType.BYTE, ArgType.NARROW_INTEGRAL);
|
||||||
case AGET_BYTE_BOOLEAN:
|
case AGET_BYTE_BOOLEAN:
|
||||||
return arrayGet(insn, ArgType.BYTE_BOOLEAN);
|
return arrayGet(insn, ArgType.BYTE_BOOLEAN);
|
||||||
case AGET_CHAR:
|
case AGET_CHAR:
|
||||||
@@ -406,7 +406,7 @@ public class InsnDecoder {
|
|||||||
return arrayGet(insn, ArgType.UNKNOWN_OBJECT);
|
return arrayGet(insn, ArgType.UNKNOWN_OBJECT);
|
||||||
|
|
||||||
case APUT:
|
case APUT:
|
||||||
return arrayPut(insn, ArgType.INT_FLOAT);
|
return arrayPut(insn, ArgType.INT_FLOAT, ArgType.NARROW_NUMBERS_NO_BOOL);
|
||||||
case APUT_BOOLEAN:
|
case APUT_BOOLEAN:
|
||||||
return arrayPut(insn, ArgType.BOOLEAN);
|
return arrayPut(insn, ArgType.BOOLEAN);
|
||||||
case APUT_BYTE:
|
case APUT_BYTE:
|
||||||
@@ -607,16 +607,24 @@ public class InsnDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode arrayGet(InsnData insn, ArgType argType) {
|
private InsnNode arrayGet(InsnData insn, ArgType argType) {
|
||||||
|
return arrayGet(insn, argType, argType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InsnNode arrayGet(InsnData insn, ArgType arrElemType, ArgType resType) {
|
||||||
InsnNode inode = new InsnNode(InsnType.AGET, 2);
|
InsnNode inode = new InsnNode(InsnType.AGET, 2);
|
||||||
inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
|
inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, resType));
|
||||||
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
|
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(arrElemType)));
|
||||||
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
|
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
|
||||||
return inode;
|
return inode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private InsnNode arrayPut(InsnData insn, ArgType argType) {
|
private InsnNode arrayPut(InsnData insn, ArgType argType) {
|
||||||
|
return arrayPut(insn, argType, argType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InsnNode arrayPut(InsnData insn, ArgType arrElemType, ArgType argType) {
|
||||||
InsnNode inode = new InsnNode(InsnType.APUT, 3);
|
InsnNode inode = new InsnNode(InsnType.APUT, 3);
|
||||||
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
|
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(arrElemType)));
|
||||||
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
|
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
|
||||||
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
|
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
|
||||||
return inode;
|
return inode;
|
||||||
|
|||||||
@@ -192,6 +192,11 @@ public class SSAVar {
|
|||||||
return usedInPhi;
|
return usedInPhi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAssignInPhi() {
|
||||||
|
InsnNode assignInsn = getAssignInsn();
|
||||||
|
return assignInsn != null && assignInsn.getType() == InsnType.PHI;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isUsedInPhi() {
|
public boolean isUsedInPhi() {
|
||||||
return usedInPhi != null && !usedInPhi.isEmpty();
|
return usedInPhi != null && !usedInPhi.isEmpty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.DecompilationMode;
|
||||||
import jadx.api.ICodeCache;
|
import jadx.api.ICodeCache;
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.plugins.input.data.IClassData;
|
import jadx.api.plugins.input.data.IClassData;
|
||||||
import jadx.api.plugins.input.data.IFieldData;
|
import jadx.api.plugins.input.data.IFieldData;
|
||||||
import jadx.api.plugins.input.data.IMethodData;
|
import jadx.api.plugins.input.data.IMethodData;
|
||||||
@@ -34,6 +36,7 @@ import jadx.core.Consts;
|
|||||||
import jadx.core.ProcessClass;
|
import jadx.core.ProcessClass;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
||||||
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.info.AccessInfo.AFType;
|
import jadx.core.dex.info.AccessInfo.AFType;
|
||||||
@@ -304,6 +307,26 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return decompile(true);
|
return decompile(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WARNING: Slow operation! Use with caution!
|
||||||
|
*/
|
||||||
|
public ICodeInfo decompileWithMode(DecompilationMode mode) {
|
||||||
|
DecompilationMode baseMode = root.getArgs().getDecompilationMode();
|
||||||
|
if (mode == baseMode) {
|
||||||
|
return decompile(true);
|
||||||
|
}
|
||||||
|
JadxArgs args = root.getArgs();
|
||||||
|
try {
|
||||||
|
unload();
|
||||||
|
args.setDecompilationMode(mode);
|
||||||
|
ProcessClass process = new ProcessClass(args);
|
||||||
|
process.initPasses(root);
|
||||||
|
return process.generateCode(this);
|
||||||
|
} finally {
|
||||||
|
args.setDecompilationMode(baseMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ICodeInfo getCode() {
|
public ICodeInfo getCode() {
|
||||||
return decompile(true);
|
return decompile(true);
|
||||||
}
|
}
|
||||||
@@ -355,7 +378,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ICodeInfo codeInfo = ProcessClass.generateCode(this);
|
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
|
||||||
codeCache.add(clsRawName, codeInfo);
|
codeCache.add(clsRawName, codeInfo);
|
||||||
return codeInfo;
|
return codeInfo;
|
||||||
}
|
}
|
||||||
@@ -611,6 +634,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
if (inlinedClasses.isEmpty()) {
|
if (inlinedClasses.isEmpty()) {
|
||||||
inlinedClasses = new ArrayList<>(5);
|
inlinedClasses = new ArrayList<>(5);
|
||||||
}
|
}
|
||||||
|
cls.addAttr(new InlinedAttr(this));
|
||||||
inlinedClasses.add(cls);
|
inlinedClasses.add(cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -336,6 +336,14 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
return exitBlock.getPredecessors();
|
return exitBlock.getPredecessors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPreExitBlocks(BlockNode block) {
|
||||||
|
List<BlockNode> successors = block.getSuccessors();
|
||||||
|
if (successors.size() == 1) {
|
||||||
|
return successors.get(0).equals(exitBlock);
|
||||||
|
}
|
||||||
|
return exitBlock.getPredecessors().contains(block);
|
||||||
|
}
|
||||||
|
|
||||||
public void registerLoop(LoopInfo loop) {
|
public void registerLoop(LoopInfo loop) {
|
||||||
if (loops.isEmpty()) {
|
if (loops.isEmpty()) {
|
||||||
loops = new ArrayList<>(5);
|
loops = new ArrayList<>(5);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import jadx.api.data.ICodeData;
|
|||||||
import jadx.api.plugins.input.data.IClassData;
|
import jadx.api.plugins.input.data.IClassData;
|
||||||
import jadx.api.plugins.input.data.ILoadResult;
|
import jadx.api.plugins.input.data.ILoadResult;
|
||||||
import jadx.core.Jadx;
|
import jadx.core.Jadx;
|
||||||
|
import jadx.core.ProcessClass;
|
||||||
import jadx.core.clsp.ClspGraph;
|
import jadx.core.clsp.ClspGraph;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.info.ConstStorage;
|
import jadx.core.dex.info.ConstStorage;
|
||||||
@@ -51,9 +52,9 @@ public class RootNode {
|
|||||||
|
|
||||||
private final JadxArgs args;
|
private final JadxArgs args;
|
||||||
private final List<IDexTreeVisitor> preDecompilePasses;
|
private final List<IDexTreeVisitor> preDecompilePasses;
|
||||||
private final List<IDexTreeVisitor> passes;
|
|
||||||
private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>();
|
private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>();
|
||||||
|
|
||||||
|
private final ProcessClass processClasses;
|
||||||
private final ErrorsCounter errorsCounter = new ErrorsCounter();
|
private final ErrorsCounter errorsCounter = new ErrorsCounter();
|
||||||
private final StringUtils stringUtils;
|
private final StringUtils stringUtils;
|
||||||
private final ConstStorage constValues;
|
private final ConstStorage constValues;
|
||||||
@@ -76,7 +77,7 @@ public class RootNode {
|
|||||||
public RootNode(JadxArgs args) {
|
public RootNode(JadxArgs args) {
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this.preDecompilePasses = Jadx.getPreDecompilePassesList();
|
this.preDecompilePasses = Jadx.getPreDecompilePassesList();
|
||||||
this.passes = Jadx.getPassesList(args);
|
this.processClasses = new ProcessClass(this.getArgs());
|
||||||
this.stringUtils = new StringUtils(args);
|
this.stringUtils = new StringUtils(args);
|
||||||
this.constValues = new ConstStorage(args);
|
this.constValues = new ConstStorage(args);
|
||||||
this.typeUpdate = new TypeUpdate(this);
|
this.typeUpdate = new TypeUpdate(this);
|
||||||
@@ -460,18 +461,16 @@ public class RootNode {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ProcessClass getProcessClasses() {
|
||||||
|
return processClasses;
|
||||||
|
}
|
||||||
|
|
||||||
public List<IDexTreeVisitor> getPasses() {
|
public List<IDexTreeVisitor> getPasses() {
|
||||||
return passes;
|
return processClasses.getPasses();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initPasses() {
|
public void initPasses() {
|
||||||
for (IDexTreeVisitor pass : passes) {
|
processClasses.initPasses(this);
|
||||||
try {
|
|
||||||
pass.init(this);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICodeWriter makeCodeWriter() {
|
public ICodeWriter makeCodeWriter() {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import jadx.core.dex.attributes.AFlag;
|
|||||||
import jadx.core.dex.instructions.IfNode;
|
import jadx.core.dex.instructions.IfNode;
|
||||||
import jadx.core.dex.instructions.IfOp;
|
import jadx.core.dex.instructions.IfOp;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.LiteralArg;
|
|
||||||
|
|
||||||
public final class Compare {
|
public final class Compare {
|
||||||
private final IfNode insn;
|
private final IfNode insn;
|
||||||
@@ -35,13 +34,8 @@ public final class Compare {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Change 'a != false' to 'a == true'
|
|
||||||
*/
|
|
||||||
public void normalize() {
|
public void normalize() {
|
||||||
if (getOp() == IfOp.NE && getB().isFalse()) {
|
insn.normalize();
|
||||||
insn.changeCondition(IfOp.EQ, getA(), LiteralArg.litTrue());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import jadx.api.plugins.input.data.AccessFlags;
|
|||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
@@ -31,6 +32,7 @@ import jadx.core.dex.nodes.ClassNode;
|
|||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||||
import jadx.core.utils.BlockUtils;
|
import jadx.core.utils.BlockUtils;
|
||||||
import jadx.core.utils.InsnRemover;
|
import jadx.core.utils.InsnRemover;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
@@ -155,7 +157,7 @@ public class ClassModifier extends AbstractVisitor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// remove synthetic constructor for inner classes
|
// remove synthetic constructor for inner classes
|
||||||
if (af.isConstructor()) {
|
if (mth.isConstructor() && mth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE)) {
|
||||||
InsnNode insn = BlockUtils.getOnlyOneInsnFromMth(mth);
|
InsnNode insn = BlockUtils.getOnlyOneInsnFromMth(mth);
|
||||||
if (insn != null) {
|
if (insn != null) {
|
||||||
List<RegisterArg> args = mth.getArgRegs();
|
List<RegisterArg> args = mth.getArgRegs();
|
||||||
@@ -210,7 +212,14 @@ public class ClassModifier extends AbstractVisitor {
|
|||||||
SkipMethodArgsAttr.skipArg(mth, i);
|
SkipMethodArgsAttr.skipArg(mth, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mth.add(AFlag.DONT_GENERATE);
|
MethodInfo callMth = constr.getCallMth();
|
||||||
|
MethodNode callMthNode = cls.root().resolveMethod(callMth);
|
||||||
|
if (callMthNode != null) {
|
||||||
|
mth.addAttr(new MethodReplaceAttr(callMthNode));
|
||||||
|
mth.add(AFlag.DONT_GENERATE);
|
||||||
|
// code generation order should be already fixed for marked methods
|
||||||
|
UsageInfoVisitor.replaceMethodUsage(callMthNode, mth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
|
public class MethodVisitor implements IDexTreeVisitor {
|
||||||
|
|
||||||
|
private final Consumer<MethodNode> visitor;
|
||||||
|
|
||||||
|
public MethodVisitor(Consumer<MethodNode> visitor) {
|
||||||
|
this.visitor = visitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(MethodNode mth) throws JadxException {
|
||||||
|
visitor.accept(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(RootNode root) throws JadxException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visit(ClassNode cls) throws JadxException {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,6 +52,13 @@ public class MoveInlineVisitor extends AbstractVisitor {
|
|||||||
if (resultArg.sameRegAndSVar(moveArg)) {
|
if (resultArg.sameRegAndSVar(moveArg)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (moveArg.isRegister()) {
|
||||||
|
RegisterArg moveReg = (RegisterArg) moveArg;
|
||||||
|
if (moveReg.getSVar().isAssignInPhi()) {
|
||||||
|
// don't mix already merged variables
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
SSAVar ssaVar = resultArg.getSVar();
|
SSAVar ssaVar = resultArg.getSVar();
|
||||||
if (ssaVar.isUsedInPhi()) {
|
if (ssaVar.isUsedInPhi()) {
|
||||||
return deleteMove(mth, move);
|
return deleteMove(mth, move);
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ public class ProcessAnonymous extends AbstractVisitor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
|
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
|
||||||
|
outerCls.addInlinedClass(cls);
|
||||||
cls.addAttr(new AnonymousClassAttr(outerCls, baseType));
|
cls.addAttr(new AnonymousClassAttr(outerCls, baseType));
|
||||||
cls.add(AFlag.DONT_GENERATE);
|
cls.add(AFlag.DONT_GENERATE);
|
||||||
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
||||||
|
|||||||
@@ -45,16 +45,17 @@ public class ProcessMethodsForInline extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
AccessInfo accessFlags = mth.getAccessFlags();
|
AccessInfo accessFlags = mth.getAccessFlags();
|
||||||
boolean isSynthetic = accessFlags.isSynthetic() || mth.getName().contains("$");
|
boolean isSynthetic = accessFlags.isSynthetic() || mth.getName().contains("$");
|
||||||
return isSynthetic && accessFlags.isStatic();
|
return isSynthetic && (accessFlags.isStatic() || mth.isConstructor());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void fixClassDependencies(MethodNode mth) {
|
private static void fixClassDependencies(MethodNode mth) {
|
||||||
ClassNode parentClass = mth.getTopParentClass();
|
ClassNode parentClass = mth.getTopParentClass();
|
||||||
for (MethodNode useInMth : mth.getUseIn()) {
|
for (MethodNode useInMth : mth.getUseIn()) {
|
||||||
// remove possible cross dependency to force class with inline method to be processed before its
|
// remove possible cross dependency
|
||||||
// usage
|
// to force class with inline method to be processed before its usage
|
||||||
ClassNode useTopCls = useInMth.getTopParentClass();
|
ClassNode useTopCls = useInMth.getTopParentClass();
|
||||||
parentClass.setDependencies(ListUtils.safeRemoveAndTrim(parentClass.getDependencies(), useTopCls));
|
parentClass.setDependencies(ListUtils.safeRemoveAndTrim(parentClass.getDependencies(), useTopCls));
|
||||||
|
useTopCls.addCodegenDep(parentClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package jadx.core.dex.visitors;
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.SortedMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -34,7 +34,6 @@ import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
|||||||
import jadx.core.utils.InsnList;
|
import jadx.core.utils.InsnList;
|
||||||
import jadx.core.utils.InsnRemover;
|
import jadx.core.utils.InsnRemover;
|
||||||
import jadx.core.utils.InsnUtils;
|
import jadx.core.utils.InsnUtils;
|
||||||
import jadx.core.utils.Utils;
|
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
@JadxVisitor(
|
@JadxVisitor(
|
||||||
@@ -87,7 +86,7 @@ public class ReSugarCode extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
switch (insn.getType()) {
|
switch (insn.getType()) {
|
||||||
case NEW_ARRAY:
|
case NEW_ARRAY:
|
||||||
return processNewArray(mth, (NewArrayNode) insn, instructions, i, remover);
|
return processNewArray(mth, (NewArrayNode) insn, instructions, remover);
|
||||||
|
|
||||||
case SWITCH:
|
case SWITCH:
|
||||||
return processEnumSwitch(mth, (SwitchInsn) insn);
|
return processEnumSwitch(mth, (SwitchInsn) insn);
|
||||||
@@ -100,8 +99,7 @@ public class ReSugarCode extends AbstractVisitor {
|
|||||||
/**
|
/**
|
||||||
* Replace new-array and sequence of array-put to new filled-array instruction.
|
* Replace new-array and sequence of array-put to new filled-array instruction.
|
||||||
*/
|
*/
|
||||||
private static boolean processNewArray(MethodNode mth, NewArrayNode newArrayInsn,
|
private static boolean processNewArray(MethodNode mth, NewArrayNode newArrayInsn, List<InsnNode> instructions, InsnRemover remover) {
|
||||||
List<InsnNode> instructions, int i, InsnRemover remover) {
|
|
||||||
Object arrayLenConst = InsnUtils.getConstValueByArg(mth.root(), newArrayInsn.getArg(0));
|
Object arrayLenConst = InsnUtils.getConstValueByArg(mth.root(), newArrayInsn.getArg(0));
|
||||||
if (!(arrayLenConst instanceof LiteralArg)) {
|
if (!(arrayLenConst instanceof LiteralArg)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -110,50 +108,81 @@ public class ReSugarCode extends AbstractVisitor {
|
|||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
ArgType arrType = newArrayInsn.getArrayType();
|
||||||
|
ArgType elemType = arrType.getArrayElement();
|
||||||
|
boolean allowMissingKeys = arrType.getArrayDimension() == 1 && elemType.isPrimitive();
|
||||||
|
int minLen = allowMissingKeys ? len / 2 : len;
|
||||||
|
|
||||||
RegisterArg arrArg = newArrayInsn.getResult();
|
RegisterArg arrArg = newArrayInsn.getResult();
|
||||||
List<RegisterArg> useList = arrArg.getSVar().getUseList();
|
List<RegisterArg> useList = arrArg.getSVar().getUseList();
|
||||||
if (useList.size() < len) {
|
if (useList.size() < minLen) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
List<InsnNode> arrPuts = useList.stream()
|
// quick check if APUT is used
|
||||||
.map(InsnArg::getParentInsn)
|
boolean foundPut = false;
|
||||||
.filter(Objects::nonNull)
|
for (RegisterArg registerArg : useList) {
|
||||||
.filter(insn -> insn.getType() == InsnType.APUT)
|
InsnNode parentInsn = registerArg.getParentInsn();
|
||||||
.sorted(Comparator.comparingLong(insn -> {
|
if (parentInsn != null && parentInsn.getType() == InsnType.APUT) {
|
||||||
Object constVal = InsnUtils.getConstValueByArg(mth.root(), insn.getArg(1));
|
foundPut = true;
|
||||||
if (constVal instanceof LiteralArg) {
|
break;
|
||||||
return ((LiteralArg) constVal).getLiteral();
|
}
|
||||||
}
|
}
|
||||||
return -1; // bad value, put at top to fail fast next check
|
if (!foundPut) {
|
||||||
}))
|
return false;
|
||||||
.collect(Collectors.toList());
|
}
|
||||||
if (arrPuts.size() != len) {
|
// collect put instructions sorted by array index
|
||||||
|
SortedMap<Long, InsnNode> arrPuts = new TreeMap<>();
|
||||||
|
for (RegisterArg registerArg : useList) {
|
||||||
|
InsnNode parentInsn = registerArg.getParentInsn();
|
||||||
|
if (parentInsn == null || parentInsn.getType() != InsnType.APUT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!arrArg.sameRegAndSVar(parentInsn.getArg(0))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Object constVal = InsnUtils.getConstValueByArg(mth.root(), parentInsn.getArg(1));
|
||||||
|
if (!(constVal instanceof LiteralArg)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
long index = ((LiteralArg) constVal).getLiteral();
|
||||||
|
if (index >= len) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (arrPuts.containsKey(index)) {
|
||||||
|
// stop on index rewrite
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
arrPuts.put(index, parentInsn);
|
||||||
|
}
|
||||||
|
if (arrPuts.size() < minLen) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// expect all puts to be in same block
|
// expect all puts to be in same block
|
||||||
if (!new HashSet<>(instructions).containsAll(arrPuts)) {
|
if (!new HashSet<>(instructions).containsAll(arrPuts.values())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int j = 0; j < len; j++) {
|
|
||||||
InsnNode insn = arrPuts.get(j);
|
|
||||||
if (!checkPutInsn(mth, insn, arrArg, j)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checks complete, apply
|
// checks complete, apply
|
||||||
ArgType arrType = newArrayInsn.getArrayType();
|
InsnNode filledArr = new FilledNewArrayNode(elemType, len);
|
||||||
InsnNode filledArr = new FilledNewArrayNode(arrType.getArrayElement(), len);
|
|
||||||
filledArr.setResult(arrArg.duplicate());
|
filledArr.setResult(arrArg.duplicate());
|
||||||
|
|
||||||
for (InsnNode put : arrPuts) {
|
long prevIndex = -1;
|
||||||
|
for (Map.Entry<Long, InsnNode> entry : arrPuts.entrySet()) {
|
||||||
|
long index = entry.getKey();
|
||||||
|
if (index != prevIndex) {
|
||||||
|
// use zero for missing keys
|
||||||
|
for (long i = prevIndex + 1; i < index; i++) {
|
||||||
|
filledArr.addArg(InsnArg.lit(0, elemType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InsnNode put = entry.getValue();
|
||||||
filledArr.addArg(replaceConstInArg(mth, put.getArg(2)));
|
filledArr.addArg(replaceConstInArg(mth, put.getArg(2)));
|
||||||
remover.addAndUnbind(put);
|
remover.addAndUnbind(put);
|
||||||
|
prevIndex = index;
|
||||||
}
|
}
|
||||||
remover.addAndUnbind(newArrayInsn);
|
remover.addAndUnbind(newArrayInsn);
|
||||||
|
|
||||||
InsnNode lastPut = Utils.last(arrPuts);
|
InsnNode lastPut = arrPuts.get(arrPuts.lastKey());
|
||||||
int replaceIndex = InsnList.getIndex(instructions, lastPut);
|
int replaceIndex = InsnList.getIndex(instructions, lastPut);
|
||||||
instructions.set(replaceIndex, filledArr);
|
instructions.set(replaceIndex, filledArr);
|
||||||
return true;
|
return true;
|
||||||
@@ -172,22 +201,6 @@ public class ReSugarCode extends AbstractVisitor {
|
|||||||
return valueArg.duplicate();
|
return valueArg.duplicate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean checkPutInsn(MethodNode mth, InsnNode insn, RegisterArg arrArg, int putIndex) {
|
|
||||||
if (insn == null || insn.getType() != InsnType.APUT) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!arrArg.sameRegAndSVar(insn.getArg(0))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
InsnArg indexArg = insn.getArg(1);
|
|
||||||
Object value = InsnUtils.getConstValueByArg(mth.root(), indexArg);
|
|
||||||
if (value instanceof LiteralArg) {
|
|
||||||
int index = (int) ((LiteralArg) value).getLiteral();
|
|
||||||
return index == putIndex;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean processEnumSwitch(MethodNode mth, SwitchInsn insn) {
|
private static boolean processEnumSwitch(MethodNode mth, SwitchInsn insn) {
|
||||||
InsnArg arg = insn.getArg(0);
|
InsnArg arg = insn.getArg(0);
|
||||||
if (!arg.isInsnWrap()) {
|
if (!arg.isInsnWrap()) {
|
||||||
|
|||||||
@@ -149,12 +149,7 @@ public class BlockExceptionHandler {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
firstInsn.remove(AType.EXC_HANDLER);
|
firstInsn.remove(AType.EXC_HANDLER);
|
||||||
TmpEdgeAttr tmpEdgeAttr = block.get(AType.TMP_EDGE);
|
removeTmpConnection(block);
|
||||||
if (tmpEdgeAttr != null) {
|
|
||||||
// remove temp connection
|
|
||||||
BlockSplitter.removeConnection(tmpEdgeAttr.getBlock(), block);
|
|
||||||
block.remove(AType.TMP_EDGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
ExceptionHandler excHandler = excHandlerAttr.getHandler();
|
ExceptionHandler excHandler = excHandlerAttr.getHandler();
|
||||||
if (block.getPredecessors().isEmpty()) {
|
if (block.getPredecessors().isEmpty()) {
|
||||||
@@ -176,6 +171,19 @@ public class BlockExceptionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static void removeTmpConnections(MethodNode mth) {
|
||||||
|
mth.getBasicBlocks().forEach(BlockExceptionHandler::removeTmpConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void removeTmpConnection(BlockNode block) {
|
||||||
|
TmpEdgeAttr tmpEdgeAttr = block.get(AType.TMP_EDGE);
|
||||||
|
if (tmpEdgeAttr != null) {
|
||||||
|
// remove temp connection
|
||||||
|
BlockSplitter.removeConnection(tmpEdgeAttr.getBlock(), block);
|
||||||
|
block.remove(AType.TMP_EDGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static List<TryCatchBlockAttr> prepareTryBlocks(MethodNode mth) {
|
private static List<TryCatchBlockAttr> prepareTryBlocks(MethodNode mth) {
|
||||||
Map<ExceptionHandler, List<BlockNode>> blocksByHandler = new HashMap<>();
|
Map<ExceptionHandler, List<BlockNode>> blocksByHandler = new HashMap<>();
|
||||||
for (BlockNode block : mth.getBasicBlocks()) {
|
for (BlockNode block : mth.getBasicBlocks()) {
|
||||||
|
|||||||
@@ -77,7 +77,9 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
processNestedLoops(mth);
|
processNestedLoops(mth);
|
||||||
|
|
||||||
updateCleanSuccessors(mth);
|
updateCleanSuccessors(mth);
|
||||||
mth.finishBasicBlocks();
|
if (!mth.contains(AFlag.DISABLE_BLOCKS_LOCK)) {
|
||||||
|
mth.finishBasicBlocks();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void updateCleanSuccessors(MethodNode mth) {
|
static void updateCleanSuccessors(MethodNode mth) {
|
||||||
@@ -686,7 +688,7 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void removeMarkedBlocks(MethodNode mth) {
|
public static void removeMarkedBlocks(MethodNode mth) {
|
||||||
mth.getBasicBlocks().removeIf(block -> {
|
mth.getBasicBlocks().removeIf(block -> {
|
||||||
if (block.contains(AFlag.REMOVE)) {
|
if (block.contains(AFlag.REMOVE)) {
|
||||||
if (!block.getPredecessors().isEmpty() || !block.getSuccessors().isEmpty()) {
|
if (!block.getPredecessors().isEmpty() || !block.getSuccessors().isEmpty()) {
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ public class BlockSplitter extends AbstractVisitor {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void connect(BlockNode from, BlockNode to) {
|
public static void connect(BlockNode from, BlockNode to) {
|
||||||
if (!from.getSuccessors().contains(to)) {
|
if (!from.getSuccessors().contains(to)) {
|
||||||
from.getSuccessors().add(to);
|
from.getSuccessors().add(to);
|
||||||
}
|
}
|
||||||
@@ -151,19 +151,19 @@ public class BlockSplitter extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void removeConnection(BlockNode from, BlockNode to) {
|
public static void removeConnection(BlockNode from, BlockNode to) {
|
||||||
from.getSuccessors().remove(to);
|
from.getSuccessors().remove(to);
|
||||||
to.getPredecessors().remove(from);
|
to.getPredecessors().remove(from);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void removePredecessors(BlockNode block) {
|
public static void removePredecessors(BlockNode block) {
|
||||||
for (BlockNode pred : block.getPredecessors()) {
|
for (BlockNode pred : block.getPredecessors()) {
|
||||||
pred.getSuccessors().remove(block);
|
pred.getSuccessors().remove(block);
|
||||||
}
|
}
|
||||||
block.getPredecessors().clear();
|
block.getPredecessors().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void replaceConnection(BlockNode source, BlockNode oldDest, BlockNode newDest) {
|
public static void replaceConnection(BlockNode source, BlockNode oldDest, BlockNode newDest) {
|
||||||
removeConnection(source, oldDest);
|
removeConnection(source, oldDest);
|
||||||
connect(source, newDest);
|
connect(source, newDest);
|
||||||
replaceTarget(source, oldDest, newDest);
|
replaceTarget(source, oldDest, newDest);
|
||||||
|
|||||||
+32
-13
@@ -9,7 +9,10 @@ import java.util.Set;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.api.plugins.input.data.ILocalVar;
|
import jadx.api.plugins.input.data.ILocalVar;
|
||||||
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
|
import jadx.api.plugins.input.data.attributes.types.MethodParametersAttr;
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.deobf.NameMapper;
|
import jadx.core.deobf.NameMapper;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
@@ -18,6 +21,7 @@ import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
|||||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||||
import jadx.core.dex.instructions.PhiInsn;
|
import jadx.core.dex.instructions.PhiInsn;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
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.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.Named;
|
import jadx.core.dex.instructions.args.Named;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
@@ -51,24 +55,12 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
|||||||
applyDebugInfo(mth);
|
applyDebugInfo(mth);
|
||||||
mth.remove(AType.LOCAL_VARS_DEBUG_INFO);
|
mth.remove(AType.LOCAL_VARS_DEBUG_INFO);
|
||||||
}
|
}
|
||||||
checkTypes(mth);
|
processMethodParametersAttribute(mth);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
mth.addWarnComment("Failed to apply debug info", e);
|
mth.addWarnComment("Failed to apply debug info", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkTypes(MethodNode mth) {
|
|
||||||
if (mth.isNoCode() || mth.getSVars().isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mth.getSVars().forEach(var -> {
|
|
||||||
ArgType type = var.getTypeInfo().getType();
|
|
||||||
if (!type.isTypeKnown()) {
|
|
||||||
mth.addWarnComment("Type inference failed for: " + var.getDetailedVarInfo(mth));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void applyDebugInfo(MethodNode mth) {
|
private static void applyDebugInfo(MethodNode mth) {
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
LOG.info("Apply debug info for method: {}", mth);
|
LOG.info("Apply debug info for method: {}", mth);
|
||||||
@@ -221,4 +213,31 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processMethodParametersAttribute(MethodNode mth) {
|
||||||
|
MethodParametersAttr parametersAttr = mth.get(JadxAttrType.METHOD_PARAMETERS);
|
||||||
|
if (parametersAttr == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
List<MethodParametersAttr.Info> params = parametersAttr.getList();
|
||||||
|
if (params.size() != mth.getMethodInfo().getArgsCount()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
for (RegisterArg mthArg : mth.getArgRegs()) {
|
||||||
|
MethodParametersAttr.Info paramInfo = params.get(i++);
|
||||||
|
String name = paramInfo.getName();
|
||||||
|
if (NameMapper.isValidAndPrintable(name)) {
|
||||||
|
CodeVar codeVar = mthArg.getSVar().getCodeVar();
|
||||||
|
codeVar.setName(name);
|
||||||
|
if (AccessFlags.hasFlag(paramInfo.getAccFlags(), AccessFlags.FINAL)) {
|
||||||
|
codeVar.setFinal(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
mth.addWarnComment("Failed to process method parameters attribute: " + parametersAttr.getList(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.visitors.AbstractVisitor;
|
||||||
|
import jadx.core.dex.visitors.JadxVisitor;
|
||||||
|
|
||||||
|
@JadxVisitor(
|
||||||
|
name = "Finish Type Inference",
|
||||||
|
desc = "Check used types",
|
||||||
|
runAfter = {
|
||||||
|
TypeInferenceVisitor.class
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public final class FinishTypeInference extends AbstractVisitor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(MethodNode mth) {
|
||||||
|
if (mth.isNoCode() || mth.getSVars().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mth.getSVars().forEach(var -> {
|
||||||
|
ArgType type = var.getTypeInfo().getType();
|
||||||
|
if (!type.isTypeKnown()) {
|
||||||
|
mth.addWarnComment("Type inference failed for: " + var.getDetailedVarInfo(mth));
|
||||||
|
}
|
||||||
|
ArgType codeVarType = var.getCodeVar().getType();
|
||||||
|
if (codeVarType == null) {
|
||||||
|
var.getCodeVar().setType(ArgType.UNKNOWN);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
@@ -46,6 +46,10 @@ public final class TypeBoundCheckCastAssign implements ITypeBoundDynamic {
|
|||||||
return insn.getResult();
|
return insn.getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IndexInsnNode getInsn() {
|
||||||
|
return insn;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "CHECK_CAST_ASSIGN{(" + insn.getIndex() + ") " + insn.getArg(0).getType() + "}";
|
return "CHECK_CAST_ASSIGN{(" + insn.getIndex() + ") " + insn.getArg(0).getType() + "}";
|
||||||
|
|||||||
+16
-2
@@ -1,5 +1,7 @@
|
|||||||
package jadx.core.dex.visitors.typeinference;
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.core.dex.instructions.InvokeNode;
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
@@ -48,12 +50,24 @@ public final class TypeBoundInvokeAssign implements ITypeBoundDynamic {
|
|||||||
mthDeclType = instanceType;
|
mthDeclType = instanceType;
|
||||||
}
|
}
|
||||||
ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(instanceType, mthDeclType, genericReturnType);
|
ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(instanceType, mthDeclType, genericReturnType);
|
||||||
if (resultGeneric != null && !resultGeneric.isWildcard()) {
|
ArgType result = processResultType(resultGeneric);
|
||||||
return resultGeneric;
|
if (result != null) {
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
return invokeNode.getCallMth().getReturnType();
|
return invokeNode.getCallMth().getReturnType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ArgType processResultType(@Nullable ArgType resultGeneric) {
|
||||||
|
if (resultGeneric == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!resultGeneric.isWildcard()) {
|
||||||
|
return resultGeneric;
|
||||||
|
}
|
||||||
|
return resultGeneric.getWildcardType();
|
||||||
|
}
|
||||||
|
|
||||||
private InsnArg getInstanceArg() {
|
private InsnArg getInstanceArg() {
|
||||||
return invokeNode.getArg(0);
|
return invokeNode.getArg(0);
|
||||||
}
|
}
|
||||||
|
|||||||
+92
-1
@@ -57,6 +57,7 @@ import jadx.core.dex.visitors.ssa.SSATransform;
|
|||||||
import jadx.core.utils.BlockUtils;
|
import jadx.core.utils.BlockUtils;
|
||||||
import jadx.core.utils.InsnList;
|
import jadx.core.utils.InsnList;
|
||||||
import jadx.core.utils.InsnUtils;
|
import jadx.core.utils.InsnUtils;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||||
|
|
||||||
@@ -83,10 +84,12 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
this.resolvers = Arrays.asList(
|
this.resolvers = Arrays.asList(
|
||||||
this::initTypeBounds,
|
this::initTypeBounds,
|
||||||
this::runTypePropagation,
|
this::runTypePropagation,
|
||||||
|
this::tryRestoreTypeVarCasts,
|
||||||
this::tryInsertCasts,
|
this::tryInsertCasts,
|
||||||
this::tryDeduceTypes,
|
this::tryDeduceTypes,
|
||||||
this::trySplitConstInsns,
|
this::trySplitConstInsns,
|
||||||
this::tryToFixIncompatiblePrimitives,
|
this::tryToFixIncompatiblePrimitives,
|
||||||
|
this::tryToForceImmutableTypes,
|
||||||
this::tryInsertAdditionalMove,
|
this::tryInsertAdditionalMove,
|
||||||
this::runMultiVariableSearch,
|
this::runMultiVariableSearch,
|
||||||
this::tryRemoveGenerics);
|
this::tryRemoveGenerics);
|
||||||
@@ -519,7 +522,60 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ForLoopReplaceableByWhile")
|
/**
|
||||||
|
* Fix check casts to type var extend type:
|
||||||
|
* <br>
|
||||||
|
* {@code <T extends Comparable> T var = (Comparable) obj; => T var = (T) obj; }
|
||||||
|
*/
|
||||||
|
private boolean tryRestoreTypeVarCasts(MethodNode mth) {
|
||||||
|
int changed = 0;
|
||||||
|
List<SSAVar> mthSVars = mth.getSVars();
|
||||||
|
for (SSAVar var : mthSVars) {
|
||||||
|
changed += restoreTypeVarCasts(var);
|
||||||
|
}
|
||||||
|
if (changed == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
|
mth.addDebugComment("Restore " + changed + " type vars casts");
|
||||||
|
}
|
||||||
|
initTypeBounds(mth);
|
||||||
|
return runTypePropagation(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int restoreTypeVarCasts(SSAVar var) {
|
||||||
|
TypeInfo typeInfo = var.getTypeInfo();
|
||||||
|
Set<ITypeBound> bounds = typeInfo.getBounds();
|
||||||
|
if (!ListUtils.anyMatch(bounds, t -> t.getType().isGenericType())) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
List<ITypeBound> casts = ListUtils.filter(bounds, TypeBoundCheckCastAssign.class::isInstance);
|
||||||
|
if (casts.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ArgType bestType = selectBestTypeFromBounds(bounds).orElse(ArgType.UNKNOWN);
|
||||||
|
if (!bestType.isGenericType()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
List<ArgType> extendTypes = bestType.getExtendTypes();
|
||||||
|
if (extendTypes.size() != 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int fixed = 0;
|
||||||
|
ArgType extendType = extendTypes.get(0);
|
||||||
|
for (ITypeBound bound : casts) {
|
||||||
|
TypeBoundCheckCastAssign cast = (TypeBoundCheckCastAssign) bound;
|
||||||
|
ArgType castType = cast.getType();
|
||||||
|
TypeCompareEnum result = typeUpdate.getTypeCompare().compareTypes(extendType, castType);
|
||||||
|
if (result.isEqual() || result == TypeCompareEnum.NARROW_BY_GENERIC) {
|
||||||
|
cast.getInsn().updateIndex(bestType);
|
||||||
|
fixed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "ForLoopReplaceableByWhile", "ForLoopReplaceableByForEach" })
|
||||||
private boolean tryInsertCasts(MethodNode mth) {
|
private boolean tryInsertCasts(MethodNode mth) {
|
||||||
int added = 0;
|
int added = 0;
|
||||||
List<SSAVar> mthSVars = mth.getSVars();
|
List<SSAVar> mthSVars = mth.getSVars();
|
||||||
@@ -835,6 +891,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
if (typeInfo.getType().isTypeKnown()) {
|
if (typeInfo.getType().isTypeKnown()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
boolean assigned = false;
|
||||||
for (ITypeBound bound : typeInfo.getBounds()) {
|
for (ITypeBound bound : typeInfo.getBounds()) {
|
||||||
ArgType boundType = bound.getType();
|
ArgType boundType = bound.getType();
|
||||||
switch (bound.getBound()) {
|
switch (bound.getBound()) {
|
||||||
@@ -842,6 +899,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
if (!boundType.contains(PrimitiveType.BOOLEAN)) {
|
if (!boundType.contains(PrimitiveType.BOOLEAN)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
assigned = true;
|
||||||
break;
|
break;
|
||||||
case USE:
|
case USE:
|
||||||
if (!boundType.canBeAnyNumber()) {
|
if (!boundType.canBeAnyNumber()) {
|
||||||
@@ -850,6 +908,9 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!assigned) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
boolean fixed = false;
|
boolean fixed = false;
|
||||||
for (ITypeBound bound : typeInfo.getBounds()) {
|
for (ITypeBound bound : typeInfo.getBounds()) {
|
||||||
@@ -932,6 +993,36 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
return convertInsn;
|
return convertInsn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean tryToForceImmutableTypes(MethodNode mth) {
|
||||||
|
boolean fixed = false;
|
||||||
|
for (SSAVar ssaVar : mth.getSVars()) {
|
||||||
|
ArgType type = ssaVar.getTypeInfo().getType();
|
||||||
|
if (!type.isTypeKnown() && ssaVar.isTypeImmutable()) {
|
||||||
|
if (forceImmutableType(ssaVar)) {
|
||||||
|
fixed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!fixed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return runTypePropagation(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean forceImmutableType(SSAVar ssaVar) {
|
||||||
|
for (RegisterArg useArg : ssaVar.getUseList()) {
|
||||||
|
InsnNode parentInsn = useArg.getParentInsn();
|
||||||
|
if (parentInsn != null) {
|
||||||
|
InsnType insnType = parentInsn.getType();
|
||||||
|
if (insnType == InsnType.AGET || insnType == InsnType.APUT) {
|
||||||
|
ssaVar.setType(ssaVar.getImmutableType());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static void assignImmutableTypes(MethodNode mth) {
|
private static void assignImmutableTypes(MethodNode mth) {
|
||||||
for (SSAVar ssaVar : mth.getSVars()) {
|
for (SSAVar ssaVar : mth.getSVars()) {
|
||||||
ArgType immutableType = getSsaImmutableType(ssaVar);
|
ArgType immutableType = getSsaImmutableType(ssaVar);
|
||||||
|
|||||||
@@ -513,7 +513,18 @@ public final class TypeUpdate {
|
|||||||
|
|
||||||
private TypeUpdateResult arrayGetListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
private TypeUpdateResult arrayGetListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
||||||
if (isAssign(insn, arg)) {
|
if (isAssign(insn, arg)) {
|
||||||
return updateTypeChecked(updateInfo, insn.getArg(0), ArgType.array(candidateType));
|
TypeUpdateResult result = updateTypeChecked(updateInfo, insn.getArg(0), ArgType.array(candidateType));
|
||||||
|
if (result == REJECT) {
|
||||||
|
ArgType arrType = insn.getArg(0).getType();
|
||||||
|
if (arrType.isTypeKnown() && arrType.isArray() && arrType.getArrayElement().isPrimitive()) {
|
||||||
|
TypeCompareEnum compResult = comparator.compareTypes(candidateType, arrType.getArrayElement());
|
||||||
|
if (compResult == TypeCompareEnum.WIDER) {
|
||||||
|
// allow implicit upcast for primitive types (int a = byteArr[n])
|
||||||
|
return CHANGED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
InsnArg arrArg = insn.getArg(0);
|
InsnArg arrArg = insn.getArg(0);
|
||||||
if (arrArg == arg) {
|
if (arrArg == arg) {
|
||||||
@@ -521,7 +532,18 @@ public final class TypeUpdate {
|
|||||||
if (arrayElement == null) {
|
if (arrayElement == null) {
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
return updateTypeChecked(updateInfo, insn.getResult(), arrayElement);
|
TypeUpdateResult result = updateTypeChecked(updateInfo, insn.getResult(), arrayElement);
|
||||||
|
if (result == REJECT) {
|
||||||
|
ArgType resType = insn.getResult().getType();
|
||||||
|
if (resType.isTypeKnown() && resType.isPrimitive()) {
|
||||||
|
TypeCompareEnum compResult = comparator.compareTypes(resType, arrayElement);
|
||||||
|
if (compResult == TypeCompareEnum.WIDER) {
|
||||||
|
// allow implicit upcast for primitive types (int a = byteArr[n])
|
||||||
|
return CHANGED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
// index argument
|
// index argument
|
||||||
return SAME;
|
return SAME;
|
||||||
@@ -538,10 +560,10 @@ public final class TypeUpdate {
|
|||||||
TypeUpdateResult result = updateTypeChecked(updateInfo, putArg, arrayElement);
|
TypeUpdateResult result = updateTypeChecked(updateInfo, putArg, arrayElement);
|
||||||
if (result == REJECT) {
|
if (result == REJECT) {
|
||||||
ArgType putType = putArg.getType();
|
ArgType putType = putArg.getType();
|
||||||
if (putType.isTypeKnown() && !putType.isPrimitive()) {
|
if (putType.isTypeKnown()) {
|
||||||
TypeCompareEnum compResult = comparator.compareTypes(arrayElement, putType);
|
TypeCompareEnum compResult = comparator.compareTypes(arrayElement, putType);
|
||||||
if (compResult == TypeCompareEnum.WIDER || compResult == TypeCompareEnum.WIDER_BY_GENERIC) {
|
if (compResult == TypeCompareEnum.WIDER || compResult == TypeCompareEnum.WIDER_BY_GENERIC) {
|
||||||
// allow wider result (i.e allow put in Object[] any objects)
|
// allow wider result (i.e. allow put any objects in Object[] or byte in int[])
|
||||||
return CHANGED;
|
return CHANGED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package jadx.core.dex.visitors.usage;
|
package jadx.core.dex.visitors.usage;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.ICallSite;
|
import jadx.api.plugins.input.data.ICallSite;
|
||||||
import jadx.api.plugins.input.data.ICodeReader;
|
import jadx.api.plugins.input.data.ICodeReader;
|
||||||
import jadx.api.plugins.input.data.IMethodHandle;
|
import jadx.api.plugins.input.data.IMethodHandle;
|
||||||
@@ -18,6 +21,7 @@ import jadx.core.dex.visitors.AbstractVisitor;
|
|||||||
import jadx.core.dex.visitors.JadxVisitor;
|
import jadx.core.dex.visitors.JadxVisitor;
|
||||||
import jadx.core.dex.visitors.OverrideMethodVisitor;
|
import jadx.core.dex.visitors.OverrideMethodVisitor;
|
||||||
import jadx.core.dex.visitors.rename.RenameVisitor;
|
import jadx.core.dex.visitors.rename.RenameVisitor;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.core.utils.input.InsnDataUtils;
|
import jadx.core.utils.input.InsnDataUtils;
|
||||||
|
|
||||||
@JadxVisitor(
|
@JadxVisitor(
|
||||||
@@ -134,4 +138,11 @@ public class UsageInfoVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void replaceMethodUsage(MethodNode mergeIntoMth, MethodNode sourceMth) {
|
||||||
|
List<MethodNode> mergedUsage = ListUtils.distinctMergeSortedLists(mergeIntoMth.getUseIn(), sourceMth.getUseIn());
|
||||||
|
mergedUsage.remove(sourceMth);
|
||||||
|
mergeIntoMth.setUseIn(mergedUsage);
|
||||||
|
sourceMth.setUseIn(Collections.emptyList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -453,6 +453,31 @@ public class BlockUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void dfsVisit(MethodNode mth, Consumer<BlockNode> visitor) {
|
||||||
|
BitSet visited = newBlocksBitSet(mth);
|
||||||
|
Deque<BlockNode> queue = new ArrayDeque<>();
|
||||||
|
BlockNode enterBlock = mth.getEnterBlock();
|
||||||
|
queue.addLast(enterBlock);
|
||||||
|
visited.set(mth.getEnterBlock().getId());
|
||||||
|
while (true) {
|
||||||
|
BlockNode current = queue.pollLast();
|
||||||
|
if (current == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visitor.accept(current);
|
||||||
|
List<BlockNode> successors = current.getSuccessors();
|
||||||
|
int count = successors.size();
|
||||||
|
for (int i = count - 1; i >= 0; i--) { // to preserve order in queue
|
||||||
|
BlockNode next = successors.get(i);
|
||||||
|
int nextId = next.getId();
|
||||||
|
if (!visited.get(nextId)) {
|
||||||
|
queue.addLast(next);
|
||||||
|
visited.set(nextId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static List<BlockNode> collectPredecessors(MethodNode mth, BlockNode start, Collection<BlockNode> stopBlocks) {
|
public static List<BlockNode> collectPredecessors(MethodNode mth, BlockNode start, Collection<BlockNode> stopBlocks) {
|
||||||
BitSet bs = newBlocksBitSet(mth);
|
BitSet bs = newBlocksBitSet(mth);
|
||||||
if (!stopBlocks.isEmpty()) {
|
if (!stopBlocks.isEmpty()) {
|
||||||
|
|||||||
@@ -35,18 +35,21 @@ public class CodeGenUtils {
|
|||||||
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
|
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
|
||||||
if (!errors.isEmpty()) {
|
if (!errors.isEmpty()) {
|
||||||
errors.stream().distinct().sorted().forEach(err -> {
|
errors.stream().distinct().sorted().forEach(err -> {
|
||||||
code.startLine("/* JADX ERROR: ").add(err.getError());
|
addError(code, err.getError(), err.getCause());
|
||||||
Throwable cause = err.getCause();
|
|
||||||
if (cause != null) {
|
|
||||||
code.incIndent();
|
|
||||||
Utils.appendStackTrace(code, cause);
|
|
||||||
code.decIndent();
|
|
||||||
}
|
|
||||||
code.add("*/");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void addError(ICodeWriter code, String errMsg, Throwable cause) {
|
||||||
|
code.startLine("/* JADX ERROR: ").add(errMsg);
|
||||||
|
if (cause != null) {
|
||||||
|
code.incIndent();
|
||||||
|
Utils.appendStackTrace(code, cause);
|
||||||
|
code.decIndent();
|
||||||
|
}
|
||||||
|
code.add("*/");
|
||||||
|
}
|
||||||
|
|
||||||
public static void addComments(ICodeWriter code, NotificationAttrNode node) {
|
public static void addComments(ICodeWriter code, NotificationAttrNode node) {
|
||||||
JadxCommentsAttr commentsAttr = node.get(AType.JADX_COMMENTS);
|
JadxCommentsAttr commentsAttr = node.get(AType.JADX_COMMENTS);
|
||||||
if (commentsAttr != null) {
|
if (commentsAttr != null) {
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ public class ListUtils {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> List<T> filter(List<T> list, Predicate<T> filter) {
|
public static <T> List<T> filter(Collection<T> list, Predicate<T> filter) {
|
||||||
if (list == null || list.isEmpty()) {
|
if (list == null || list.isEmpty()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
@@ -148,7 +148,7 @@ public class ListUtils {
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> boolean allMatch(List<T> list, Predicate<T> test) {
|
public static <T> boolean allMatch(Collection<T> list, Predicate<T> test) {
|
||||||
if (list == null || list.isEmpty()) {
|
if (list == null || list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,7 @@ public class ListUtils {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> boolean anyMatch(List<T> list, Predicate<T> test) {
|
public static <T> boolean anyMatch(Collection<T> list, Predicate<T> test) {
|
||||||
if (list == null || list.isEmpty()) {
|
if (list == null || list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package jadx.core.utils.log;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape input from untrusted source before pass to logger.
|
||||||
|
* Suggested by CodeQL: https://codeql.github.com/codeql-query-help/java/java-log-injection/
|
||||||
|
*/
|
||||||
|
public class LogUtils {
|
||||||
|
|
||||||
|
private static final Pattern ALFA_NUMERIC = Pattern.compile("\\w*");
|
||||||
|
|
||||||
|
public static String escape(String input) {
|
||||||
|
if (input == null) {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
if (ALFA_NUMERIC.matcher(input).matches()) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
return input.replaceAll("\\W", ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String escape(byte[] input) {
|
||||||
|
if (input == null) {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
return escape(new String(input, StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -212,7 +212,8 @@ public class ResTableParser extends CommonBinaryParser {
|
|||||||
/* int headerSize = */
|
/* int headerSize = */
|
||||||
is.readInt16();
|
is.readInt16();
|
||||||
/* int size = */
|
/* int size = */
|
||||||
is.readInt32();
|
long chunkSize = is.readUInt32();
|
||||||
|
long chunkEnd = start + chunkSize;
|
||||||
|
|
||||||
int id = is.readInt8();
|
int id = is.readInt8();
|
||||||
is.checkInt8(0, "type chunk, res0");
|
is.checkInt8(0, "type chunk, res0");
|
||||||
@@ -231,10 +232,15 @@ public class ResTableParser extends CommonBinaryParser {
|
|||||||
for (int i = 0; i < entryCount; i++) {
|
for (int i = 0; i < entryCount; i++) {
|
||||||
entryIndexes[i] = is.readInt32();
|
entryIndexes[i] = is.readInt32();
|
||||||
}
|
}
|
||||||
|
|
||||||
is.checkPos(entriesStart, "Expected entry start");
|
is.checkPos(entriesStart, "Expected entry start");
|
||||||
for (int i = 0; i < entryCount; i++) {
|
for (int i = 0; i < entryCount; i++) {
|
||||||
if (entryIndexes[i] != NO_ENTRY) {
|
if (entryIndexes[i] != NO_ENTRY) {
|
||||||
|
if (is.getPos() >= chunkEnd) {
|
||||||
|
// Certain resource obfuscated apps like com.facebook.orca have more entries defined
|
||||||
|
// than actually fit into the chunk size -> ignore the remaining entries
|
||||||
|
LOG.warn("End of chunk reached - ignoring remaining {} entries", entryCount - i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
parseEntry(pkg, id, i, config.getQualifiers());
|
parseEntry(pkg, id, i, config.getQualifiers());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,13 @@
|
|||||||
|
package jadx.core.utils.log;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class LogUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void escape() {
|
||||||
|
assertThat(LogUtils.escape("Guest'%0AUser:'Admin")).isEqualTo("Guest..0AUser..Admin");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package jadx.tests.api;
|
package jadx.tests.api;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
@@ -7,6 +8,7 @@ import java.lang.reflect.Method;
|
|||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -19,7 +21,10 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarOutputStream;
|
import java.util.jar.JarOutputStream;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Assumptions;
|
import org.junit.jupiter.api.Assumptions;
|
||||||
@@ -34,6 +39,8 @@ import jadx.api.ICodeWriter;
|
|||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.JadxInternalAccess;
|
import jadx.api.JadxInternalAccess;
|
||||||
|
import jadx.api.JavaClass;
|
||||||
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
import jadx.api.data.annotations.InsnCodeOffset;
|
import jadx.api.data.annotations.InsnCodeOffset;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
@@ -48,9 +55,9 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
|||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
import jadx.core.xmlgen.ResourceStorage;
|
import jadx.core.xmlgen.ResourceStorage;
|
||||||
import jadx.core.xmlgen.entry.ResourceEntry;
|
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||||
import jadx.tests.api.compiler.DynamicCompiler;
|
import jadx.tests.api.compiler.CompilerOptions;
|
||||||
import jadx.tests.api.compiler.JavaUtils;
|
import jadx.tests.api.compiler.JavaUtils;
|
||||||
import jadx.tests.api.compiler.StaticCompiler;
|
import jadx.tests.api.compiler.TestCompiler;
|
||||||
import jadx.tests.api.utils.TestUtils;
|
import jadx.tests.api.utils.TestUtils;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.leftPad;
|
import static org.apache.commons.lang3.StringUtils.leftPad;
|
||||||
@@ -58,12 +65,12 @@ import static org.apache.commons.lang3.StringUtils.rightPad;
|
|||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.empty;
|
import static org.hamcrest.Matchers.empty;
|
||||||
|
import static org.hamcrest.Matchers.emptyArray;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
public abstract class IntegrationTest extends TestUtils {
|
public abstract class IntegrationTest extends TestUtils {
|
||||||
@@ -93,9 +100,7 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
protected JadxArgs args;
|
protected JadxArgs args;
|
||||||
|
|
||||||
protected boolean compile;
|
protected boolean compile;
|
||||||
protected boolean withDebugInfo;
|
private CompilerOptions compilerOptions;
|
||||||
protected boolean useEclipseCompiler;
|
|
||||||
private int targetJavaVersion = 8;
|
|
||||||
|
|
||||||
private boolean saveTestJar = false;
|
private boolean saveTestJar = false;
|
||||||
|
|
||||||
@@ -105,9 +110,11 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
private boolean printLineNumbers;
|
private boolean printLineNumbers;
|
||||||
private boolean printOffsets;
|
private boolean printOffsets;
|
||||||
private boolean printDisassemble;
|
private boolean printDisassemble;
|
||||||
private Boolean useJavaInput = null;
|
private @Nullable Boolean useJavaInput;
|
||||||
|
private boolean removeParentClassOnInput;
|
||||||
|
|
||||||
private DynamicCompiler dynamicCompiler;
|
private @Nullable TestCompiler sourceCompiler;
|
||||||
|
private @Nullable TestCompiler decompiledCompiler;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// enable debug checks
|
// enable debug checks
|
||||||
@@ -118,10 +125,11 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void init() {
|
public void init() {
|
||||||
this.withDebugInfo = true;
|
|
||||||
this.compile = true;
|
this.compile = true;
|
||||||
this.useEclipseCompiler = false;
|
this.compilerOptions = new CompilerOptions();
|
||||||
this.resMap = Collections.emptyMap();
|
this.resMap = Collections.emptyMap();
|
||||||
|
this.removeParentClassOnInput = true;
|
||||||
|
this.useJavaInput = null;
|
||||||
|
|
||||||
args = new JadxArgs();
|
args = new JadxArgs();
|
||||||
args.setOutDir(new File(OUT_DIR));
|
args.setOutDir(new File(OUT_DIR));
|
||||||
@@ -130,13 +138,21 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
args.setSkipResources(true);
|
args.setSkipResources(true);
|
||||||
args.setFsCaseSensitive(false); // use same value on all systems
|
args.setFsCaseSensitive(false); // use same value on all systems
|
||||||
args.setCommentsLevel(CommentsLevel.DEBUG);
|
args.setCommentsLevel(CommentsLevel.DEBUG);
|
||||||
|
args.setDeobfuscationOn(false);
|
||||||
|
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.IGNORE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
public void after() {
|
public void after() throws IOException {
|
||||||
FileUtils.clearTempRootDir();
|
FileUtils.clearTempRootDir();
|
||||||
if (jadxDecompiler != null) {
|
close(jadxDecompiler);
|
||||||
jadxDecompiler.close();
|
close(sourceCompiler);
|
||||||
|
close(decompiledCompiler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close(Closeable cloaseble) throws IOException {
|
||||||
|
if (cloaseble != null) {
|
||||||
|
cloaseble.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,8 +172,22 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to get class node", e);
|
LOG.error("Failed to get class node", e);
|
||||||
fail(e.getMessage());
|
fail(e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ClassNode> getClassNodes(Class<?>... classes) {
|
||||||
|
try {
|
||||||
|
assertThat("Class list is empty", classes, not(emptyArray()));
|
||||||
|
List<File> srcFiles = Stream.of(classes).map(this::getSourceFileForClass).collect(Collectors.toList());
|
||||||
|
List<File> clsFiles = compileSourceFiles(srcFiles);
|
||||||
|
assertThat("Class files list is empty", clsFiles, not(empty()));
|
||||||
|
return decompileFiles(clsFiles);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Failed to get class node", e);
|
||||||
|
fail(e.getMessage());
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClassNode getClassNodeFromFiles(List<File> files, String clsName) {
|
public ClassNode getClassNodeFromFiles(List<File> files, String clsName) {
|
||||||
@@ -166,13 +196,31 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
|
|
||||||
ClassNode cls = root.resolveClass(clsName);
|
ClassNode cls = root.resolveClass(clsName);
|
||||||
assertThat("Class not found: " + clsName, cls, notNullValue());
|
assertThat("Class not found: " + clsName, cls, notNullValue());
|
||||||
assertThat(clsName, is(cls.getClassInfo().getFullName()));
|
if (removeParentClassOnInput) {
|
||||||
|
assertThat(clsName, is(cls.getClassInfo().getFullName()));
|
||||||
|
} else {
|
||||||
|
LOG.info("Convert back to top level: {}", cls);
|
||||||
|
cls.getTopParentClass().decompile(); // keep correct process order
|
||||||
|
cls.getClassInfo().notInner(root);
|
||||||
|
cls.updateParentClass();
|
||||||
|
}
|
||||||
decompileAndCheck(cls);
|
decompileAndCheck(cls);
|
||||||
return cls;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
public List<ClassNode> decompileFiles(List<File> files) {
|
||||||
|
jadxDecompiler = loadFiles(files);
|
||||||
|
List<ClassNode> sortedClsNodes = jadxDecompiler.getDecompileScheduler()
|
||||||
|
.buildBatches(jadxDecompiler.getClasses())
|
||||||
|
.stream()
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.map(JavaClass::getClassNode)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
decompileAndCheck(sortedClsNodes);
|
||||||
|
return sortedClsNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
public ClassNode searchCls(List<ClassNode> list, String clsName) {
|
public ClassNode searchCls(List<ClassNode> list, String clsName) {
|
||||||
for (ClassNode cls : list) {
|
for (ClassNode cls : list) {
|
||||||
if (cls.getClassInfo().getFullName().equals(clsName)) {
|
if (cls.getClassInfo().getFullName().equals(clsName)) {
|
||||||
@@ -240,7 +288,7 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
|
|
||||||
protected void runChecks(List<ClassNode> clsList) {
|
protected void runChecks(List<ClassNode> clsList) {
|
||||||
clsList.forEach(this::checkCode);
|
clsList.forEach(this::checkCode);
|
||||||
compile(clsList);
|
compileClassNode(clsList);
|
||||||
clsList.forEach(this::runAutoCheck);
|
clsList.forEach(this::runAutoCheck);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,7 +373,7 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void runAutoCheck(ClassNode cls) {
|
private void runAutoCheck(ClassNode cls) {
|
||||||
String clsName = cls.getClassInfo().getFullName();
|
String clsName = cls.getClassInfo().getRawName().replace('/', '.');
|
||||||
try {
|
try {
|
||||||
// run 'check' method from original class
|
// run 'check' method from original class
|
||||||
if (runSourceAutoCheck(clsName)) {
|
if (runSourceAutoCheck(clsName)) {
|
||||||
@@ -342,16 +390,20 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean runSourceAutoCheck(String clsName) {
|
private boolean runSourceAutoCheck(String clsName) {
|
||||||
|
if (sourceCompiler == null) {
|
||||||
|
// no source code (smali case)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Class<?> origCls;
|
Class<?> origCls;
|
||||||
try {
|
try {
|
||||||
origCls = Class.forName(clsName);
|
origCls = sourceCompiler.getClass(clsName);
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
// ignore
|
rethrow("Missing class: " + clsName, e);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Method checkMth;
|
Method checkMth;
|
||||||
try {
|
try {
|
||||||
checkMth = origCls.getMethod(CHECK_METHOD_NAME);
|
checkMth = sourceCompiler.getMethod(origCls, CHECK_METHOD_NAME, new Class[] {});
|
||||||
} catch (NoSuchMethodException e) {
|
} catch (NoSuchMethodException e) {
|
||||||
// ignore
|
// ignore
|
||||||
return true;
|
return true;
|
||||||
@@ -373,10 +425,10 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
|
|
||||||
public void runDecompiledAutoCheck(ClassNode cls) {
|
public void runDecompiledAutoCheck(ClassNode cls) {
|
||||||
try {
|
try {
|
||||||
limitExecTime(() -> invoke(cls, "check"));
|
limitExecTime(() -> invoke(decompiledCompiler, cls.getFullName(), CHECK_METHOD_NAME));
|
||||||
System.out.println("Decompiled check: PASSED");
|
System.out.println("Decompiled check: PASSED");
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
throw new JadxRuntimeException("Decompiled check failed", e);
|
rethrow("Decompiled check failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,8 +452,9 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
if (e instanceof InvocationTargetException) {
|
if (e instanceof InvocationTargetException) {
|
||||||
rethrow(msg, e.getCause());
|
rethrow(msg, e.getCause());
|
||||||
} else if (e instanceof ExecutionException) {
|
} else if (e instanceof ExecutionException) {
|
||||||
rethrow(e.getMessage(), e.getCause());
|
rethrow(msg, e.getCause());
|
||||||
} else if (e instanceof AssertionError) {
|
} else if (e instanceof AssertionError) {
|
||||||
|
System.err.println(msg);
|
||||||
throw (AssertionError) e;
|
throw (AssertionError) e;
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException(msg, e);
|
throw new RuntimeException(msg, e);
|
||||||
@@ -418,55 +471,61 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void compile(List<ClassNode> clsList) {
|
void compileClassNode(List<ClassNode> clsList) {
|
||||||
if (!compile) {
|
if (!compile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
dynamicCompiler = new DynamicCompiler(clsList);
|
// TODO: eclipse uses files or compilation units providers added in Java 9
|
||||||
boolean result = dynamicCompiler.compile();
|
compilerOptions.setUseEclipseCompiler(false);
|
||||||
assertTrue(result, "Compilation failed");
|
decompiledCompiler = new TestCompiler(compilerOptions);
|
||||||
|
decompiledCompiler.compileNodes(clsList);
|
||||||
System.out.println("Compilation: PASSED");
|
System.out.println("Compilation: PASSED");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
fail(e);
|
fail(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object invoke(ClassNode cls, String method) throws Exception {
|
public Object invoke(TestCompiler compiler, String clsFullName, String method) throws Exception {
|
||||||
return invoke(cls, method, new Class<?>[0]);
|
assertNotNull(compiler, "compiler not ready");
|
||||||
}
|
return compiler.invoke(clsFullName, method, new Class<?>[] {}, new Object[] {});
|
||||||
|
|
||||||
public Object invoke(ClassNode cls, String methodName, Class<?>[] types, Object... args) throws Exception {
|
|
||||||
assertNotNull(dynamicCompiler, "dynamicCompiler not ready");
|
|
||||||
return dynamicCompiler.invoke(cls, methodName, types, args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<File> compileClass(Class<?> cls) throws IOException {
|
private List<File> compileClass(Class<?> cls) throws IOException {
|
||||||
String clsFullName = cls.getName();
|
File sourceFile = getSourceFileForClass(cls);
|
||||||
String rootClsName;
|
List<File> clsFiles = compileSourceFiles(Collections.singletonList(sourceFile));
|
||||||
int end = clsFullName.indexOf('$');
|
if (removeParentClassOnInput) {
|
||||||
if (end != -1) {
|
// remove classes which are parents for test class
|
||||||
rootClsName = clsFullName.substring(0, end);
|
String clsFullName = cls.getName();
|
||||||
} else {
|
String clsName = clsFullName.substring(clsFullName.lastIndexOf('.') + 1);
|
||||||
rootClsName = clsFullName;
|
clsFiles.removeIf(next -> !next.getName().contains(clsName));
|
||||||
}
|
}
|
||||||
|
return clsFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getSourceFileForClass(Class<?> cls) {
|
||||||
|
String clsFullName = cls.getName();
|
||||||
|
int innerEnd = clsFullName.indexOf('$');
|
||||||
|
String rootClsName = innerEnd == -1 ? clsFullName : clsFullName.substring(0, innerEnd);
|
||||||
String javaFileName = rootClsName.replace('.', '/') + ".java";
|
String javaFileName = rootClsName.replace('.', '/') + ".java";
|
||||||
File file = new File(TEST_DIRECTORY, javaFileName);
|
File file = new File(TEST_DIRECTORY, javaFileName);
|
||||||
if (!file.exists()) {
|
if (file.exists()) {
|
||||||
file = new File(TEST_DIRECTORY2, javaFileName);
|
return file;
|
||||||
}
|
}
|
||||||
assertThat("Test source file not found: " + javaFileName, file.exists(), is(true));
|
File file2 = new File(TEST_DIRECTORY2, javaFileName);
|
||||||
List<File> compileFileList = Collections.singletonList(file);
|
if (file2.exists()) {
|
||||||
|
return file2;
|
||||||
|
}
|
||||||
|
throw new JadxRuntimeException("Test source not found for class: " + clsFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<File> compileSourceFiles(List<File> compileFileList) throws IOException {
|
||||||
Path outTmp = FileUtils.createTempDir("jadx-tmp-classes");
|
Path outTmp = FileUtils.createTempDir("jadx-tmp-classes");
|
||||||
List<File> files = StaticCompiler.compile(compileFileList, outTmp.toFile(), withDebugInfo, useEclipseCompiler, targetJavaVersion);
|
sourceCompiler = new TestCompiler(compilerOptions);
|
||||||
files.forEach(File::deleteOnExit);
|
List<File> files = sourceCompiler.compileFiles(compileFileList, outTmp);
|
||||||
if (saveTestJar) {
|
if (saveTestJar) {
|
||||||
saveToJar(files, outTmp);
|
saveToJar(files, outTmp);
|
||||||
}
|
}
|
||||||
// remove classes which are parents for test class
|
|
||||||
String clsName = clsFullName.substring(clsFullName.lastIndexOf('.') + 1);
|
|
||||||
files.removeIf(next -> !next.getName().contains(clsName));
|
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,6 +548,10 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CompilerOptions getCompilerOptions() {
|
||||||
|
return compilerOptions;
|
||||||
|
}
|
||||||
|
|
||||||
public void setArgs(JadxArgs args) {
|
public void setArgs(JadxArgs args) {
|
||||||
this.args = args;
|
this.args = args;
|
||||||
}
|
}
|
||||||
@@ -498,16 +561,17 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void noDebugInfo() {
|
protected void noDebugInfo() {
|
||||||
this.withDebugInfo = false;
|
this.compilerOptions.setIncludeDebugInfo(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void useEclipseCompiler() {
|
public void useEclipseCompiler() {
|
||||||
this.useEclipseCompiler = true;
|
Assumptions.assumeTrue(JavaUtils.checkJavaVersion(11), "eclipse compiler library using Java 11");
|
||||||
|
this.compilerOptions.setUseEclipseCompiler(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useTargetJavaVersion(int version) {
|
public void useTargetJavaVersion(int version) {
|
||||||
Assumptions.assumeTrue(JavaUtils.checkJavaVersion(version), "skip test for higher java version");
|
Assumptions.assumeTrue(JavaUtils.checkJavaVersion(version), "skip test for higher java version");
|
||||||
this.targetJavaVersion = version;
|
this.compilerOptions.setJavaVersion(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setFallback() {
|
protected void setFallback() {
|
||||||
@@ -521,7 +585,7 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
|
|
||||||
protected void enableDeobfuscation() {
|
protected void enableDeobfuscation() {
|
||||||
args.setDeobfuscationOn(true);
|
args.setDeobfuscationOn(true);
|
||||||
args.setDeobfuscationForceSave(true);
|
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
|
||||||
args.setDeobfuscationMinLength(2);
|
args.setDeobfuscationMinLength(2);
|
||||||
args.setDeobfuscationMaxLength(64);
|
args.setDeobfuscationMaxLength(64);
|
||||||
}
|
}
|
||||||
@@ -547,10 +611,19 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
this.useJavaInput = false;
|
this.useJavaInput = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void useDexInput(String mode) {
|
||||||
|
useDexInput();
|
||||||
|
this.getArgs().getPluginOptions().put("java-convert.mode", mode);
|
||||||
|
}
|
||||||
|
|
||||||
protected boolean isJavaInput() {
|
protected boolean isJavaInput() {
|
||||||
return Utils.getOrElse(useJavaInput, USE_JAVA_INPUT);
|
return Utils.getOrElse(useJavaInput, USE_JAVA_INPUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void keepParentClassOnInput() {
|
||||||
|
this.removeParentClassOnInput = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Use only for debug purpose
|
// Use only for debug purpose
|
||||||
protected void printDisassemble() {
|
protected void printDisassemble() {
|
||||||
this.printDisassemble = true;
|
this.printDisassemble = true;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package jadx.tests.api.compiler;
|
package jadx.tests.api.compiler;
|
||||||
|
|
||||||
import java.security.SecureClassLoader;
|
import java.io.Closeable;
|
||||||
import java.util.Map;
|
import java.io.File;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.tools.FileObject;
|
import javax.tools.FileObject;
|
||||||
import javax.tools.ForwardingJavaFileManager;
|
import javax.tools.ForwardingJavaFileManager;
|
||||||
@@ -11,19 +12,27 @@ import javax.tools.StandardJavaFileManager;
|
|||||||
|
|
||||||
import static javax.tools.JavaFileObject.Kind;
|
import static javax.tools.JavaFileObject.Kind;
|
||||||
|
|
||||||
public class ClassFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
|
public class ClassFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> implements Closeable {
|
||||||
|
|
||||||
private DynamicClassLoader classLoader;
|
private final DynamicClassLoader classLoader;
|
||||||
|
|
||||||
public ClassFileManager(StandardJavaFileManager standardManager) {
|
public ClassFileManager(StandardJavaFileManager standardManager) {
|
||||||
super(standardManager);
|
super(standardManager);
|
||||||
classLoader = new DynamicClassLoader();
|
classLoader = new DynamicClassLoader();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<JavaFileObject> getJavaFileObjectsFromFiles(List<File> sourceFiles) {
|
||||||
|
List<JavaFileObject> list = new ArrayList<>();
|
||||||
|
for (JavaFileObject javaFileObject : fileManager.getJavaFileObjectsFromFiles(sourceFiles)) {
|
||||||
|
list.add(javaFileObject);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) {
|
public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) {
|
||||||
JavaClassObject clsObject = new JavaClassObject(className, kind);
|
JavaClassObject clsObject = new JavaClassObject(className, kind);
|
||||||
classLoader.getClsMap().put(className, clsObject);
|
classLoader.add(className, clsObject);
|
||||||
return clsObject;
|
return clsObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,44 +41,7 @@ public class ClassFileManager extends ForwardingJavaFileManager<StandardJavaFile
|
|||||||
return classLoader;
|
return classLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DynamicClassLoader extends SecureClassLoader {
|
public DynamicClassLoader getClassLoader() {
|
||||||
private final Map<String, JavaClassObject> clsMap = new ConcurrentHashMap<>();
|
return classLoader;
|
||||||
private final Map<String, Class<?>> clsCache = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
|
||||||
Class<?> cls = replaceClass(name);
|
|
||||||
if (cls != null) {
|
|
||||||
return cls;
|
|
||||||
}
|
|
||||||
return super.findClass(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
|
||||||
Class<?> cls = replaceClass(name);
|
|
||||||
if (cls != null) {
|
|
||||||
return cls;
|
|
||||||
}
|
|
||||||
return super.loadClass(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<?> replaceClass(String name) throws ClassNotFoundException {
|
|
||||||
Class<?> cacheCls = clsCache.get(name);
|
|
||||||
if (cacheCls != null) {
|
|
||||||
return cacheCls;
|
|
||||||
}
|
|
||||||
JavaClassObject clsObject = clsMap.get(name);
|
|
||||||
if (clsObject == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
byte[] clsBytes = clsObject.getBytes();
|
|
||||||
Class<?> cls = super.defineClass(name, clsBytes, 0, clsBytes.length);
|
|
||||||
clsCache.put(name, cls);
|
|
||||||
return cls;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, JavaClassObject> getClsMap() {
|
|
||||||
return clsMap;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package jadx.tests.api.compiler;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CompilerOptions {
|
||||||
|
private boolean includeDebugInfo = true;
|
||||||
|
private boolean useEclipseCompiler = false;
|
||||||
|
private int javaVersion = 8;
|
||||||
|
|
||||||
|
List<String> arguments = Collections.emptyList();
|
||||||
|
|
||||||
|
public boolean isIncludeDebugInfo() {
|
||||||
|
return includeDebugInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIncludeDebugInfo(boolean includeDebugInfo) {
|
||||||
|
this.includeDebugInfo = includeDebugInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUseEclipseCompiler() {
|
||||||
|
return useEclipseCompiler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUseEclipseCompiler(boolean useEclipseCompiler) {
|
||||||
|
this.useEclipseCompiler = useEclipseCompiler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getJavaVersion() {
|
||||||
|
return javaVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJavaVersion(int javaVersion) {
|
||||||
|
this.javaVersion = javaVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getArguments() {
|
||||||
|
return Collections.unmodifiableList(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addArgument(String argName) {
|
||||||
|
if (arguments.isEmpty()) {
|
||||||
|
arguments = new ArrayList<>();
|
||||||
|
}
|
||||||
|
arguments.add(argName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addArgument(String argName, String argValue) {
|
||||||
|
if (arguments.isEmpty()) {
|
||||||
|
arguments = new ArrayList<>();
|
||||||
|
}
|
||||||
|
arguments.add(argName);
|
||||||
|
arguments.add(argValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package jadx.tests.api.compiler;
|
||||||
|
|
||||||
|
import java.security.SecureClassLoader;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class DynamicClassLoader extends SecureClassLoader {
|
||||||
|
private final Map<String, JavaClassObject> clsMap = new ConcurrentHashMap<>();
|
||||||
|
private final Map<String, Class<?>> clsCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void add(String className, JavaClassObject clsObject) {
|
||||||
|
this.clsMap.put(className, clsObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> findClass(String name) throws ClassNotFoundException {
|
||||||
|
Class<?> cls = replaceClass(name);
|
||||||
|
if (cls != null) {
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
return super.findClass(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||||
|
Class<?> cls = replaceClass(name);
|
||||||
|
if (cls != null) {
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
return super.loadClass(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Class<?> replaceClass(String name) {
|
||||||
|
Class<?> cacheCls = clsCache.get(name);
|
||||||
|
if (cacheCls != null) {
|
||||||
|
return cacheCls;
|
||||||
|
}
|
||||||
|
JavaClassObject clsObject = clsMap.get(name);
|
||||||
|
if (clsObject == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] clsBytes = clsObject.getBytes();
|
||||||
|
Class<?> cls = super.defineClass(name, clsBytes, 0, clsBytes.length);
|
||||||
|
clsCache.put(name, cls);
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<? extends JavaClassObject> getClassObjects() {
|
||||||
|
return clsMap.values();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
package jadx.tests.api.compiler;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.tools.JavaCompiler;
|
|
||||||
import javax.tools.JavaFileManager;
|
|
||||||
import javax.tools.JavaFileObject;
|
|
||||||
import javax.tools.ToolProvider;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
|
||||||
import jadx.tests.api.IntegrationTest;
|
|
||||||
|
|
||||||
import static javax.tools.JavaCompiler.CompilationTask;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
|
|
||||||
public class DynamicCompiler {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DynamicCompiler.class);
|
|
||||||
|
|
||||||
private final List<ClassNode> clsNodeList;
|
|
||||||
private JavaFileManager fileManager;
|
|
||||||
|
|
||||||
public DynamicCompiler(List<ClassNode> clsNodeList) {
|
|
||||||
this.clsNodeList = clsNodeList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean compile() {
|
|
||||||
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
|
||||||
if (compiler == null) {
|
|
||||||
LOG.error("Can not find compiler, please use JDK instead");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null));
|
|
||||||
|
|
||||||
List<JavaFileObject> jFiles = new ArrayList<>(clsNodeList.size());
|
|
||||||
for (ClassNode clsNode : clsNodeList) {
|
|
||||||
jFiles.add(new CharSequenceJavaFileObject(clsNode.getFullName(), clsNode.getCode().toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
CompilationTask compilerTask = compiler.getTask(null, fileManager, null, null, null, jFiles);
|
|
||||||
return Boolean.TRUE.equals(compilerTask.call());
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClassLoader getClassLoader() {
|
|
||||||
return fileManager.getClassLoader(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object makeInstance(ClassNode cls) throws Exception {
|
|
||||||
String fullName = cls.getFullName();
|
|
||||||
return getClassLoader().loadClass(fullName).getConstructor().newInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public Method getMethod(Object inst, String methodName, Class<?>[] types) throws Exception {
|
|
||||||
for (Class<?> type : types) {
|
|
||||||
checkType(type);
|
|
||||||
}
|
|
||||||
return inst.getClass().getMethod(methodName, types);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object invoke(ClassNode cls, String methodName, Class<?>[] types, Object[] args) {
|
|
||||||
try {
|
|
||||||
Object inst = makeInstance(cls);
|
|
||||||
Method reflMth = getMethod(inst, methodName, types);
|
|
||||||
assertNotNull(reflMth, "Failed to get method " + methodName + '(' + Arrays.toString(types) + ')');
|
|
||||||
return reflMth.invoke(inst, args);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
IntegrationTest.rethrow("Invoke error", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Class<?> checkType(Class<?> type) throws ClassNotFoundException {
|
|
||||||
if (type.isPrimitive()) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
if (type.isArray()) {
|
|
||||||
return checkType(type.getComponentType());
|
|
||||||
}
|
|
||||||
Class<?> decompiledCls = getClassLoader().loadClass(type.getName());
|
|
||||||
if (type != decompiledCls) {
|
|
||||||
throw new IllegalArgumentException("Internal test class cannot be used in method invoke");
|
|
||||||
}
|
|
||||||
return decompiledCls;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package jadx.tests.api.compiler;
|
||||||
|
|
||||||
|
import javax.tools.JavaCompiler;
|
||||||
|
|
||||||
|
public class EclipseCompilerUtils {
|
||||||
|
|
||||||
|
public static JavaCompiler newInstance() {
|
||||||
|
if (!JavaUtils.checkJavaVersion(11)) {
|
||||||
|
throw new IllegalArgumentException("Eclipse compiler build with Java 11");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Class<?> ecjCls = Class.forName("org.eclipse.jdt.internal.compiler.tool.EclipseCompiler");
|
||||||
|
return (JavaCompiler) ecjCls.getConstructor().newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to init Eclipse compiler", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package jadx.tests.api.compiler;
|
package jadx.tests.api.compiler;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
@@ -9,10 +8,17 @@ import javax.tools.SimpleJavaFileObject;
|
|||||||
|
|
||||||
public class JavaClassObject extends SimpleJavaFileObject {
|
public class JavaClassObject extends SimpleJavaFileObject {
|
||||||
|
|
||||||
protected final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
private final String name;
|
||||||
|
private final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
|
||||||
public JavaClassObject(String name, Kind kind) {
|
public JavaClassObject(String name, Kind kind) {
|
||||||
super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
|
super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getBytes() {
|
public byte[] getBytes() {
|
||||||
@@ -20,7 +26,7 @@ public class JavaClassObject extends SimpleJavaFileObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream openOutputStream() throws IOException {
|
public OutputStream openOutputStream() {
|
||||||
return bos;
|
return bos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ public class JavaUtils {
|
|||||||
|
|
||||||
public static final int JAVA_VERSION_INT = getJavaVersionInt();
|
public static final int JAVA_VERSION_INT = getJavaVersionInt();
|
||||||
|
|
||||||
|
public static boolean checkJavaVersion(int requiredVersion) {
|
||||||
|
return JAVA_VERSION_INT >= requiredVersion;
|
||||||
|
}
|
||||||
|
|
||||||
private static int getJavaVersionInt() {
|
private static int getJavaVersionInt() {
|
||||||
String javaSpecVerStr = SystemUtils.JAVA_SPECIFICATION_VERSION;
|
String javaSpecVerStr = SystemUtils.JAVA_SPECIFICATION_VERSION;
|
||||||
if (javaSpecVerStr == null) {
|
if (javaSpecVerStr == null) {
|
||||||
@@ -21,8 +25,4 @@ public class JavaUtils {
|
|||||||
}
|
}
|
||||||
return Integer.parseInt(javaSpecVerStr);
|
return Integer.parseInt(javaSpecVerStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean checkJavaVersion(int requiredVersion) {
|
|
||||||
return JAVA_VERSION_INT >= requiredVersion;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
package jadx.tests.api.compiler;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.tools.FileObject;
|
|
||||||
import javax.tools.ForwardingJavaFileManager;
|
|
||||||
import javax.tools.JavaCompiler;
|
|
||||||
import javax.tools.JavaCompiler.CompilationTask;
|
|
||||||
import javax.tools.JavaFileObject;
|
|
||||||
import javax.tools.SimpleJavaFileObject;
|
|
||||||
import javax.tools.StandardJavaFileManager;
|
|
||||||
import javax.tools.ToolProvider;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;
|
|
||||||
|
|
||||||
import jadx.core.utils.files.FileUtils;
|
|
||||||
|
|
||||||
public class StaticCompiler {
|
|
||||||
|
|
||||||
public static List<File> compile(List<File> files, File outDir, boolean includeDebugInfo,
|
|
||||||
boolean useEclipseCompiler, int javaVersion) throws IOException {
|
|
||||||
if (!JavaUtils.checkJavaVersion(javaVersion)) {
|
|
||||||
throw new IllegalArgumentException("Current java version not meet requirement: "
|
|
||||||
+ "current: " + JavaUtils.JAVA_VERSION_INT + ", required: " + javaVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
JavaCompiler compiler;
|
|
||||||
if (useEclipseCompiler) {
|
|
||||||
compiler = new EclipseCompiler();
|
|
||||||
} else {
|
|
||||||
compiler = ToolProvider.getSystemJavaCompiler();
|
|
||||||
if (compiler == null) {
|
|
||||||
throw new IllegalStateException("Can not find compiler, please use JDK instead");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
|
|
||||||
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(files);
|
|
||||||
|
|
||||||
StaticFileManager staticFileManager = new StaticFileManager(fileManager, outDir);
|
|
||||||
|
|
||||||
List<String> options = new ArrayList<>();
|
|
||||||
options.add(includeDebugInfo ? "-g" : "-g:none");
|
|
||||||
String javaVerStr = javaVersion <= 8 ? "1." + javaVersion : Integer.toString(javaVersion);
|
|
||||||
options.add("-source");
|
|
||||||
options.add(javaVerStr);
|
|
||||||
options.add("-target");
|
|
||||||
options.add(javaVerStr);
|
|
||||||
|
|
||||||
CompilationTask task = compiler.getTask(null, staticFileManager, null, options, null, compilationUnits);
|
|
||||||
Boolean result = task.call();
|
|
||||||
fileManager.close();
|
|
||||||
if (Boolean.TRUE.equals(result)) {
|
|
||||||
return staticFileManager.outputFiles();
|
|
||||||
}
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class StaticFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
|
|
||||||
private final List<File> files = new ArrayList<>();
|
|
||||||
private final File outDir;
|
|
||||||
|
|
||||||
protected StaticFileManager(StandardJavaFileManager fileManager, File outDir) {
|
|
||||||
super(fileManager);
|
|
||||||
this.outDir = outDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) {
|
|
||||||
if (kind == JavaFileObject.Kind.CLASS) {
|
|
||||||
File file = new File(outDir, className.replace('.', '/') + ".class");
|
|
||||||
files.add(file);
|
|
||||||
return new ClassFileObject(file, kind);
|
|
||||||
}
|
|
||||||
throw new UnsupportedOperationException("Can't save location with kind: " + kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<File> outputFiles() {
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ClassFileObject extends SimpleJavaFileObject {
|
|
||||||
private final File file;
|
|
||||||
|
|
||||||
protected ClassFileObject(File file, Kind kind) {
|
|
||||||
super(file.toURI(), kind);
|
|
||||||
this.file = file;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OutputStream openOutputStream() throws IOException {
|
|
||||||
FileUtils.makeDirsForFile(file);
|
|
||||||
return new FileOutputStream(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+3
-3
@@ -4,11 +4,11 @@ import java.net.URI;
|
|||||||
|
|
||||||
import javax.tools.SimpleJavaFileObject;
|
import javax.tools.SimpleJavaFileObject;
|
||||||
|
|
||||||
public class CharSequenceJavaFileObject extends SimpleJavaFileObject {
|
public class StringJavaFileObject extends SimpleJavaFileObject {
|
||||||
|
|
||||||
private CharSequence content;
|
private final String content;
|
||||||
|
|
||||||
public CharSequenceJavaFileObject(String className, CharSequence content) {
|
public StringJavaFileObject(String className, String content) {
|
||||||
super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
|
super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
|
||||||
this.content = content;
|
this.content = content;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package jadx.tests.api.compiler;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.tools.DiagnosticListener;
|
||||||
|
import javax.tools.JavaCompiler;
|
||||||
|
import javax.tools.JavaCompiler.CompilationTask;
|
||||||
|
import javax.tools.JavaFileObject;
|
||||||
|
import javax.tools.ToolProvider;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
public class TestCompiler implements Closeable {
|
||||||
|
private final CompilerOptions options;
|
||||||
|
private final JavaCompiler compiler;
|
||||||
|
private final ClassFileManager fileManager;
|
||||||
|
|
||||||
|
public TestCompiler(CompilerOptions options) {
|
||||||
|
this.options = options;
|
||||||
|
int javaVersion = options.getJavaVersion();
|
||||||
|
if (!JavaUtils.checkJavaVersion(javaVersion)) {
|
||||||
|
throw new IllegalArgumentException("Current java version not meet requirement: "
|
||||||
|
+ "current: " + JavaUtils.JAVA_VERSION_INT + ", required: " + javaVersion);
|
||||||
|
}
|
||||||
|
if (options.isUseEclipseCompiler()) {
|
||||||
|
compiler = EclipseCompilerUtils.newInstance();
|
||||||
|
} else {
|
||||||
|
compiler = ToolProvider.getSystemJavaCompiler();
|
||||||
|
if (compiler == null) {
|
||||||
|
throw new IllegalStateException("Can not find compiler, please use JDK instead");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<File> compileFiles(List<File> sourceFiles, Path outTmp) throws IOException {
|
||||||
|
compile(fileManager.getJavaFileObjectsFromFiles(sourceFiles));
|
||||||
|
List<File> files = new ArrayList<>();
|
||||||
|
for (JavaClassObject classObject : fileManager.getClassLoader().getClassObjects()) {
|
||||||
|
Path path = outTmp.resolve(classObject.getName().replace('.', '/') + ".class");
|
||||||
|
FileUtils.makeDirsForFile(path);
|
||||||
|
Files.write(path, classObject.getBytes());
|
||||||
|
files.add(path.toFile());
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compileNodes(List<ClassNode> clsNodeList) {
|
||||||
|
List<JavaFileObject> jfObjects = new ArrayList<>(clsNodeList.size());
|
||||||
|
for (ClassNode clsNode : clsNodeList) {
|
||||||
|
jfObjects.add(new StringJavaFileObject(clsNode.getFullName(), clsNode.getCode().getCodeStr()));
|
||||||
|
}
|
||||||
|
compile(jfObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void compile(List<JavaFileObject> jfObjects) {
|
||||||
|
List<String> arguments = new ArrayList<>();
|
||||||
|
arguments.add(options.isIncludeDebugInfo() ? "-g" : "-g:none");
|
||||||
|
int javaVersion = options.getJavaVersion();
|
||||||
|
String javaVerStr = javaVersion <= 8 ? "1." + javaVersion : Integer.toString(javaVersion);
|
||||||
|
arguments.add("-source");
|
||||||
|
arguments.add(javaVerStr);
|
||||||
|
arguments.add("-target");
|
||||||
|
arguments.add(javaVerStr);
|
||||||
|
arguments.addAll(options.getArguments());
|
||||||
|
|
||||||
|
DiagnosticListener<? super JavaFileObject> diagnostic =
|
||||||
|
diagObj -> System.out.println("Compiler diagnostic: " + diagObj.getMessage(Locale.ROOT));
|
||||||
|
Writer out = new PrintWriter(System.out);
|
||||||
|
CompilationTask compilerTask = compiler.getTask(out, fileManager, diagnostic, arguments, null, jfObjects);
|
||||||
|
if (Boolean.FALSE.equals(compilerTask.call())) {
|
||||||
|
throw new RuntimeException("Compilation failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassLoader getClassLoader() {
|
||||||
|
return fileManager.getClassLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<?> getClass(String clsFullName) throws ClassNotFoundException {
|
||||||
|
return getClassLoader().loadClass(clsFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public Method getMethod(Class<?> cls, String methodName, Class<?>[] types) throws NoSuchMethodException {
|
||||||
|
return cls.getMethod(methodName, types);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object invoke(String clsFullName, String methodName, Class<?>[] types, Object[] args) {
|
||||||
|
try {
|
||||||
|
for (Class<?> type : types) {
|
||||||
|
checkType(type);
|
||||||
|
}
|
||||||
|
Class<?> cls = getClass(clsFullName);
|
||||||
|
Method mth = getMethod(cls, methodName, types);
|
||||||
|
Object inst = cls.getConstructor().newInstance();
|
||||||
|
assertNotNull(mth, "Failed to get method " + methodName + '(' + Arrays.toString(types) + ')');
|
||||||
|
return mth.invoke(inst, args);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
IntegrationTest.rethrow("Invoke error", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<?> checkType(Class<?> type) throws ClassNotFoundException {
|
||||||
|
if (type.isPrimitive()) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
if (type.isArray()) {
|
||||||
|
return checkType(type.getComponentType());
|
||||||
|
}
|
||||||
|
Class<?> cls = getClassLoader().loadClass(type.getName());
|
||||||
|
if (type != cls) {
|
||||||
|
throw new IllegalArgumentException("Internal test class cannot be used in method invoke");
|
||||||
|
}
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
fileManager.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-1
@@ -2,6 +2,7 @@ package jadx.tests.api.extensions.profiles;
|
|||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -35,7 +36,13 @@ public class JadxTestProfilesExtension implements TestTemplateInvocationContextP
|
|||||||
Preconditions.condition(!testAnnAdded, "@Test annotation should be removed");
|
Preconditions.condition(!testAnnAdded, "@Test annotation should be removed");
|
||||||
|
|
||||||
TestWithProfiles profilesAnn = AnnotationUtils.findAnnotation(testMethod, TestWithProfiles.class).get();
|
TestWithProfiles profilesAnn = AnnotationUtils.findAnnotation(testMethod, TestWithProfiles.class).get();
|
||||||
return Stream.of(profilesAnn.value())
|
EnumSet<TestProfile> profilesSet = EnumSet.noneOf(TestProfile.class);
|
||||||
|
Collections.addAll(profilesSet, profilesAnn.value());
|
||||||
|
if (profilesSet.contains(TestProfile.ALL)) {
|
||||||
|
Collections.addAll(profilesSet, TestProfile.values());
|
||||||
|
}
|
||||||
|
profilesSet.remove(TestProfile.ALL);
|
||||||
|
return profilesSet.stream()
|
||||||
.sorted()
|
.sorted()
|
||||||
.map(RunWithProfile::new);
|
.map(RunWithProfile::new);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,23 @@ import java.util.function.Consumer;
|
|||||||
import jadx.tests.api.IntegrationTest;
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
public enum TestProfile implements Consumer<IntegrationTest> {
|
public enum TestProfile implements Consumer<IntegrationTest> {
|
||||||
DX_J8("dx-java-8", test -> {
|
DX_J8("dx-j8", test -> {
|
||||||
test.useTargetJavaVersion(8);
|
test.useTargetJavaVersion(8);
|
||||||
test.useDexInput();
|
test.useDexInput("dx");
|
||||||
}),
|
}),
|
||||||
D8_J11("d8-java-11", test -> {
|
D8_J8("d8-j8", test -> {
|
||||||
|
test.useTargetJavaVersion(8);
|
||||||
|
test.useDexInput("d8");
|
||||||
|
}),
|
||||||
|
D8_J11("d8-j11", test -> {
|
||||||
test.useTargetJavaVersion(11);
|
test.useTargetJavaVersion(11);
|
||||||
test.useDexInput();
|
test.useDexInput("d8");
|
||||||
|
}),
|
||||||
|
D8_J11_DESUGAR("d8-j11-desugar", test -> {
|
||||||
|
test.useTargetJavaVersion(11);
|
||||||
|
test.useDexInput("d8");
|
||||||
|
test.keepParentClassOnInput();
|
||||||
|
test.getArgs().getPluginOptions().put("java-convert.d8-desugar", "yes");
|
||||||
}),
|
}),
|
||||||
JAVA8("java-8", test -> {
|
JAVA8("java-8", test -> {
|
||||||
test.useTargetJavaVersion(8);
|
test.useTargetJavaVersion(8);
|
||||||
@@ -20,7 +30,18 @@ public enum TestProfile implements Consumer<IntegrationTest> {
|
|||||||
JAVA11("java-11", test -> {
|
JAVA11("java-11", test -> {
|
||||||
test.useTargetJavaVersion(11);
|
test.useTargetJavaVersion(11);
|
||||||
test.useJavaInput();
|
test.useJavaInput();
|
||||||
});
|
}),
|
||||||
|
ECJ_DX_J8("ecj-dx-j8", test -> {
|
||||||
|
test.useEclipseCompiler();
|
||||||
|
test.useTargetJavaVersion(8);
|
||||||
|
test.useDexInput();
|
||||||
|
}),
|
||||||
|
ECJ_J8("ecj-j8", test -> {
|
||||||
|
test.useEclipseCompiler();
|
||||||
|
test.useTargetJavaVersion(8);
|
||||||
|
test.useJavaInput();
|
||||||
|
}),
|
||||||
|
ALL("all", null);
|
||||||
|
|
||||||
private final String description;
|
private final String description;
|
||||||
private final Consumer<IntegrationTest> setup;
|
private final Consumer<IntegrationTest> setup;
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
package jadx.tests.api.utils.assertj;
|
package jadx.tests.api.utils.assertj;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.assertj.core.api.AbstractObjectAssert;
|
import org.assertj.core.api.AbstractObjectAssert;
|
||||||
import org.assertj.core.api.Assertions;
|
import org.assertj.core.api.Assertions;
|
||||||
|
|
||||||
|
import jadx.api.CodePosition;
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.ICodeNode;
|
||||||
import jadx.tests.api.IntegrationTest;
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.fail;
|
||||||
|
|
||||||
public class JadxClassNodeAssertions extends AbstractObjectAssert<JadxClassNodeAssertions, ClassNode> {
|
public class JadxClassNodeAssertions extends AbstractObjectAssert<JadxClassNodeAssertions, ClassNode> {
|
||||||
public JadxClassNodeAssertions(ClassNode cls) {
|
public JadxClassNodeAssertions(ClassNode cls) {
|
||||||
@@ -52,4 +57,22 @@ public class JadxClassNodeAssertions extends AbstractObjectAssert<JadxClassNodeA
|
|||||||
testInstance.runDecompiledAutoCheck(actual);
|
testInstance.runDecompiledAutoCheck(actual);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void checkCodeAnnotationFor(String refStr, ICodeNode node) {
|
||||||
|
checkCodeAnnotationFor(refStr, 0, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkCodeAnnotationFor(String refStr, int refOffset, ICodeNode node) {
|
||||||
|
ICodeInfo code = actual.getCode();
|
||||||
|
int codePos = code.getCodeStr().indexOf(refStr);
|
||||||
|
assertThat(codePos).describedAs("String '%s' not found", refStr).isNotEqualTo(-1);
|
||||||
|
int refPos = codePos + refOffset;
|
||||||
|
for (Map.Entry<CodePosition, Object> entry : code.getAnnotations().entrySet()) {
|
||||||
|
if (entry.getKey().getPos() == refPos) {
|
||||||
|
Assertions.assertThat(entry.getValue()).isEqualTo(node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fail("Annotation for reference string: '%s' at position %d not found", refStr, refPos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,23 @@
|
|||||||
package jadx.tests.integration.arrays;
|
package jadx.tests.integration.arrays;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import jadx.NotYetImplemented;
|
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
|
||||||
import jadx.tests.api.IntegrationTest;
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestProfile;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestWithProfiles;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
|
|
||||||
public class TestArrayFill3 extends IntegrationTest {
|
public class TestArrayFill3 extends IntegrationTest {
|
||||||
|
|
||||||
public static class TestCls {
|
public static class TestCls {
|
||||||
|
public byte[] test() {
|
||||||
public byte[] test(int a) {
|
|
||||||
return new byte[] { 0, 1, 2 };
|
return new byte[] { 0, 1, 2 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestWithProfiles({ TestProfile.ECJ_J8, TestProfile.ECJ_DX_J8 })
|
||||||
@NotYetImplemented
|
|
||||||
public void test() {
|
public void test() {
|
||||||
useEclipseCompiler();
|
assertThat(getClassNode(TestCls.class))
|
||||||
ClassNode cls = getClassNode(TestCls.class);
|
.code()
|
||||||
String code = cls.getCode().toString();
|
.containsOne("return new byte[]{0, 1, 2}");
|
||||||
|
|
||||||
assertThat(code, containsString("return new byte[]{0, 1, 2}"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ public class TestFallbackMode extends IntegrationTest {
|
|||||||
|
|
||||||
assertThat(code, containsString("public int test(int r2) {"));
|
assertThat(code, containsString("public int test(int r2) {"));
|
||||||
assertThat(code, containsOne("r1 = this;"));
|
assertThat(code, containsOne("r1 = this;"));
|
||||||
assertThat(code, containsOne("L_0x0000:"));
|
assertThat(code, containsOne("L0:"));
|
||||||
assertThat(code, containsOne("L_0x0007:"));
|
assertThat(code, containsOne("L7:"));
|
||||||
assertThat(code, containsOne("int r2 = r2 + 1"));
|
assertThat(code, containsOne("int r2 = r2 + 1"));
|
||||||
assertThat(code, not(containsString("throw new UnsupportedOperationException")));
|
assertThat(code, not(containsString("throw new UnsupportedOperationException")));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ package jadx.tests.integration.inner;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.tests.api.SmaliTest;
|
import jadx.tests.api.SmaliTest;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
import static org.hamcrest.Matchers.not;
|
|
||||||
|
|
||||||
public class TestAnonymousClass14 extends SmaliTest {
|
public class TestAnonymousClass14 extends SmaliTest {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
@@ -44,11 +45,23 @@ public class TestAnonymousClass14 extends SmaliTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
ClassNode clsNode = getClassNodeFromSmaliFiles("inner", "TestAnonymousClass14", "OuterCls");
|
getArgs().setCommentsLevel(CommentsLevel.WARN);
|
||||||
String code = clsNode.getCode().toString();
|
ClassNode outerCls = getClassNodeFromSmaliFiles("OuterCls");
|
||||||
code = code.replaceAll("/\\*.*?\\*/", ""); // remove block comments
|
assertThat(outerCls).code()
|
||||||
|
.doesNotContain("synthetic", "AnonymousClass1")
|
||||||
|
.describedAs("only one constructor").containsOne("private TestCls(")
|
||||||
|
.describedAs("constructor without args").containsOne("private TestCls() {");
|
||||||
|
|
||||||
assertThat(code, not(containsString("AnonymousClass1")));
|
MethodNode makeTestClsMth = outerCls.searchMethodByShortName("makeTestCls");
|
||||||
assertThat(code, not(containsString("synthetic")));
|
assertThat(makeTestClsMth).isNotNull();
|
||||||
|
|
||||||
|
ClassNode testCls = searchCls(outerCls.getInnerClasses(), "TestCls");
|
||||||
|
MethodNode ctrMth = ListUtils.filterOnlyOne(testCls.getMethods(),
|
||||||
|
m -> m.isConstructor() && !m.getAccessFlags().isSynthetic());
|
||||||
|
assertThat(ctrMth).isNotNull();
|
||||||
|
assertThat(ctrMth.getUseIn()).hasSize(1);
|
||||||
|
assertThat(ctrMth.getUseIn().get(0)).isEqualTo(makeTestClsMth);
|
||||||
|
|
||||||
|
assertThat(outerCls).checkCodeAnnotationFor("new TestCls();", 4, ctrMth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,20 @@ package jadx.tests.integration.inner;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.tests.api.IntegrationTest;
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
import static org.hamcrest.Matchers.not;
|
|
||||||
|
|
||||||
public class TestOuterConstructorCall extends IntegrationTest {
|
public class TestOuterConstructorCall extends IntegrationTest {
|
||||||
|
|
||||||
|
@SuppressWarnings({ "InnerClassMayBeStatic", "unused" })
|
||||||
public static class TestCls {
|
public static class TestCls {
|
||||||
private TestCls(Inner inner) {
|
private TestCls(Inner inner) {
|
||||||
System.out.println(inner);
|
System.out.println(inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Inner {
|
private class Inner {
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private TestCls test() {
|
private TestCls test() {
|
||||||
return new TestCls(this);
|
return new TestCls(this);
|
||||||
}
|
}
|
||||||
@@ -26,11 +24,11 @@ public class TestOuterConstructorCall extends IntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
ClassNode cls = getClassNode(TestCls.class);
|
getArgs().setCommentsLevel(CommentsLevel.WARN);
|
||||||
String code = cls.getCode().toString();
|
assertThat(getClassNode(TestCls.class))
|
||||||
|
.code()
|
||||||
assertThat(code, containsString("private class Inner {"));
|
.containsOne("class Inner {")
|
||||||
assertThat(code, containsString("return new TestOuterConstructorCall$TestCls(this);"));
|
.containsOne("return new TestOuterConstructorCall$TestCls(this);")
|
||||||
assertThat(code, not(containsString("synthetic")));
|
.doesNotContain("synthetic", "this$0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package jadx.tests.integration.java8;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import jadx.NotYetImplemented;
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestProfile;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestWithProfiles;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestLambdaResugar extends IntegrationTest {
|
||||||
|
|
||||||
|
public static class TestCls {
|
||||||
|
private String field;
|
||||||
|
|
||||||
|
public void test() {
|
||||||
|
call(s -> {
|
||||||
|
this.field = s;
|
||||||
|
return s.length();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void call(Function<String, Integer> func) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotYetImplemented("Inline lambda methods")
|
||||||
|
@TestWithProfiles(TestProfile.D8_J11_DESUGAR)
|
||||||
|
public void test() {
|
||||||
|
assertThat(getClassNode(TestCls.class))
|
||||||
|
.code()
|
||||||
|
.doesNotContain("lambda$");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package jadx.tests.integration.names;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.api.CommentsLevel;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
import jadx.tests.integration.names.pkg.a;
|
||||||
|
import jadx.tests.integration.names.pkg.b;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestClassNamesCollision extends IntegrationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
getArgs().setCommentsLevel(CommentsLevel.WARN);
|
||||||
|
List<ClassNode> classNodes = getClassNodes(a.class, b.class);
|
||||||
|
|
||||||
|
assertThat(searchCls(classNodes, "a"))
|
||||||
|
.code()
|
||||||
|
.containsOne("public class a {")
|
||||||
|
.containsOne("public static a a() {");
|
||||||
|
|
||||||
|
assertThat(searchCls(classNodes, "b"))
|
||||||
|
.code()
|
||||||
|
.containsOne("class a {")
|
||||||
|
.containsOne("jadx.tests.integration.names.pkg.a a = jadx.tests.integration.names.pkg.a.a();");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package jadx.tests.integration.names;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.api.CommentsLevel;
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestClassNamesCollision2 extends IntegrationTest {
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public static class TestCls {
|
||||||
|
static class List {
|
||||||
|
public static List getList() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List list = List.getList();
|
||||||
|
|
||||||
|
protected void clearList(java.util.List l) {
|
||||||
|
l.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
getArgs().setCommentsLevel(CommentsLevel.WARN);
|
||||||
|
assertThat(getClassNode(TestCls.class))
|
||||||
|
.code()
|
||||||
|
.containsOne("static class List {")
|
||||||
|
.containsOne("protected void clearList(java.util.List l) {");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package jadx.tests.integration.names.pkg;
|
||||||
|
|
||||||
|
@SuppressWarnings({ "TypeName", "MethodName" })
|
||||||
|
public class a {
|
||||||
|
public static a a() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package jadx.tests.integration.names.pkg;
|
||||||
|
|
||||||
|
@SuppressWarnings("TypeName")
|
||||||
|
public class b {
|
||||||
|
class a {
|
||||||
|
}
|
||||||
|
|
||||||
|
private jadx.tests.integration.names.pkg.a a = jadx.tests.integration.names.pkg.a.a();
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package jadx.tests.integration.others;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Parameter;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestProfile;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestWithProfiles;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestMethodParametersAttribute extends IntegrationTest {
|
||||||
|
|
||||||
|
public static class TestCls {
|
||||||
|
public String test(String paramStr, final int number) {
|
||||||
|
return paramStr + number;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String paramNames() throws NoSuchMethodException {
|
||||||
|
Method testMethod = TestCls.class.getMethod("test", String.class, int.class);
|
||||||
|
return Arrays.stream(testMethod.getParameters())
|
||||||
|
.map(Parameter::getName)
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void check() throws NoSuchMethodException {
|
||||||
|
assertThat(paramNames()).isEqualTo("paramStr, number");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestWithProfiles({ TestProfile.JAVA8, TestProfile.D8_J11 })
|
||||||
|
public void test() {
|
||||||
|
getCompilerOptions().addArgument("-parameters");
|
||||||
|
noDebugInfo();
|
||||||
|
assertThat(getClassNode(TestCls.class))
|
||||||
|
.code()
|
||||||
|
.containsOne("public String test(String paramStr, final int number) {");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,9 +52,8 @@ public class TestStringConcatJava11 extends RaungTest {
|
|||||||
"return str + \"test\" + str + \"7\";"); // dynamic concat add const to string recipe
|
"return str + \"test\" + str + \"7\";"); // dynamic concat add const to string recipe
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestWithProfiles({ TestProfile.DX_J8, TestProfile.JAVA8 })
|
@TestWithProfiles({ TestProfile.D8_J11, TestProfile.JAVA11 })
|
||||||
public void testJava11() {
|
public void testJava11() {
|
||||||
useTargetJavaVersion(11);
|
|
||||||
noDebugInfo();
|
noDebugInfo();
|
||||||
assertThat(getClassNode(TestCls.class))
|
assertThat(getClassNode(TestCls.class))
|
||||||
.code()
|
.code()
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package jadx.tests.integration.types;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.tests.api.SmaliTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issue 1407
|
||||||
|
*/
|
||||||
|
public class TestTypeResolver19 extends SmaliTest {
|
||||||
|
|
||||||
|
public static class TestCls {
|
||||||
|
public static int[] test(byte[] bArr) {
|
||||||
|
int[] iArr = new int[bArr.length];
|
||||||
|
for (int i = 0; i < bArr.length; i++) {
|
||||||
|
iArr[i] = bArr[i];
|
||||||
|
}
|
||||||
|
return iArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] test2(byte[] bArr) {
|
||||||
|
int[] iArr = new int[bArr.length];
|
||||||
|
for (int i = 0; i < bArr.length; i++) {
|
||||||
|
int i2 = bArr[i];
|
||||||
|
if (i2 < 0) {
|
||||||
|
i2 = (int) ((long) i2 & 0xFFFF_FFFFL);
|
||||||
|
}
|
||||||
|
iArr[i] = i2;
|
||||||
|
}
|
||||||
|
return iArr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
noDebugInfo();
|
||||||
|
assertThat(getClassNode(TestCls.class))
|
||||||
|
.code()
|
||||||
|
.containsOne("iArr[i] = bArr[i];")
|
||||||
|
.containsOne("iArr[i] = i2;");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package jadx.tests.integration.types;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.tests.api.SmaliTest;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestProfile;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestWithProfiles;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issue 1238
|
||||||
|
*/
|
||||||
|
public class TestTypeResolver20 extends SmaliTest {
|
||||||
|
|
||||||
|
public static class TestCls {
|
||||||
|
public interface Sequence<T> {
|
||||||
|
Iterator<T> iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends Comparable<? super T>> T max(Sequence<? extends T> seq) {
|
||||||
|
Iterator<? extends T> it = seq.iterator();
|
||||||
|
if (!it.hasNext()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
T t = it.next();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
T next = it.next();
|
||||||
|
if (t.compareTo(next) < 0) {
|
||||||
|
t = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ArraySeq<T> implements Sequence<T> {
|
||||||
|
private final List<T> list;
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public ArraySeq(T... arr) {
|
||||||
|
this.list = Arrays.asList(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return list.iterator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void check() {
|
||||||
|
assertThat(max(new ArraySeq<>(2, 5, 3, 4))).isEqualTo(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestWithProfiles({ TestProfile.DX_J8, TestProfile.JAVA8 })
|
||||||
|
public void test() {
|
||||||
|
noDebugInfo();
|
||||||
|
assertThat(getClassNode(TestCls.class))
|
||||||
|
.code()
|
||||||
|
.doesNotContain("next = next;")
|
||||||
|
.containsOne("T next = it.next();");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmali() {
|
||||||
|
assertThat(getClassNodeFromSmaliFiles())
|
||||||
|
.code()
|
||||||
|
.containsOne("T next = it.next();")
|
||||||
|
.containsOne("T next2 = it.next();");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
.class public interface abstract Lkotlin/sequences/Sequence;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.source "SourceFile"
|
||||||
|
|
||||||
|
.annotation system Ldalvik/annotation/Signature;
|
||||||
|
value = {
|
||||||
|
"<T:",
|
||||||
|
"Ljava/lang/Object;",
|
||||||
|
">",
|
||||||
|
"Ljava/lang/Object;"
|
||||||
|
}
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
.method public abstract iterator()Ljava/util/Iterator;
|
||||||
|
.annotation system Ldalvik/annotation/Signature;
|
||||||
|
value = {
|
||||||
|
"()",
|
||||||
|
"Ljava/util/Iterator<",
|
||||||
|
"TT;>;"
|
||||||
|
}
|
||||||
|
.end annotation
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
.class public Ltypes/TestTypeResolver20;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.source "SourceFile"
|
||||||
|
|
||||||
|
|
||||||
|
.method public static final max(Lkotlin/sequences/Sequence;)Ljava/lang/Comparable;
|
||||||
|
.registers 4
|
||||||
|
.annotation system Ldalvik/annotation/Signature;
|
||||||
|
value = {
|
||||||
|
"<T::",
|
||||||
|
"Ljava/lang/Comparable<",
|
||||||
|
"-TT;>;>(",
|
||||||
|
"Lkotlin/sequences/Sequence<",
|
||||||
|
"+TT;>;)TT;"
|
||||||
|
}
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
.line 1147
|
||||||
|
invoke-interface {p0}, Lkotlin/sequences/Sequence;->iterator()Ljava/util/Iterator;
|
||||||
|
move-result-object p0
|
||||||
|
|
||||||
|
.line 1148
|
||||||
|
invoke-interface {p0}, Ljava/util/Iterator;->hasNext()Z
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
if-nez v0, :cond_11
|
||||||
|
|
||||||
|
const/4 p0, 0x0
|
||||||
|
return-object p0
|
||||||
|
|
||||||
|
.line 1149
|
||||||
|
:cond_11
|
||||||
|
invoke-interface {p0}, Ljava/util/Iterator;->next()Ljava/lang/Object;
|
||||||
|
move-result-object v0
|
||||||
|
check-cast v0, Ljava/lang/Comparable;
|
||||||
|
|
||||||
|
.line 1150
|
||||||
|
:cond_17
|
||||||
|
:goto_17
|
||||||
|
invoke-interface {p0}, Ljava/util/Iterator;->hasNext()Z
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
if-eqz v1, :cond_2b
|
||||||
|
|
||||||
|
.line 1151
|
||||||
|
invoke-interface {p0}, Ljava/util/Iterator;->next()Ljava/lang/Object;
|
||||||
|
move-result-object v1
|
||||||
|
check-cast v1, Ljava/lang/Comparable;
|
||||||
|
|
||||||
|
.line 1152
|
||||||
|
invoke-interface {v0, v1}, Ljava/lang/Comparable;->compareTo(Ljava/lang/Object;)I
|
||||||
|
move-result v2
|
||||||
|
|
||||||
|
if-gez v2, :cond_17
|
||||||
|
|
||||||
|
move-object v0, v1
|
||||||
|
goto :goto_17
|
||||||
|
|
||||||
|
:cond_2b
|
||||||
|
return-object v0
|
||||||
|
.end method
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'application'
|
id 'application'
|
||||||
id 'edu.sc.seis.launch4j' version '2.5.1'
|
id 'edu.sc.seis.launch4j' version '2.5.3'
|
||||||
id 'com.github.johnrengelman.shadow' version '7.1.2'
|
id 'com.github.johnrengelman.shadow' version '7.1.2'
|
||||||
id 'org.beryx.runtime' version '1.12.7'
|
id 'org.beryx.runtime' version '1.12.7'
|
||||||
}
|
}
|
||||||
@@ -10,15 +10,15 @@ dependencies {
|
|||||||
|
|
||||||
implementation(project(":jadx-cli"))
|
implementation(project(":jadx-cli"))
|
||||||
implementation 'com.beust:jcommander:1.82'
|
implementation 'com.beust:jcommander:1.82'
|
||||||
implementation 'ch.qos.logback:logback-classic:1.2.10'
|
implementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||||
|
|
||||||
implementation 'com.fifesoft:rsyntaxtextarea:3.1.6'
|
implementation 'com.fifesoft:rsyntaxtextarea:3.2.0'
|
||||||
implementation files('libs/jfontchooser-1.0.5.jar')
|
implementation files('libs/jfontchooser-1.0.5.jar')
|
||||||
implementation 'hu.kazocsaba:image-viewer:1.2.3'
|
implementation 'hu.kazocsaba:image-viewer:1.2.3'
|
||||||
|
|
||||||
implementation 'com.formdev:flatlaf:2.0.1'
|
implementation 'com.formdev:flatlaf:2.1'
|
||||||
implementation 'com.formdev:flatlaf-intellij-themes:2.0.1'
|
implementation 'com.formdev:flatlaf-intellij-themes:2.1'
|
||||||
implementation 'com.formdev:flatlaf-extras:2.0.1'
|
implementation 'com.formdev:flatlaf-extras:2.1'
|
||||||
implementation 'com.formdev:svgSalamander:1.1.3'
|
implementation 'com.formdev:svgSalamander:1.1.3'
|
||||||
|
|
||||||
implementation 'com.google.code.gson:gson:2.9.0'
|
implementation 'com.google.code.gson:gson:2.9.0'
|
||||||
@@ -34,6 +34,9 @@ dependencies {
|
|||||||
application {
|
application {
|
||||||
applicationName = 'jadx-gui'
|
applicationName = 'jadx-gui'
|
||||||
mainClass.set('jadx.gui.JadxGUI')
|
mainClass.set('jadx.gui.JadxGUI')
|
||||||
|
// The option -XX:+UseG1GC is only relevant for Java 8. Starting with Java 9 G1GC is already the default GC
|
||||||
|
applicationDefaultJvmArgs = ['-Xms128M', '-XX:MaxRAMPercentage=70.0', '-XX:+UseG1GC',
|
||||||
|
'-Dawt.useSystemAAFontSettings=lcd', '-Dswing.aatext=true']
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationDistribution.with {
|
applicationDistribution.with {
|
||||||
@@ -62,8 +65,6 @@ shadowJar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startScripts {
|
startScripts {
|
||||||
// The option -XX:+UseG1GC is only relevant for Java 8. Starting with Java 9 G1GC is already the default GC
|
|
||||||
defaultJvmOpts = ['-Xms128M', '-Xmx4g', '-Dawt.useSystemAAFontSettings=lcd', '-Dswing.aatext=true', '-XX:+UseG1GC']
|
|
||||||
doLast {
|
doLast {
|
||||||
def str = windowsScript.text
|
def str = windowsScript.text
|
||||||
str = str.replaceAll('java.exe', 'javaw.exe')
|
str = str.replaceAll('java.exe', 'javaw.exe')
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import jadx.cli.LogHelper;
|
import jadx.cli.LogHelper;
|
||||||
import jadx.gui.settings.JadxSettings;
|
import jadx.gui.settings.JadxSettings;
|
||||||
import jadx.gui.settings.JadxSettingsAdapter;
|
import jadx.gui.settings.JadxSettingsAdapter;
|
||||||
|
import jadx.gui.ui.ExceptionDialog;
|
||||||
import jadx.gui.ui.MainWindow;
|
import jadx.gui.ui.MainWindow;
|
||||||
import jadx.gui.utils.LafManager;
|
import jadx.gui.utils.LafManager;
|
||||||
import jadx.gui.utils.NLS;
|
import jadx.gui.utils.NLS;
|
||||||
@@ -26,10 +27,13 @@ public class JadxGUI {
|
|||||||
if (!settings.overrideProvided(args)) {
|
if (!settings.overrideProvided(args)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
LogHelper.initLogLevel(settings);
|
||||||
|
LogHelper.setLogLevelsForDecompileStage();
|
||||||
printSystemInfo();
|
printSystemInfo();
|
||||||
|
|
||||||
LafManager.init(settings);
|
LafManager.init(settings);
|
||||||
NLS.setLocale(settings.getLangLocale());
|
NLS.setLocale(settings.getLangLocale());
|
||||||
|
ExceptionDialog.registerUncaughtExceptionHandler();
|
||||||
SwingUtilities.invokeLater(new MainWindow(settings)::init);
|
SwingUtilities.invokeLater(new MainWindow(settings)::init);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Error: {}", e.getMessage(), e);
|
LOG.error("Error: {}", e.getMessage(), e);
|
||||||
|
|||||||
@@ -16,9 +16,14 @@ import jadx.api.JadxDecompiler;
|
|||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
import jadx.api.JavaPackage;
|
import jadx.api.JavaPackage;
|
||||||
import jadx.api.ResourceFile;
|
import jadx.api.ResourceFile;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.ProcessState;
|
||||||
import jadx.gui.settings.JadxProject;
|
import jadx.gui.settings.JadxProject;
|
||||||
import jadx.gui.settings.JadxSettings;
|
import jadx.gui.settings.JadxSettings;
|
||||||
|
|
||||||
|
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
|
||||||
|
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||||
|
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
||||||
import static jadx.gui.utils.FileUtils.toFiles;
|
import static jadx.gui.utils.FileUtils.toFiles;
|
||||||
|
|
||||||
public class JadxWrapper {
|
public class JadxWrapper {
|
||||||
@@ -26,11 +31,12 @@ public class JadxWrapper {
|
|||||||
|
|
||||||
private final JadxSettings settings;
|
private final JadxSettings settings;
|
||||||
private JadxDecompiler decompiler;
|
private JadxDecompiler decompiler;
|
||||||
private JadxProject project;
|
private @Nullable JadxProject project;
|
||||||
private List<Path> openPaths = Collections.emptyList();
|
private List<Path> openPaths = Collections.emptyList();
|
||||||
|
|
||||||
public JadxWrapper(JadxSettings settings) {
|
public JadxWrapper(JadxSettings settings) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
this.decompiler = new JadxDecompiler(settings.toJadxArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openFile(List<Path> paths) {
|
public void openFile(List<Path> paths) {
|
||||||
@@ -39,8 +45,9 @@ public class JadxWrapper {
|
|||||||
try {
|
try {
|
||||||
JadxArgs jadxArgs = settings.toJadxArgs();
|
JadxArgs jadxArgs = settings.toJadxArgs();
|
||||||
jadxArgs.setInputFiles(toFiles(paths));
|
jadxArgs.setInputFiles(toFiles(paths));
|
||||||
jadxArgs.setCodeData(project.getCodeData());
|
if (project != null) {
|
||||||
|
jadxArgs.setCodeData(project.getCodeData());
|
||||||
|
}
|
||||||
this.decompiler = new JadxDecompiler(jadxArgs);
|
this.decompiler = new JadxDecompiler(jadxArgs);
|
||||||
this.decompiler.load();
|
this.decompiler.load();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -49,13 +56,20 @@ public class JadxWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: check and move into core package
|
||||||
|
public void unloadClasses() {
|
||||||
|
for (ClassNode cls : decompiler.getRoot().getClasses()) {
|
||||||
|
ProcessState clsState = cls.getState();
|
||||||
|
cls.unload();
|
||||||
|
cls.setState(clsState == PROCESS_COMPLETE ? GENERATED_AND_UNLOADED : NOT_LOADED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
if (decompiler != null) {
|
try {
|
||||||
try {
|
decompiler.close();
|
||||||
decompiler.close();
|
} catch (Exception e) {
|
||||||
} catch (Exception e) {
|
LOG.error("jadx decompiler close error", e);
|
||||||
LOG.error("jadx decompiler close error", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.openPaths = Collections.emptyList();
|
this.openPaths = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package jadx.gui.device.debugger;
|
package jadx.gui.device.debugger;
|
||||||
|
|
||||||
|
import java.io.Reader;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.AbstractMap.SimpleEntry;
|
import java.util.AbstractMap.SimpleEntry;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -14,6 +16,8 @@ import java.util.Map;
|
|||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -28,52 +32,39 @@ import jadx.gui.treemodel.JClass;
|
|||||||
public class BreakpointManager {
|
public class BreakpointManager {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(BreakpointManager.class);
|
private static final Logger LOG = LoggerFactory.getLogger(BreakpointManager.class);
|
||||||
|
|
||||||
private static Gson gson = null;
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
private static final Type TYPE_TOKEN = new TypeToken<Map<String, List<FileBreakpoint>>>() {
|
private static final Type TYPE_TOKEN = new TypeToken<Map<String, List<FileBreakpoint>>>() {
|
||||||
}.getType();
|
}.getType();
|
||||||
|
|
||||||
private static Map<String, List<FileBreakpoint>> bpm;
|
private static @NotNull Map<String, List<FileBreakpoint>> bpm = Collections.emptyMap();
|
||||||
private static Path savePath;
|
private static @Nullable Path savePath;
|
||||||
private static DebugController debugController;
|
private static DebugController debugController;
|
||||||
private static Map<String, Entry<ClassNode, Listener>> listeners = Collections.emptyMap(); // class full name as key
|
private static Map<String, Entry<ClassNode, Listener>> listeners = Collections.emptyMap(); // class full name as key
|
||||||
|
|
||||||
public static void saveAndExit() {
|
public static void saveAndExit() {
|
||||||
if (bpm != null) {
|
sync();
|
||||||
if (bpm.size() == 0 && !Files.exists(savePath)) {
|
bpm = Collections.emptyMap();
|
||||||
return; // user didn't do anything with breakpoint so don't output breakpoint file.
|
savePath = null;
|
||||||
}
|
listeners = Collections.emptyMap();
|
||||||
sync();
|
|
||||||
bpm = null;
|
|
||||||
savePath = null;
|
|
||||||
listeners = Collections.emptyMap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void init(Path dirPath) {
|
public static void init(@Nullable Path baseDir) {
|
||||||
if (gson == null) {
|
Path saveDir = baseDir != null ? baseDir : Paths.get(".");
|
||||||
gson = new GsonBuilder()
|
savePath = saveDir.resolve("breakpoints.json"); // TODO: move into project file or same dir as project file
|
||||||
.setPrettyPrinting()
|
|
||||||
.create();
|
|
||||||
}
|
|
||||||
savePath = dirPath.resolve("breakpoints.json");
|
|
||||||
if (Files.exists(savePath)) {
|
if (Files.exists(savePath)) {
|
||||||
try {
|
try (Reader reader = Files.newBufferedReader(savePath, StandardCharsets.UTF_8)) {
|
||||||
byte[] bytes = Files.readAllBytes(savePath);
|
bpm = GSON.fromJson(reader, TYPE_TOKEN);
|
||||||
bpm = gson.fromJson(new String(bytes, StandardCharsets.UTF_8), TYPE_TOKEN);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to read breakpoints config: {}", savePath, e);
|
LOG.error("Failed to read breakpoints config: {}", savePath, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bpm == null) {
|
|
||||||
bpm = Collections.emptyMap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param listener When breakpoint is failed to set during debugging, this listener will be called.
|
* @param listener When breakpoint is failed to set during debugging, this listener will be called.
|
||||||
*/
|
*/
|
||||||
public static void addListener(JClass topCls, Listener listener) {
|
public static void addListener(JClass topCls, Listener listener) {
|
||||||
if (listeners == Collections.EMPTY_MAP) {
|
if (listeners.isEmpty()) {
|
||||||
listeners = new HashMap<>();
|
listeners = new HashMap<>();
|
||||||
}
|
}
|
||||||
listeners.put(DbgUtils.getRawFullName(topCls),
|
listeners.put(DbgUtils.getRawFullName(topCls),
|
||||||
@@ -149,8 +140,15 @@ public class BreakpointManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void sync() {
|
private static void sync() {
|
||||||
|
if (savePath == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bpm.isEmpty() && !Files.exists(savePath)) {
|
||||||
|
// user didn't do anything with breakpoint so don't output breakpoint file.
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Files.write(savePath, gson.toJson(bpm).getBytes(StandardCharsets.UTF_8));
|
Files.write(savePath, GSON.toJson(bpm).getBytes(StandardCharsets.UTF_8));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to write breakpoints config: {}", savePath, e);
|
LOG.error("Failed to write breakpoints config: {}", savePath, e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -33,7 +34,6 @@ import jadx.gui.device.debugger.SmaliDebugger.RuntimeField;
|
|||||||
import jadx.gui.device.debugger.SmaliDebugger.RuntimeRegister;
|
import jadx.gui.device.debugger.SmaliDebugger.RuntimeRegister;
|
||||||
import jadx.gui.device.debugger.SmaliDebugger.RuntimeValue;
|
import jadx.gui.device.debugger.SmaliDebugger.RuntimeValue;
|
||||||
import jadx.gui.device.debugger.SmaliDebugger.RuntimeVarInfo;
|
import jadx.gui.device.debugger.SmaliDebugger.RuntimeVarInfo;
|
||||||
import jadx.gui.device.debugger.SmaliDebugger.SmaliDebuggerException;
|
|
||||||
import jadx.gui.device.debugger.smali.Smali;
|
import jadx.gui.device.debugger.smali.Smali;
|
||||||
import jadx.gui.device.debugger.smali.SmaliRegister;
|
import jadx.gui.device.debugger.smali.SmaliRegister;
|
||||||
import jadx.gui.treemodel.JClass;
|
import jadx.gui.treemodel.JClass;
|
||||||
@@ -41,8 +41,7 @@ import jadx.gui.ui.panel.IDebugController;
|
|||||||
import jadx.gui.ui.panel.JDebuggerPanel;
|
import jadx.gui.ui.panel.JDebuggerPanel;
|
||||||
import jadx.gui.ui.panel.JDebuggerPanel.IListElement;
|
import jadx.gui.ui.panel.JDebuggerPanel.IListElement;
|
||||||
import jadx.gui.ui.panel.JDebuggerPanel.ValueTreeNode;
|
import jadx.gui.ui.panel.JDebuggerPanel.ValueTreeNode;
|
||||||
|
import jadx.gui.utils.NLS;
|
||||||
import static jadx.gui.device.debugger.SmaliDebugger.RuntimeType;
|
|
||||||
|
|
||||||
public final class DebugController implements SmaliDebugger.SuspendListener, IDebugController {
|
public final class DebugController implements SmaliDebugger.SuspendListener, IDebugController {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DebugController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(DebugController.class);
|
||||||
@@ -72,23 +71,22 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
|
|||||||
private final ExecutorService updateQueue = Executors.newSingleThreadExecutor();
|
private final ExecutorService updateQueue = Executors.newSingleThreadExecutor();
|
||||||
private final ExecutorService lazyQueue = Executors.newSingleThreadExecutor();
|
private final ExecutorService lazyQueue = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
/**
|
|
||||||
* @param args at least 3 elements, host, port and android release version respectively.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean startDebugger(JDebuggerPanel debuggerPanel, String[] args) {
|
public boolean startDebugger(JDebuggerPanel debuggerPanel, String adbHost, int adbPort, int androidVer) {
|
||||||
if (TYPE_MAP.isEmpty()) {
|
if (TYPE_MAP.isEmpty()) {
|
||||||
initTypeMap();
|
initTypeMap();
|
||||||
}
|
}
|
||||||
this.debuggerPanel = debuggerPanel;
|
this.debuggerPanel = debuggerPanel;
|
||||||
debuggerPanel.resetUI();
|
debuggerPanel.resetUI();
|
||||||
try {
|
try {
|
||||||
debugger = SmaliDebugger.attach(args[0], Integer.parseInt(args[1]), this);
|
debugger = SmaliDebugger.attach(adbHost, adbPort, this);
|
||||||
} catch (SmaliDebuggerException e) {
|
} catch (SmaliDebuggerException e) {
|
||||||
|
JOptionPane.showMessageDialog(debuggerPanel.getMainWindow(), e.getMessage(),
|
||||||
|
NLS.str("error_dialog.title"), JOptionPane.ERROR_MESSAGE);
|
||||||
logErr(e);
|
logErr(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
art = ArtAdapter.getAdapter(Integer.parseInt(args[2]));
|
art = ArtAdapter.getAdapter(androidVer);
|
||||||
resetAllInfo();
|
resetAllInfo();
|
||||||
hasResumed = false;
|
hasResumed = false;
|
||||||
run = debugger::resume;
|
run = debugger::resume;
|
||||||
@@ -359,7 +357,7 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuspendEvent(SmaliDebugger.SuspendInfo info) {
|
public void onSuspendEvent(SuspendInfo info) {
|
||||||
if (!isDebugging()) {
|
if (!isDebugging()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package jadx.gui.device.debugger;
|
||||||
|
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.BreakpointEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.ClassPrepareEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.ClassUnloadEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.ExceptionEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.FieldAccessEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.FieldModificationEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.MethodEntryEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.MethodExitEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.MethodExitWithReturnValueEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnterEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnteredEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.MonitorWaitEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.MonitorWaitedEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.SingleStepEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.ThreadDeathEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.ThreadStartEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.VMDeathEvent;
|
||||||
|
import io.github.hqktech.JDWP.Event.Composite.VMStartEvent;
|
||||||
|
|
||||||
|
abstract class EventListenerAdapter {
|
||||||
|
void onVMStart(VMStartEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onVMDeath(VMDeathEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onSingleStep(SingleStepEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onBreakpoint(BreakpointEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMethodEntry(MethodEntryEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMethodExit(MethodExitEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMethodExitWithReturnValue(MethodExitWithReturnValueEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMonitorContendedEnter(MonitorContendedEnterEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMonitorContendedEntered(MonitorContendedEnteredEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMonitorWait(MonitorWaitEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMonitorWaited(MonitorWaitedEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onException(ExceptionEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onThreadStart(ThreadStartEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onThreadDeath(ThreadDeathEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onClassPrepare(ClassPrepareEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onClassUnload(ClassUnloadEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFieldAccess(FieldAccessEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFieldModification(FieldModificationEvent event) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package jadx.gui.device.debugger;
|
||||||
|
|
||||||
|
import io.github.hqktech.JDWP;
|
||||||
|
|
||||||
|
public enum RuntimeType {
|
||||||
|
ARRAY(91, "[]"),
|
||||||
|
BYTE(66, "byte"),
|
||||||
|
CHAR(67, "char"),
|
||||||
|
OBJECT(76, "object"),
|
||||||
|
FLOAT(70, "float"),
|
||||||
|
DOUBLE(68, "double"),
|
||||||
|
INT(73, "int"),
|
||||||
|
LONG(74, "long"),
|
||||||
|
SHORT(83, "short"),
|
||||||
|
VOID(86, "void"),
|
||||||
|
BOOLEAN(90, "boolean"),
|
||||||
|
STRING(115, "string"),
|
||||||
|
THREAD(116, "thread"),
|
||||||
|
THREAD_GROUP(103, "thread_group"),
|
||||||
|
CLASS_LOADER(108, "class_loader"),
|
||||||
|
CLASS_OBJECT(99, "class_object");
|
||||||
|
|
||||||
|
private final int jdwpTag;
|
||||||
|
private final String desc;
|
||||||
|
|
||||||
|
RuntimeType(int tag, String desc) {
|
||||||
|
this.jdwpTag = tag;
|
||||||
|
this.desc = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTag() {
|
||||||
|
return jdwpTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDesc() {
|
||||||
|
return this.desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a <code>JDWP.Tag</code> to a {@link RuntimeType}
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @return
|
||||||
|
* @throws SmaliDebuggerException
|
||||||
|
*/
|
||||||
|
public static RuntimeType fromJdwpTag(int tag) throws SmaliDebuggerException {
|
||||||
|
switch (tag) {
|
||||||
|
case JDWP.Tag.ARRAY:
|
||||||
|
return RuntimeType.ARRAY;
|
||||||
|
case JDWP.Tag.BYTE:
|
||||||
|
return RuntimeType.BYTE;
|
||||||
|
case JDWP.Tag.CHAR:
|
||||||
|
return RuntimeType.CHAR;
|
||||||
|
case JDWP.Tag.OBJECT:
|
||||||
|
return RuntimeType.OBJECT;
|
||||||
|
case JDWP.Tag.FLOAT:
|
||||||
|
return RuntimeType.FLOAT;
|
||||||
|
case JDWP.Tag.DOUBLE:
|
||||||
|
return RuntimeType.DOUBLE;
|
||||||
|
case JDWP.Tag.INT:
|
||||||
|
return RuntimeType.INT;
|
||||||
|
case JDWP.Tag.LONG:
|
||||||
|
return RuntimeType.LONG;
|
||||||
|
case JDWP.Tag.SHORT:
|
||||||
|
return RuntimeType.SHORT;
|
||||||
|
case JDWP.Tag.VOID:
|
||||||
|
return RuntimeType.VOID;
|
||||||
|
case JDWP.Tag.BOOLEAN:
|
||||||
|
return RuntimeType.BOOLEAN;
|
||||||
|
case JDWP.Tag.STRING:
|
||||||
|
return RuntimeType.STRING;
|
||||||
|
case JDWP.Tag.THREAD:
|
||||||
|
return RuntimeType.THREAD;
|
||||||
|
case JDWP.Tag.THREAD_GROUP:
|
||||||
|
return RuntimeType.THREAD_GROUP;
|
||||||
|
case JDWP.Tag.CLASS_LOADER:
|
||||||
|
return RuntimeType.CLASS_LOADER;
|
||||||
|
case JDWP.Tag.CLASS_OBJECT:
|
||||||
|
return RuntimeType.CLASS_OBJECT;
|
||||||
|
default:
|
||||||
|
throw new SmaliDebuggerException("Unexpected value: " + tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import java.util.concurrent.Executors;
|
|||||||
import java.util.concurrent.SynchronousQueue;
|
import java.util.concurrent.SynchronousQueue;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -71,10 +72,10 @@ import io.github.hqktech.JDWP.VirtualMachine.AllThreads.AllThreadsReplyData;
|
|||||||
import io.github.hqktech.JDWP.VirtualMachine.AllThreads.AllThreadsReplyDataThreads;
|
import io.github.hqktech.JDWP.VirtualMachine.AllThreads.AllThreadsReplyDataThreads;
|
||||||
import io.github.hqktech.JDWP.VirtualMachine.CreateString.CreateStringReplyData;
|
import io.github.hqktech.JDWP.VirtualMachine.CreateString.CreateStringReplyData;
|
||||||
import io.reactivex.annotations.NonNull;
|
import io.reactivex.annotations.NonNull;
|
||||||
import io.reactivex.annotations.Nullable;
|
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.AccessFlags;
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.gui.device.debugger.smali.RegisterInfo;
|
import jadx.gui.device.debugger.smali.RegisterInfo;
|
||||||
|
import jadx.gui.utils.IOUtils;
|
||||||
import jadx.gui.utils.ObjectPool;
|
import jadx.gui.utils.ObjectPool;
|
||||||
|
|
||||||
// TODO: Finish error notification, inner errors should be logged let user notice.
|
// TODO: Finish error notification, inner errors should be logged let user notice.
|
||||||
@@ -83,9 +84,9 @@ public class SmaliDebugger {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SmaliDebugger.class);
|
private static final Logger LOG = LoggerFactory.getLogger(SmaliDebugger.class);
|
||||||
private final JDWP jdwp;
|
private final JDWP jdwp;
|
||||||
private int localTcpPort;
|
private final int localTcpPort;
|
||||||
private InputStream inputStream;
|
private final InputStream inputStream;
|
||||||
private OutputStream outputStream;
|
private final OutputStream outputStream;
|
||||||
|
|
||||||
// All event callbacks will be called in this queue, e.g. class prepare/unload
|
// All event callbacks will be called in this queue, e.g. class prepare/unload
|
||||||
private static final Executor EVENT_LISTENER_QUEUE = Executors.newSingleThreadExecutor();
|
private static final Executor EVENT_LISTENER_QUEUE = Executors.newSingleThreadExecutor();
|
||||||
@@ -118,10 +119,13 @@ public class SmaliDebugger {
|
|||||||
private static final ICommandResult SKIP_RESULT = res -> {
|
private static final ICommandResult SKIP_RESULT = res -> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private SmaliDebugger(SuspendListener suspendListener, int localTcpPort, JDWP jdwp) {
|
private SmaliDebugger(SuspendListener suspendListener, int localTcpPort, JDWP jdwp, InputStream inputStream,
|
||||||
|
OutputStream outputStream) {
|
||||||
this.jdwp = jdwp;
|
this.jdwp = jdwp;
|
||||||
this.localTcpPort = localTcpPort;
|
this.localTcpPort = localTcpPort;
|
||||||
this.suspendListener = suspendListener;
|
this.suspendListener = suspendListener;
|
||||||
|
this.inputStream = inputStream;
|
||||||
|
this.outputStream = outputStream;
|
||||||
|
|
||||||
oneOffEventReq = jdwp.eventRequest().cmdSet().newCountRequest();
|
oneOffEventReq = jdwp.eventRequest().cmdSet().newCountRequest();
|
||||||
oneOffEventReq.count = 1;
|
oneOffEventReq.count = 1;
|
||||||
@@ -135,6 +139,7 @@ public class SmaliDebugger {
|
|||||||
try {
|
try {
|
||||||
byte[] bytes = JDWP.IDSizes.encode().getBytes();
|
byte[] bytes = JDWP.IDSizes.encode().getBytes();
|
||||||
JDWP.setPacketID(bytes, 1);
|
JDWP.setPacketID(bytes, 1);
|
||||||
|
LOG.debug("Connecting to ADB {}:{}", host, port);
|
||||||
Socket socket = new Socket(host, port);
|
Socket socket = new Socket(host, port);
|
||||||
InputStream inputStream = socket.getInputStream();
|
InputStream inputStream = socket.getInputStream();
|
||||||
OutputStream outputStream = socket.getOutputStream();
|
OutputStream outputStream = socket.getOutputStream();
|
||||||
@@ -143,16 +148,14 @@ public class SmaliDebugger {
|
|||||||
JDWP jdwp = initJDWP(outputStream, inputStream);
|
JDWP jdwp = initJDWP(outputStream, inputStream);
|
||||||
socket.setSoTimeout(0); // set back to 0 so the decodingLoop won't break for timeout.
|
socket.setSoTimeout(0); // set back to 0 so the decodingLoop won't break for timeout.
|
||||||
|
|
||||||
SmaliDebugger debugger = new SmaliDebugger(suspendListener, port, jdwp);
|
SmaliDebugger debugger = new SmaliDebugger(suspendListener, port, jdwp, inputStream, outputStream);
|
||||||
debugger.inputStream = inputStream;
|
|
||||||
debugger.outputStream = outputStream;
|
|
||||||
|
|
||||||
debugger.decodingLoop();
|
debugger.decodingLoop();
|
||||||
debugger.listenClassUnloadEvent();
|
debugger.listenClassUnloadEvent();
|
||||||
debugger.initPools();
|
debugger.initPools();
|
||||||
return debugger;
|
return debugger;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new SmaliDebuggerException(e);
|
throw new SmaliDebuggerException("Attach failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +265,7 @@ public class SmaliDebugger {
|
|||||||
for (int i = 0; i < values.size(); i++) {
|
for (int i = 0; i < values.size(); i++) {
|
||||||
ObjectReference.GetValues.GetValuesReplyDataValues value = values.get(i);
|
ObjectReference.GetValues.GetValuesReplyDataValues value = values.get(i);
|
||||||
flds.get(i).setValue(value.value.idOrValue)
|
flds.get(i).setValue(value.value.idOrValue)
|
||||||
.setType(getType(value.value.tag));
|
.setType(RuntimeType.fromJdwpTag(value.value.tag));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,7 +505,7 @@ public class SmaliDebugger {
|
|||||||
ObjectReference.GetValues.GetValuesReplyData data =
|
ObjectReference.GetValues.GetValuesReplyData data =
|
||||||
jdwp.objectReference().cmdGetValues().decode(res.getBuf(), JDWP.PACKET_HEADER_SIZE);
|
jdwp.objectReference().cmdGetValues().decode(res.getBuf(), JDWP.PACKET_HEADER_SIZE);
|
||||||
fld.setValue(data.values.get(0).value.idOrValue)
|
fld.setValue(data.values.get(0).value.idOrValue)
|
||||||
.setType(getType(data.values.get(0).value.tag));
|
.setType(RuntimeType.fromJdwpTag(data.values.get(0).value.tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
private long createString(String localStr) throws SmaliDebuggerException {
|
private long createString(String localStr) throws SmaliDebuggerException {
|
||||||
@@ -617,8 +620,8 @@ public class SmaliDebugger {
|
|||||||
Packet res = readPacket(inputStream);
|
Packet res = readPacket(inputStream);
|
||||||
tryThrowError(res);
|
tryThrowError(res);
|
||||||
if (res.isReplyPacket() && res.getID() == 1) {
|
if (res.isReplyPacket() && res.getID() == 1) {
|
||||||
outputStream.write(JDWP.IDSizes.encode().setPacketID(1).getBytes()); // get id sizes for decoding & encoding of jdwp
|
// get id sizes for decoding & encoding of jdwp packets.
|
||||||
// packets.
|
outputStream.write(JDWP.IDSizes.encode().setPacketID(1).getBytes());
|
||||||
res = readPacket(inputStream);
|
res = readPacket(inputStream);
|
||||||
tryThrowError(res);
|
tryThrowError(res);
|
||||||
if (res.isReplyPacket() && res.getID() == 1) {
|
if (res.isReplyPacket() && res.getID() == 1) {
|
||||||
@@ -633,15 +636,15 @@ public class SmaliDebugger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void handShake(OutputStream outputStream, InputStream inputStream) throws SmaliDebuggerException {
|
private static void handShake(OutputStream outputStream, InputStream inputStream) throws SmaliDebuggerException {
|
||||||
byte[] buf = new byte[14];
|
byte[] buf;
|
||||||
try {
|
try {
|
||||||
outputStream.write(JDWP.encodeHandShakePacket());
|
outputStream.write(JDWP.encodeHandShakePacket());
|
||||||
inputStream.read(buf, 0, 14);
|
buf = IOUtils.readNBytes(inputStream, 14);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new SmaliDebuggerException("jdwp handshake failed, " + e.getMessage());
|
throw new SmaliDebuggerException("jdwp handshake failed", e);
|
||||||
}
|
}
|
||||||
if (!JDWP.decodeHandShakePacket(buf)) {
|
if (buf == null || !JDWP.decodeHandShakePacket(buf)) {
|
||||||
throw new SmaliDebuggerException("jdwp handshake failed.");
|
throw new SmaliDebuggerException("jdwp handshake bad reply");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,23 +664,19 @@ public class SmaliDebugger {
|
|||||||
return idGenerator.getAndAdd(1);
|
return idGenerator.getAndAdd(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] appendBytes(byte[] buf1, byte[] buf2) {
|
|
||||||
byte[] tempBuf = new byte[buf1.length + buf2.length];
|
|
||||||
System.arraycopy(buf1, 0, tempBuf, 0, buf1.length);
|
|
||||||
System.arraycopy(buf2, 0, tempBuf, buf1.length, buf2.length);
|
|
||||||
return tempBuf;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read & decode packets from Socket connection
|
* Read & decode packets from Socket connection
|
||||||
*/
|
*/
|
||||||
private void decodingLoop() {
|
private void decodingLoop() {
|
||||||
Executors.newSingleThreadExecutor().execute(() -> {
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
boolean errFromCallback;
|
boolean errFromCallback;
|
||||||
for (;;) {
|
while (true) {
|
||||||
errFromCallback = false;
|
errFromCallback = false;
|
||||||
try {
|
try {
|
||||||
Packet res = readPacket(inputStream);
|
Packet res = readPacket(inputStream);
|
||||||
|
if (res == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
suspendInfo.nextRound();
|
suspendInfo.nextRound();
|
||||||
ICommandResult callback = callbackMap.remove(res.getID());
|
ICommandResult callback = callbackMap.remove(res.getID());
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
@@ -1129,31 +1128,39 @@ public class SmaliDebugger {
|
|||||||
/**
|
/**
|
||||||
* Reads a JDWP packet.
|
* Reads a JDWP packet.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
private static Packet readPacket(InputStream inputStream) throws SmaliDebuggerException {
|
private static Packet readPacket(InputStream inputStream) throws SmaliDebuggerException {
|
||||||
byte[] bytes = new byte[JDWP.PACKET_HEADER_SIZE];
|
|
||||||
try {
|
try {
|
||||||
if (inputStream.read(bytes, 0, bytes.length) == bytes.length) {
|
byte[] header = IOUtils.readNBytes(inputStream, JDWP.PACKET_HEADER_SIZE);
|
||||||
int len = JDWP.getPacketLength(bytes, 0) - JDWP.PACKET_HEADER_SIZE;
|
if (header == null) {
|
||||||
if (len > 0) {
|
// stream ended
|
||||||
byte[] payload = new byte[len];
|
return null;
|
||||||
int readSize = 0;
|
|
||||||
do {
|
|
||||||
readSize += inputStream.read(payload, readSize, len - readSize);
|
|
||||||
if (readSize == len) {
|
|
||||||
bytes = appendBytes(bytes, payload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (true);
|
|
||||||
}
|
|
||||||
return Packet.make(bytes);
|
|
||||||
}
|
}
|
||||||
|
int bodyLength = JDWP.getPacketLength(header, 0) - JDWP.PACKET_HEADER_SIZE;
|
||||||
|
if (bodyLength <= 0) {
|
||||||
|
return Packet.make(header);
|
||||||
|
}
|
||||||
|
byte[] body = IOUtils.readNBytes(inputStream, bodyLength);
|
||||||
|
if (body == null) {
|
||||||
|
throw new SmaliDebuggerException("Stream truncated");
|
||||||
|
}
|
||||||
|
return Packet.make(concatBytes(header, body));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new SmaliDebuggerException(e);
|
throw new SmaliDebuggerException("Read packer error", e);
|
||||||
}
|
}
|
||||||
throw new SmaliDebuggerException("read packet failed.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void tryThrowError(Packet res) throws SmaliDebuggerException {
|
private static byte[] concatBytes(byte[] buf1, byte[] buf2) {
|
||||||
|
byte[] tempBuf = new byte[buf1.length + buf2.length];
|
||||||
|
System.arraycopy(buf1, 0, tempBuf, 0, buf1.length);
|
||||||
|
System.arraycopy(buf2, 0, tempBuf, buf1.length, buf2.length);
|
||||||
|
return tempBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void tryThrowError(@Nullable Packet res) throws SmaliDebuggerException {
|
||||||
|
if (res == null) {
|
||||||
|
throw new SmaliDebuggerException("Stream ended");
|
||||||
|
}
|
||||||
if (res.isError()) {
|
if (res.isError()) {
|
||||||
throw new SmaliDebuggerException("(JDWP Error Code:" + res.getErrorCode() + ") "
|
throw new SmaliDebuggerException("(JDWP Error Code:" + res.getErrorCode() + ") "
|
||||||
+ res.getErrorText(), res.getErrorCode());
|
+ res.getErrorText(), res.getErrorCode());
|
||||||
@@ -1164,62 +1171,6 @@ public class SmaliDebugger {
|
|||||||
void onCommandReply(Packet res) throws SmaliDebuggerException;
|
void onCommandReply(Packet res) throws SmaliDebuggerException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class EventListenerAdapter {
|
|
||||||
void onVMStart(VMStartEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onVMDeath(VMDeathEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onSingleStep(SingleStepEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onBreakpoint(BreakpointEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onMethodEntry(MethodEntryEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onMethodExit(MethodExitEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onMethodExitWithReturnValue(MethodExitWithReturnValueEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onMonitorContendedEnter(MonitorContendedEnterEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onMonitorContendedEntered(MonitorContendedEnteredEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onMonitorWait(MonitorWaitEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onMonitorWaited(MonitorWaitedEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onException(ExceptionEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onThreadStart(ThreadStartEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onThreadDeath(ThreadDeathEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onClassPrepare(ClassPrepareEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onClassUnload(ClassUnloadEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onFieldAccess(FieldAccessEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onFieldModification(FieldModificationEvent event) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class RuntimeField extends RuntimeValue {
|
public static class RuntimeField extends RuntimeValue {
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String fldType;
|
private final String fldType;
|
||||||
@@ -1277,46 +1228,7 @@ public class SmaliDebugger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private RuntimeRegister buildRegister(int num, int tag, ByteBuffer buf) throws SmaliDebuggerException {
|
private RuntimeRegister buildRegister(int num, int tag, ByteBuffer buf) throws SmaliDebuggerException {
|
||||||
return new RuntimeRegister(num, getType(tag), buf);
|
return new RuntimeRegister(num, RuntimeType.fromJdwpTag(tag), buf);
|
||||||
}
|
|
||||||
|
|
||||||
private RuntimeType getType(int tag) throws SmaliDebuggerException {
|
|
||||||
switch (tag) {
|
|
||||||
case JDWP.Tag.ARRAY:
|
|
||||||
return RuntimeType.ARRAY;
|
|
||||||
case JDWP.Tag.BYTE:
|
|
||||||
return RuntimeType.BYTE;
|
|
||||||
case JDWP.Tag.CHAR:
|
|
||||||
return RuntimeType.CHAR;
|
|
||||||
case JDWP.Tag.OBJECT:
|
|
||||||
return RuntimeType.OBJECT;
|
|
||||||
case JDWP.Tag.FLOAT:
|
|
||||||
return RuntimeType.FLOAT;
|
|
||||||
case JDWP.Tag.DOUBLE:
|
|
||||||
return RuntimeType.DOUBLE;
|
|
||||||
case JDWP.Tag.INT:
|
|
||||||
return RuntimeType.INT;
|
|
||||||
case JDWP.Tag.LONG:
|
|
||||||
return RuntimeType.LONG;
|
|
||||||
case JDWP.Tag.SHORT:
|
|
||||||
return RuntimeType.SHORT;
|
|
||||||
case JDWP.Tag.VOID:
|
|
||||||
return RuntimeType.VOID;
|
|
||||||
case JDWP.Tag.BOOLEAN:
|
|
||||||
return RuntimeType.BOOLEAN;
|
|
||||||
case JDWP.Tag.STRING:
|
|
||||||
return RuntimeType.STRING;
|
|
||||||
case JDWP.Tag.THREAD:
|
|
||||||
return RuntimeType.THREAD;
|
|
||||||
case JDWP.Tag.THREAD_GROUP:
|
|
||||||
return RuntimeType.THREAD_GROUP;
|
|
||||||
case JDWP.Tag.CLASS_LOADER:
|
|
||||||
return RuntimeType.CLASS_LOADER;
|
|
||||||
case JDWP.Tag.CLASS_OBJECT:
|
|
||||||
return RuntimeType.CLASS_OBJECT;
|
|
||||||
default:
|
|
||||||
throw new SmaliDebuggerException("Unexpected value: " + tag);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RuntimeValue {
|
public static class RuntimeValue {
|
||||||
@@ -1409,41 +1321,6 @@ public class SmaliDebugger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum RuntimeType {
|
|
||||||
ARRAY(91, "[]"),
|
|
||||||
BYTE(66, "byte"),
|
|
||||||
CHAR(67, "char"),
|
|
||||||
OBJECT(76, "object"),
|
|
||||||
FLOAT(70, "float"),
|
|
||||||
DOUBLE(68, "double"),
|
|
||||||
INT(73, "int"),
|
|
||||||
LONG(74, "long"),
|
|
||||||
SHORT(83, "short"),
|
|
||||||
VOID(86, "void"),
|
|
||||||
BOOLEAN(90, "boolean"),
|
|
||||||
STRING(115, "string"),
|
|
||||||
THREAD(116, "thread"),
|
|
||||||
THREAD_GROUP(103, "thread_group"),
|
|
||||||
CLASS_LOADER(108, "class_loader"),
|
|
||||||
CLASS_OBJECT(99, "class_object");
|
|
||||||
|
|
||||||
private final int jdwpTag;
|
|
||||||
private final String desc;
|
|
||||||
|
|
||||||
RuntimeType(int tag, String desc) {
|
|
||||||
this.jdwpTag = tag;
|
|
||||||
this.desc = desc;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getTag() {
|
|
||||||
return jdwpTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDesc() {
|
|
||||||
return this.desc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Frame {
|
public static class Frame {
|
||||||
private final long id;
|
private final long id;
|
||||||
private final long clsID;
|
private final long clsID;
|
||||||
@@ -1484,30 +1361,6 @@ public class SmaliDebugger {
|
|||||||
void onUnloaded(String cls);
|
void onUnloaded(String cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SmaliDebuggerException extends Exception {
|
|
||||||
private final int errCode;
|
|
||||||
private static final long serialVersionUID = -1111111202102191403L;
|
|
||||||
|
|
||||||
public SmaliDebuggerException(Exception e) {
|
|
||||||
super(e);
|
|
||||||
errCode = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SmaliDebuggerException(String msg) {
|
|
||||||
super(msg);
|
|
||||||
this.errCode = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SmaliDebuggerException(String msg, int errCode) {
|
|
||||||
super(msg);
|
|
||||||
this.errCode = errCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getErrCode() {
|
|
||||||
return errCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener for breakpoint, watch, step, etc.
|
* Listener for breakpoint, watch, step, etc.
|
||||||
*/
|
*/
|
||||||
@@ -1519,99 +1372,4 @@ public class SmaliDebugger {
|
|||||||
void onSuspendEvent(SuspendInfo current);
|
void onSuspendEvent(SuspendInfo current);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SuspendInfo {
|
|
||||||
private boolean terminated;
|
|
||||||
private boolean newRound;
|
|
||||||
private final InfoSetter updater = new InfoSetter();
|
|
||||||
|
|
||||||
public long getThreadID() {
|
|
||||||
return updater.thread;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getClassID() {
|
|
||||||
return updater.clazz;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getMethodID() {
|
|
||||||
return updater.method;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getOffset() {
|
|
||||||
return updater.offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
private InfoSetter update() {
|
|
||||||
updater.changed = false;
|
|
||||||
updater.nextRound(newRound);
|
|
||||||
this.newRound = false;
|
|
||||||
return updater;
|
|
||||||
}
|
|
||||||
|
|
||||||
// called by decodingLoop, to tell the updater even though the values are the same,
|
|
||||||
// they are decoded from another packet, they should be treated as new.
|
|
||||||
private void nextRound() {
|
|
||||||
newRound = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// according to JDWP document it's legal to fire two or more events on a same location,
|
|
||||||
// e.g. one for single step and the other for breakpoint, so when this happened we only
|
|
||||||
// want one of them.
|
|
||||||
private boolean isAnythingChanged() {
|
|
||||||
return updater.changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isTerminated() {
|
|
||||||
return terminated;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setTerminated() {
|
|
||||||
terminated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class InfoSetter {
|
|
||||||
private long thread;
|
|
||||||
private long clazz;
|
|
||||||
private long method;
|
|
||||||
private long offset; // code offset;
|
|
||||||
private boolean changed;
|
|
||||||
|
|
||||||
private void nextRound(boolean newRound) {
|
|
||||||
if (!changed) {
|
|
||||||
changed = newRound;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private InfoSetter updateThread(long thread) {
|
|
||||||
if (!changed) {
|
|
||||||
changed = this.thread != thread;
|
|
||||||
}
|
|
||||||
this.thread = thread;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private InfoSetter updateClass(long clazz) {
|
|
||||||
if (!changed) {
|
|
||||||
changed = this.clazz != clazz;
|
|
||||||
}
|
|
||||||
this.clazz = clazz;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private InfoSetter updateMethod(long method) {
|
|
||||||
if (!changed) {
|
|
||||||
changed = this.method != method;
|
|
||||||
}
|
|
||||||
this.method = method;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private InfoSetter updateOffset(long offset) {
|
|
||||||
if (!changed) {
|
|
||||||
changed = this.offset != offset;
|
|
||||||
}
|
|
||||||
this.offset = offset;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package jadx.gui.device.debugger;
|
||||||
|
|
||||||
|
public class SmaliDebuggerException extends Exception {
|
||||||
|
private final int errCode;
|
||||||
|
private static final long serialVersionUID = -1111111202102191403L;
|
||||||
|
|
||||||
|
public SmaliDebuggerException(Exception e) {
|
||||||
|
super(e);
|
||||||
|
errCode = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SmaliDebuggerException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
this.errCode = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SmaliDebuggerException(String msg, Exception e) {
|
||||||
|
super(msg, e);
|
||||||
|
errCode = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SmaliDebuggerException(String msg, int errCode) {
|
||||||
|
super(msg);
|
||||||
|
this.errCode = errCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getErrCode() {
|
||||||
|
return errCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user