Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 09335395f5 | |||
| 57e3dd8f15 | |||
| a9bbadd602 | |||
| 2c570681f7 | |||
| 25166970cc | |||
| d3a0a56b8b | |||
| 3c2c198a0e | |||
| 4d4d67f0b4 | |||
| 97e8a34906 | |||
| 82f3b57e83 | |||
| af2f14f807 | |||
| fe248d7098 | |||
| 1a2e702b25 | |||
| 1da20b8e7d | |||
| 01f74ff706 | |||
| 89e95eb9ee | |||
| a61ebaaa00 | |||
| 7a5a2fcd84 | |||
| 8d5554f1b5 | |||
| 873aabb471 | |||
| 4bed9dc358 | |||
| e229874195 | |||
| 473b6e31e9 | |||
| b5ce460618 | |||
| 3c05b05196 | |||
| bdb2efdb6b | |||
| a27ba3ff4b | |||
| 4684207b54 | |||
| dd1be3039b | |||
| 8b30b770cd | |||
| 47caa91e85 | |||
| d71f3e09df | |||
| 06c7415827 | |||
| bd3e62617e | |||
| 00b48473a0 | |||
| 84facb13d0 | |||
| 96f90e18e8 | |||
| 8ff18e63ee | |||
| 381405ea99 | |||
| ae5c00397a | |||
| bd4509f1a7 | |||
| b8c84886a8 | |||
| 45021389bc | |||
| f674a29a64 | |||
| 0c9e3227d0 | |||
| be7e1479a1 | |||
| 19827fca20 | |||
| 5eb7cc40ed |
@@ -5,13 +5,14 @@
|
|||||||
[](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
|
[](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
|
||||||
[](https://lgtm.com/projects/g/skylot/jadx/alerts/)
|
[](https://lgtm.com/projects/g/skylot/jadx/alerts/)
|
||||||
[](https://github.com/semantic-release/semantic-release)
|
[](https://github.com/semantic-release/semantic-release)
|
||||||
|
[](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
|
||||||
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||||
|
|
||||||
**jadx** - Dex to Java decompiler
|
**jadx** - Dex to Java decompiler
|
||||||
|
|
||||||
Command line and GUI tools for producing Java source code from Android Dex and Apk files
|
Command line and GUI tools for producing Java source code from Android Dex and Apk files
|
||||||
|
|
||||||
:exclamation: :exclamation: :exclamation: Please note that in most cases Jadx can't decompile all 100% of the code, so errors will occur. Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds
|
:exclamation::exclamation::exclamation: Please note that in most cases **jadx** can't decompile all 100% of the code, so errors will occur. Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds
|
||||||
|
|
||||||
**Main features:**
|
**Main features:**
|
||||||
- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
|
- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
|
||||||
@@ -23,7 +24,9 @@ Command line and GUI tools for producing Java source code from Android Dex and A
|
|||||||
- jump to declaration
|
- jump to declaration
|
||||||
- find usage
|
- find usage
|
||||||
- full text search
|
- full text search
|
||||||
- smali debugger (thanks to [@LBJ-the-GOAT](https://github.com/LBJ-the-GOAT)), check [wiki page](https://github.com/skylot/jadx/wiki/Smali-debugger) for setup and usage
|
- smali debugger, check [wiki page](https://github.com/skylot/jadx/wiki/Smali-debugger) for setup and usage
|
||||||
|
|
||||||
|
Jadx-gui key bindings can be found [here](https://github.com/skylot/jadx/wiki/JADX-GUI-Key-bindings)
|
||||||
|
|
||||||
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
|
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
|
||||||
|
|
||||||
@@ -39,7 +42,7 @@ After download unpack zip file go to `bin` directory and run:
|
|||||||
|
|
||||||
On Windows run `.bat` files with double-click\
|
On Windows run `.bat` files with double-click\
|
||||||
**Note:** ensure you have installed Java 11 or later 64-bit version.
|
**Note:** ensure you have installed Java 11 or later 64-bit version.
|
||||||
For windows you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
|
For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
1. Arch linux
|
1. Arch linux
|
||||||
@@ -76,7 +79,8 @@ options:
|
|||||||
-dr, --output-dir-res - output directory for resources
|
-dr, --output-dir-res - output directory for resources
|
||||||
-r, --no-res - do not decode resources
|
-r, --no-res - do not decode resources
|
||||||
-s, --no-src - do not decompile source code
|
-s, --no-src - do not decompile source code
|
||||||
--single-class - decompile a single class
|
--single-class - decompile a single class, full name, raw or alias
|
||||||
|
--single-class-output - file or dir for write if decompile a single class
|
||||||
--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
|
||||||
@@ -93,7 +97,12 @@ options:
|
|||||||
--deobf-min - min length of name, renamed if shorter, default: 3
|
--deobf-min - min length of name, renamed if shorter, default: 3
|
||||||
--deobf-max - max length of name, renamed if longer, default: 64
|
--deobf-max - max length of name, renamed if longer, default: 64
|
||||||
--deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension
|
--deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension
|
||||||
--deobf-rewrite-cfg - force to ignore and overwrite deobfuscation map file
|
--deobf-cfg-file-mode - set mode for handle deobfuscation map file:
|
||||||
|
'read' - read if found, don't save (default)
|
||||||
|
'read-or-save' - read if found, save otherwise (don't overwrite)
|
||||||
|
'overwrite' - don't read, always save
|
||||||
|
'ignore' - don't read and don't save
|
||||||
|
--deobf-rewrite-cfg - set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)
|
||||||
--deobf-use-sourcename - use source file name as class name alias
|
--deobf-use-sourcename - use source file name as class name alias
|
||||||
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
||||||
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
||||||
@@ -114,11 +123,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
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.github.ben-manes.versions' version '0.41.0'
|
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||||
id 'com.diffplug.spotless' version '6.2.0'
|
id 'com.diffplug.spotless' version '6.3.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||||
@@ -27,18 +27,17 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.slf4j:slf4j-api:1.7.33'
|
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.2.0'
|
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'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +66,9 @@ spotless {
|
|||||||
if (JavaVersion.current() < JavaVersion.VERSION_16) {
|
if (JavaVersion.current() < JavaVersion.VERSION_16) {
|
||||||
removeUnusedImports()
|
removeUnusedImports()
|
||||||
} else {
|
} else {
|
||||||
// google-format broken on java 16 (https://github.com/diffplug/spotless/issues/834)
|
// google-format on Java 16+ issue: https://github.com/diffplug/spotless/issues/834
|
||||||
println('Warning! Unused imports remove is disabled for Java 16')
|
println('Warning! Unused imports remove is disabled for Java 16+'
|
||||||
|
+ ' (use workaround from https://github.com/diffplug/spotless/tree/main/plugin-gradle#google-java-format)')
|
||||||
}
|
}
|
||||||
|
|
||||||
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=b586e04868a22fd817c8971330fec37e298f3242eb85c374181b12d637f80302
|
distributionSha256Sum=e5444a57cda4a95f90b0c9446a9e1b47d3d7f69057765bfb54bd4f482542d548
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ 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 {
|
||||||
|
|||||||
@@ -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,40 +75,44 @@ 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 {
|
||||||
opt.append("- ").append(description);
|
opt.append("- ").append(description);
|
||||||
}
|
}
|
||||||
String defaultValue = getDefaultValue(args, f, opt);
|
String defaultValue = getDefaultValue(args, f, opt);
|
||||||
if (defaultValue != null) {
|
if (defaultValue != null && !description.contains("(default)")) {
|
||||||
opt.append(", default: ").append(defaultValue);
|
opt.append(", default: ").append(defaultValue);
|
||||||
}
|
}
|
||||||
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();
|
||||||
@@ -32,23 +33,25 @@ public class JadxCLI {
|
|||||||
public static int execute(String[] args) {
|
public static int execute(String[] args) {
|
||||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||||
if (jadxArgs.processArgs(args)) {
|
if (jadxArgs.processArgs(args)) {
|
||||||
return processAndSave(jadxArgs.toJadxArgs());
|
return processAndSave(jadxArgs);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int processAndSave(JadxArgs jadxArgs) {
|
private static int processAndSave(JadxCLIArgs cliArgs) {
|
||||||
|
LogHelper.initLogLevel(cliArgs);
|
||||||
|
LogHelper.setLogLevelsForLoadingStage();
|
||||||
|
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 (LogHelper.getLogLevel() == LogHelper.LogLevelEnum.QUIET) {
|
if (checkForErrors(jadx)) {
|
||||||
jadx.save();
|
return 1;
|
||||||
} else {
|
}
|
||||||
jadx.save(500, (done, total) -> {
|
LogHelper.setLogLevelsForDecompileStage();
|
||||||
int progress = (int) (done * 100.0 / total);
|
if (!SingleClassMode.process(jadx, cliArgs)) {
|
||||||
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
|
save(jadx);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
int errorsCount = jadx.getErrorsCount();
|
int errorsCount = jadx.getErrorsCount();
|
||||||
if (errorsCount != 0) {
|
if (errorsCount != 0) {
|
||||||
@@ -60,4 +63,30 @@ 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) {
|
||||||
|
if (LogHelper.getLogLevel() == LogLevelEnum.QUIET) {
|
||||||
|
jadx.save();
|
||||||
|
} else {
|
||||||
|
jadx.save(500, (done, total) -> {
|
||||||
|
int progress = (int) (done * 100.0 / total);
|
||||||
|
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
|
||||||
|
});
|
||||||
|
// dumb line clear :)
|
||||||
|
System.out.print(" \r");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ 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;
|
||||||
|
|
||||||
@@ -16,6 +19,7 @@ import jadx.api.JadxArgs;
|
|||||||
import jadx.api.JadxArgs.RenameEnum;
|
import jadx.api.JadxArgs.RenameEnum;
|
||||||
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
@@ -39,9 +43,12 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
|
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
|
||||||
protected boolean skipSources = false;
|
protected boolean skipSources = false;
|
||||||
|
|
||||||
@Parameter(names = { "--single-class" }, description = "decompile a single class")
|
@Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
|
||||||
protected String singleClass = null;
|
protected String singleClass = null;
|
||||||
|
|
||||||
|
@Parameter(names = { "--single-class-output" }, description = "file or dir for write if decompile a single class")
|
||||||
|
protected String singleClassOutput = null;
|
||||||
|
|
||||||
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
|
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
|
||||||
protected String outputFormat = "java";
|
protected String outputFormat = "java";
|
||||||
|
|
||||||
@@ -93,7 +100,18 @@ public class JadxCLIArgs {
|
|||||||
)
|
)
|
||||||
protected String deobfuscationMapFile;
|
protected String deobfuscationMapFile;
|
||||||
|
|
||||||
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "force to ignore and overwrite deobfuscation map file")
|
@Parameter(
|
||||||
|
names = { "--deobf-cfg-file-mode" },
|
||||||
|
description = "set mode for handle deobfuscation map file:"
|
||||||
|
+ "\n 'read' - read if found, don't save (default)"
|
||||||
|
+ "\n 'read-or-save' - read if found, save otherwise (don't overwrite)"
|
||||||
|
+ "\n 'overwrite' - don't read, always save"
|
||||||
|
+ "\n 'ignore' - don't read and don't save",
|
||||||
|
converter = DeobfuscationMapFileModeConverter.class
|
||||||
|
)
|
||||||
|
protected DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||||
|
|
||||||
|
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)")
|
||||||
protected boolean deobfuscationForceSave = false;
|
protected boolean deobfuscationForceSave = false;
|
||||||
|
|
||||||
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
||||||
@@ -162,6 +180,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);
|
||||||
@@ -197,7 +218,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();
|
||||||
@@ -215,9 +235,6 @@ public class JadxCLIArgs {
|
|||||||
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
|
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
|
||||||
args.setThreadsCount(threadsCount);
|
args.setThreadsCount(threadsCount);
|
||||||
args.setSkipSources(skipSources);
|
args.setSkipSources(skipSources);
|
||||||
if (singleClass != null) {
|
|
||||||
args.setClassFilter(className -> singleClass.equals(className));
|
|
||||||
}
|
|
||||||
args.setSkipResources(skipResources);
|
args.setSkipResources(skipResources);
|
||||||
args.setFallbackMode(fallbackMode);
|
args.setFallbackMode(fallbackMode);
|
||||||
args.setShowInconsistentCode(showInconsistentCode);
|
args.setShowInconsistentCode(showInconsistentCode);
|
||||||
@@ -226,7 +243,11 @@ public class JadxCLIArgs {
|
|||||||
args.setReplaceConsts(replaceConsts);
|
args.setReplaceConsts(replaceConsts);
|
||||||
args.setDeobfuscationOn(deobfuscationOn);
|
args.setDeobfuscationOn(deobfuscationOn);
|
||||||
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
|
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
|
||||||
args.setDeobfuscationForceSave(deobfuscationForceSave);
|
if (deobfuscationForceSave) {
|
||||||
|
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
|
||||||
|
} else {
|
||||||
|
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
|
||||||
|
}
|
||||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||||
@@ -244,6 +265,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,6 +285,14 @@ public class JadxCLIArgs {
|
|||||||
return outDirRes;
|
return outDirRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSingleClass() {
|
||||||
|
return singleClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSingleClassOutput() {
|
||||||
|
return singleClassOutput;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSkipResources() {
|
public boolean isSkipResources() {
|
||||||
return skipResources;
|
return skipResources;
|
||||||
}
|
}
|
||||||
@@ -323,6 +353,10 @@ public class JadxCLIArgs {
|
|||||||
return deobfuscationMapFile;
|
return deobfuscationMapFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
|
||||||
|
return deobfuscationMapFileMode;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDeobfuscationForceSave() {
|
public boolean isDeobfuscationForceSave() {
|
||||||
return deobfuscationForceSave;
|
return deobfuscationForceSave;
|
||||||
}
|
}
|
||||||
@@ -383,6 +417,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;
|
||||||
|
|
||||||
@@ -438,6 +480,19 @@ public class JadxCLIArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class DeobfuscationMapFileModeConverter implements IStringConverter<DeobfuscationMapFileMode> {
|
||||||
|
@Override
|
||||||
|
public DeobfuscationMapFileMode convert(String value) {
|
||||||
|
try {
|
||||||
|
return DeobfuscationMapFileMode.valueOf(value.toUpperCase());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
'\'' + value + "' is unknown, possible values are: "
|
||||||
|
+ JadxCLIArgs.enumValuesString(DeobfuscationMapFileMode.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,33 +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.QUIET) {
|
|
||||||
// show progress for all levels except quiet
|
|
||||||
setLevelForClass(JadxCLI.class, Level.INFO);
|
|
||||||
setLevelForClass(JadxDecompiler.class, Level.INFO);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package jadx.cli;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.visitors.SaveCode;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
|
public class SingleClassMode {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SingleClassMode.class);
|
||||||
|
|
||||||
|
public static boolean process(JadxDecompiler jadx, JadxCLIArgs cliArgs) {
|
||||||
|
String singleClass = cliArgs.getSingleClass();
|
||||||
|
String singleClassOutput = cliArgs.getSingleClassOutput();
|
||||||
|
if (singleClass == null && singleClassOutput == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ClassNode clsForProcess;
|
||||||
|
if (singleClass != null) {
|
||||||
|
clsForProcess = jadx.getRoot().resolveClass(singleClass);
|
||||||
|
if (clsForProcess == null) {
|
||||||
|
clsForProcess = jadx.getRoot().getClasses().stream()
|
||||||
|
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(singleClass))
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
if (clsForProcess == null) {
|
||||||
|
throw new JadxRuntimeException("Input class not found: " + singleClass);
|
||||||
|
}
|
||||||
|
if (clsForProcess.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
throw new JadxRuntimeException("Input class can't be saved by currect jadx settings (marked as DONT_GENERATE)");
|
||||||
|
}
|
||||||
|
if (clsForProcess.isInner()) {
|
||||||
|
clsForProcess = clsForProcess.getTopParentClass();
|
||||||
|
LOG.warn("Input class is inner, parent class will be saved: {}", clsForProcess.getFullName());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// singleClassOutput is set
|
||||||
|
// expect only one class to be loaded
|
||||||
|
List<ClassNode> classes = jadx.getRoot().getClasses().stream()
|
||||||
|
.filter(c -> !c.isInner() && !c.contains(AFlag.DONT_GENERATE))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
int size = classes.size();
|
||||||
|
if (size == 1) {
|
||||||
|
clsForProcess = classes.get(0);
|
||||||
|
} else {
|
||||||
|
throw new JadxRuntimeException("Found " + size + " classes, single class output can't be used");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ICodeInfo codeInfo;
|
||||||
|
try {
|
||||||
|
codeInfo = clsForProcess.decompile();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Class decompilation failed", e);
|
||||||
|
}
|
||||||
|
String fileExt = SaveCode.getFileExtension(jadx.getRoot());
|
||||||
|
File out;
|
||||||
|
if (singleClassOutput == null) {
|
||||||
|
out = new File(jadx.getArgs().getOutDirSrc(), clsForProcess.getClassInfo().getAliasFullPath() + fileExt);
|
||||||
|
} else {
|
||||||
|
if (singleClassOutput.endsWith(fileExt)) {
|
||||||
|
// treat as file name
|
||||||
|
out = new File(singleClassOutput);
|
||||||
|
} else {
|
||||||
|
// treat as directory
|
||||||
|
out = new File(singleClassOutput, clsForProcess.getShortName() + fileExt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File resultOut = FileUtils.prepareFile(out);
|
||||||
|
if (clsForProcess.getClassInfo().hasAlias()) {
|
||||||
|
LOG.info("Saving class '{}' (alias: '{}') to file '{}'",
|
||||||
|
clsForProcess.getClassInfo().getFullName(), clsForProcess.getFullName(), resultOut.getAbsolutePath());
|
||||||
|
} else {
|
||||||
|
LOG.info("Saving class '{}' to file '{}'", clsForProcess.getFullName(), resultOut.getAbsolutePath());
|
||||||
|
}
|
||||||
|
SaveCode.save(codeInfo.getCodeStr(), resultOut);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ plugins {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api(project(':jadx-plugins:jadx-plugins-api'))
|
api(project(':jadx-plugins:jadx-plugins-api'))
|
||||||
|
|
||||||
implementation 'com.google.code.gson:gson:2.8.9'
|
implementation 'com.google.code.gson:gson:2.9.0'
|
||||||
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
|
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
|
||||||
constraints {
|
constraints {
|
||||||
// Force protobuf version to prevent Java-7 issue
|
// Force protobuf version to prevent Java-7 issue
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ 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;
|
||||||
|
|
||||||
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
import jadx.api.data.ICodeData;
|
import jadx.api.data.ICodeData;
|
||||||
import jadx.api.impl.AnnotatedCodeWriter;
|
import jadx.api.impl.AnnotatedCodeWriter;
|
||||||
import jadx.api.impl.InMemoryCodeCache;
|
import jadx.api.impl.InMemoryCodeCache;
|
||||||
@@ -54,11 +57,12 @@ public class JadxArgs {
|
|||||||
private Predicate<String> classFilter = null;
|
private Predicate<String> classFilter = null;
|
||||||
|
|
||||||
private boolean deobfuscationOn = false;
|
private boolean deobfuscationOn = false;
|
||||||
private boolean deobfuscationForceSave = false;
|
|
||||||
private boolean useSourceNameAsClassAlias = false;
|
private boolean useSourceNameAsClassAlias = false;
|
||||||
private boolean parseKotlinMetadata = false;
|
private boolean parseKotlinMetadata = false;
|
||||||
private File deobfuscationMapFile = null;
|
private File deobfuscationMapFile = null;
|
||||||
|
|
||||||
|
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||||
|
|
||||||
private int deobfuscationMinLength = 0;
|
private int deobfuscationMinLength = 0;
|
||||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||||
|
|
||||||
@@ -93,6 +97,13 @@ public class JadxArgs {
|
|||||||
|
|
||||||
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
|
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't save files (can be using for performance testing)
|
||||||
|
*/
|
||||||
|
private boolean skipFilesSave = false;
|
||||||
|
|
||||||
|
private Map<String, String> pluginOptions = new HashMap<>();
|
||||||
|
|
||||||
public JadxArgs() {
|
public JadxArgs() {
|
||||||
// use default options
|
// use default options
|
||||||
}
|
}
|
||||||
@@ -259,12 +270,24 @@ public class JadxArgs {
|
|||||||
this.deobfuscationOn = deobfuscationOn;
|
this.deobfuscationOn = deobfuscationOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public boolean isDeobfuscationForceSave() {
|
public boolean isDeobfuscationForceSave() {
|
||||||
return deobfuscationForceSave;
|
return deobfuscationMapFileMode == DeobfuscationMapFileMode.OVERWRITE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
|
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
|
||||||
this.deobfuscationForceSave = deobfuscationForceSave;
|
if (deobfuscationForceSave) {
|
||||||
|
this.deobfuscationMapFileMode = DeobfuscationMapFileMode.OVERWRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
|
||||||
|
return deobfuscationMapFileMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeobfuscationMapFileMode(DeobfuscationMapFileMode deobfuscationMapFileMode) {
|
||||||
|
this.deobfuscationMapFileMode = deobfuscationMapFileMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUseSourceNameAsClassAlias() {
|
public boolean isUseSourceNameAsClassAlias() {
|
||||||
@@ -447,6 +470,22 @@ public class JadxArgs {
|
|||||||
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
|
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSkipFilesSave() {
|
||||||
|
return skipFilesSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSkipFilesSave(boolean 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
|
||||||
@@ -463,7 +502,7 @@ public class JadxArgs {
|
|||||||
+ ", skipSources=" + skipSources
|
+ ", skipSources=" + skipSources
|
||||||
+ ", deobfuscationOn=" + deobfuscationOn
|
+ ", deobfuscationOn=" + deobfuscationOn
|
||||||
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
||||||
+ ", deobfuscationForceSave=" + deobfuscationForceSave
|
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
|
||||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||||
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||||
@@ -480,6 +519,7 @@ 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
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ 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.nodes.LineAttrNode;
|
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||||
@@ -122,12 +123,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
private void reset() {
|
||||||
@@ -167,6 +172,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) {
|
||||||
@@ -282,6 +299,9 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void appendResourcesSaveTasks(List<Runnable> tasks, File outDir) {
|
private void appendResourcesSaveTasks(List<Runnable> tasks, File outDir) {
|
||||||
|
if (args.isSkipFilesSave()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
|
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
|
||||||
for (ResourceFile resourceFile : getResources()) {
|
for (ResourceFile resourceFile : getResources()) {
|
||||||
if (resourceFile.getType() != ResourceType.ARSC
|
if (resourceFile.getType() != ResourceType.ARSC
|
||||||
@@ -306,7 +326,13 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
}
|
}
|
||||||
processQueue.add(cls);
|
processQueue.add(cls);
|
||||||
}
|
}
|
||||||
for (List<JavaClass> decompileBatch : decompileScheduler.buildBatches(processQueue)) {
|
List<List<JavaClass>> batches;
|
||||||
|
try {
|
||||||
|
batches = decompileScheduler.buildBatches(processQueue);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Decompilation batches build failed", e);
|
||||||
|
}
|
||||||
|
for (List<JavaClass> decompileBatch : batches) {
|
||||||
tasks.add(() -> {
|
tasks.add(() -> {
|
||||||
for (JavaClass cls : decompileBatch) {
|
for (JavaClass cls : decompileBatch) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import org.jetbrains.annotations.ApiStatus;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
@@ -69,6 +71,10 @@ public final class JavaClass implements JavaNode {
|
|||||||
cls.unloadCode();
|
cls.unloadCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isNoCode() {
|
||||||
|
return cls.contains(AFlag.DONT_GENERATE);
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized String getSmali() {
|
public synchronized String getSmali() {
|
||||||
return cls.getDisassembledCode();
|
return cls.getDisassembledCode();
|
||||||
}
|
}
|
||||||
@@ -237,7 +243,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaClass getTopParentClass() {
|
public JavaClass getTopParentClass() {
|
||||||
if (cls.contains(AFlag.ANONYMOUS_CLASS)) {
|
if (cls.contains(AType.ANONYMOUS_CLASS)) {
|
||||||
// moved to usage class
|
// moved to usage class
|
||||||
return getParentForAnonymousClass();
|
return getParentForAnonymousClass();
|
||||||
}
|
}
|
||||||
@@ -245,15 +251,9 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private JavaClass getParentForAnonymousClass() {
|
private JavaClass getParentForAnonymousClass() {
|
||||||
List<JavaNode> useIn = getUseIn();
|
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
|
||||||
if (useIn.isEmpty()) {
|
ClassNode topParentClass = attr.getOuterCls().getTopParentClass();
|
||||||
return this;
|
return getRootDecompiler().convertClassNode(topParentClass);
|
||||||
}
|
|
||||||
JavaNode useNode = useIn.get(0);
|
|
||||||
if (useNode.equals(this)) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
return useNode.getTopParentClass();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccessInfo getAccessInfo() {
|
public AccessInfo getAccessInfo() {
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ public final class JavaField implements JavaNode {
|
|||||||
return parent.getFullName() + '.' + getName();
|
return parent.getFullName() + '.' + getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRawName() {
|
||||||
|
return field.getName();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaClass getDeclaringClass() {
|
public JavaClass getDeclaringClass() {
|
||||||
return parent;
|
return parent;
|
||||||
|
|||||||
@@ -126,8 +126,9 @@ public final class ResourcesLoader {
|
|||||||
if (name.endsWith(".9.png")) {
|
if (name.endsWith(".9.png")) {
|
||||||
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
||||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||||
decoder.decode(inputStream, os);
|
if (decoder.decode(inputStream, os)) {
|
||||||
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
|
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
|
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package jadx.api.args;
|
||||||
|
|
||||||
|
public enum DeobfuscationMapFileMode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load if found, don't save (default)
|
||||||
|
*/
|
||||||
|
READ,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load if found, save only if new (don't overwrite)
|
||||||
|
*/
|
||||||
|
READ_OR_SAVE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't load, always save
|
||||||
|
*/
|
||||||
|
OVERWRITE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't load and don't save
|
||||||
|
*/
|
||||||
|
IGNORE;
|
||||||
|
|
||||||
|
public boolean shouldRead() {
|
||||||
|
return this == READ || this == READ_OR_SAVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldWrite() {
|
||||||
|
return this == READ_OR_SAVE || this == OVERWRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -178,7 +178,14 @@ public class Jadx {
|
|||||||
return passes;
|
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 +195,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 +204,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,17 +34,17 @@ public final class ProcessClass {
|
|||||||
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
|
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
|
||||||
cls.remove(AFlag.CLASS_DEEP_RELOAD);
|
cls.remove(AFlag.CLASS_DEEP_RELOAD);
|
||||||
cls.deepUnload();
|
cls.deepUnload();
|
||||||
cls.root().runPreDecompileStageForClass(cls);
|
cls.add(AFlag.CLASS_UNLOADED);
|
||||||
}
|
}
|
||||||
if (cls.contains(AFlag.CLASS_UNLOADED)) {
|
if (cls.contains(AFlag.CLASS_UNLOADED)) {
|
||||||
cls.remove(AFlag.CLASS_UNLOADED);
|
|
||||||
cls.root().runPreDecompileStageForClass(cls);
|
cls.root().runPreDecompileStageForClass(cls);
|
||||||
|
cls.remove(AFlag.CLASS_UNLOADED);
|
||||||
|
}
|
||||||
|
if (cls.getState() == GENERATED_AND_UNLOADED) {
|
||||||
|
// force loading code again
|
||||||
|
cls.setState(NOT_LOADED);
|
||||||
}
|
}
|
||||||
if (codegen) {
|
if (codegen) {
|
||||||
if (cls.getState() == GENERATED_AND_UNLOADED) {
|
|
||||||
// allow to run code generation again
|
|
||||||
cls.setState(NOT_LOADED);
|
|
||||||
}
|
|
||||||
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
|
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
|
||||||
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
|
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
|
||||||
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
|
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ public class ClassGen {
|
|||||||
|
|
||||||
private boolean isInnerClassesPresents() {
|
private boolean isInnerClassesPresents() {
|
||||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||||
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
|
if (!innerCls.contains(AType.ANONYMOUS_CLASS)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -459,7 +459,7 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
if (f.getCls() != null) {
|
if (f.getCls() != null) {
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
new ClassGen(f.getCls(), this).addClassBody(code);
|
new ClassGen(f.getCls(), this).addClassBody(code, true);
|
||||||
}
|
}
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
code.add(',');
|
code.add(',');
|
||||||
@@ -526,12 +526,42 @@ public class ClassGen {
|
|||||||
if (outerType != null) {
|
if (outerType != null) {
|
||||||
useClass(code, outerType);
|
useClass(code, outerType);
|
||||||
code.add('.');
|
code.add('.');
|
||||||
// import not needed, force use short name
|
addInnerType(code, type);
|
||||||
useClassShortName(code, type.getObject());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
useClass(code, ClassInfo.fromType(cls.root(), type));
|
useClass(code, ClassInfo.fromType(cls.root(), type));
|
||||||
|
addGenerics(code, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addInnerType(ICodeWriter code, ArgType baseType) {
|
||||||
|
ArgType innerType = baseType.getInnerType();
|
||||||
|
ArgType outerType = innerType.getOuterType();
|
||||||
|
if (outerType != null) {
|
||||||
|
useClassWithShortName(code, baseType, outerType);
|
||||||
|
code.add('.');
|
||||||
|
addInnerType(code, innerType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
useClassWithShortName(code, baseType, innerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void useClassWithShortName(ICodeWriter code, ArgType baseType, ArgType type) {
|
||||||
|
String fullNameObj;
|
||||||
|
if (type.getObject().contains(".")) {
|
||||||
|
fullNameObj = type.getObject();
|
||||||
|
} else {
|
||||||
|
fullNameObj = baseType.getObject();
|
||||||
|
}
|
||||||
|
ClassInfo classInfo = ClassInfo.fromName(cls.root(), fullNameObj);
|
||||||
|
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||||
|
if (classNode != null) {
|
||||||
|
code.attachAnnotation(classNode);
|
||||||
|
}
|
||||||
|
code.add(classInfo.getAliasShortName());
|
||||||
|
addGenerics(code, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addGenerics(ICodeWriter code, ArgType type) {
|
||||||
List<ArgType> generics = type.getGenericTypes();
|
List<ArgType> generics = type.getGenericTypes();
|
||||||
if (generics != null) {
|
if (generics != null) {
|
||||||
code.add('<');
|
code.add('<');
|
||||||
@@ -556,15 +586,6 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void useClassShortName(ICodeWriter code, String object) {
|
|
||||||
ClassInfo classInfo = ClassInfo.fromName(cls.root(), object);
|
|
||||||
ClassNode classNode = cls.root().resolveClass(classInfo);
|
|
||||||
if (classNode != null) {
|
|
||||||
code.attachAnnotation(classNode);
|
|
||||||
}
|
|
||||||
code.add(classInfo.getAliasShortName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void useClass(ICodeWriter code, ClassInfo classInfo) {
|
public void useClass(ICodeWriter code, ClassInfo classInfo) {
|
||||||
ClassNode classNode = cls.root().resolveClass(classInfo);
|
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||||
if (classNode != null) {
|
if (classNode != null) {
|
||||||
@@ -590,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;
|
||||||
}
|
}
|
||||||
@@ -599,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;
|
||||||
}
|
}
|
||||||
@@ -606,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;
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ public class InsnGen {
|
|||||||
|
|
||||||
private void instanceField(ICodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
private void instanceField(ICodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||||
ClassNode pCls = mth.getParentClass();
|
ClassNode pCls = mth.getParentClass();
|
||||||
FieldNode fieldNode = pCls.root().deepResolveField(field);
|
FieldNode fieldNode = pCls.root().resolveField(field);
|
||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
||||||
if (replace != null) {
|
if (replace != null) {
|
||||||
@@ -210,7 +210,7 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
code.add('.');
|
code.add('.');
|
||||||
}
|
}
|
||||||
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field);
|
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
|
||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
code.attachAnnotation(fieldNode);
|
code.attachAnnotation(fieldNode);
|
||||||
}
|
}
|
||||||
@@ -719,13 +719,13 @@ public class InsnGen {
|
|||||||
|
|
||||||
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||||
if (this.mth.getParentClass() == cls) {
|
if (this.mth.getParentClass() == cls) {
|
||||||
cls.remove(AFlag.ANONYMOUS_CLASS);
|
cls.remove(AType.ANONYMOUS_CLASS);
|
||||||
cls.remove(AFlag.DONT_GENERATE);
|
cls.remove(AFlag.DONT_GENERATE);
|
||||||
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
|
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
|
||||||
throw new CodegenException("Anonymous inner class unlimited recursion detected."
|
throw new CodegenException("Anonymous inner class unlimited recursion detected."
|
||||||
+ " Convert class to inner: " + cls.getClassInfo().getFullName());
|
+ " Convert class to inner: " + cls.getClassInfo().getFullName());
|
||||||
}
|
}
|
||||||
ArgType parent = cls.get(AType.ANONYMOUS_CLASS_BASE).getBaseType();
|
ArgType parent = cls.get(AType.ANONYMOUS_CLASS).getBaseType();
|
||||||
// hide empty anonymous constructors
|
// hide empty anonymous constructors
|
||||||
for (MethodNode ctor : cls.getMethods()) {
|
for (MethodNode ctor : cls.getMethods()) {
|
||||||
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
|
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
|
||||||
@@ -764,7 +764,7 @@ public class InsnGen {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MethodInfo callMth = insn.getCallMth();
|
MethodInfo callMth = insn.getCallMth();
|
||||||
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
|
MethodNode callMthNode = mth.root().resolveMethod(callMth);
|
||||||
|
|
||||||
int k = 0;
|
int k = 0;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ 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;
|
||||||
@@ -195,7 +195,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()) {
|
||||||
@@ -238,10 +238,11 @@ public class MethodGen {
|
|||||||
classGen.useType(code, argType);
|
classGen.useType(code, argType);
|
||||||
}
|
}
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
if (code.isMetadataSupported() && ssaVar != null) {
|
String varName = nameGen.assignArg(var);
|
||||||
|
if (code.isMetadataSupported() && ssaVar != null /* for fallback mode */) {
|
||||||
code.attachDefinition(VarDeclareRef.get(mth, var));
|
code.attachDefinition(VarDeclareRef.get(mth, var));
|
||||||
}
|
}
|
||||||
code.add(nameGen.assignArg(var));
|
code.add(varName);
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package jadx.core.deobf;
|
||||||
|
|
||||||
|
public class ClsAliasPair {
|
||||||
|
private final String pkg;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public ClsAliasPair(String pkg, String name) {
|
||||||
|
this.pkg = pkg;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPkg() {
|
||||||
|
return pkg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return pkg + '.' + name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,11 +12,11 @@ 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;
|
||||||
|
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
@@ -37,28 +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();
|
||||||
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");
|
||||||
}
|
}
|
||||||
@@ -70,9 +63,9 @@ public class DeobfPresets {
|
|||||||
/**
|
/**
|
||||||
* Loads deobfuscator presets
|
* Loads deobfuscator presets
|
||||||
*/
|
*/
|
||||||
public void load() {
|
public boolean load() {
|
||||||
if (!Files.exists(deobfMapFile)) {
|
if (!Files.exists(deobfMapFile)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
|
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
|
||||||
try {
|
try {
|
||||||
@@ -106,8 +99,10 @@ public class DeobfPresets {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
|
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,9 +137,7 @@ public class DeobfPresets {
|
|||||||
}
|
}
|
||||||
Files.write(deobfMapFile, list, MAP_FILE_CHARSET,
|
Files.write(deobfMapFile, list, MAP_FILE_CHARSET,
|
||||||
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
if (LOG.isDebugEnabled()) {
|
LOG.info("Deobfuscation map file saved as: {}", deobfMapFile);
|
||||||
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getForCls(ClassInfo cls) {
|
public String getForCls(ClassInfo cls) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
|
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
@@ -76,22 +77,25 @@ public class Deobfuscator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void execute() {
|
public void execute() {
|
||||||
if (!args.isDeobfuscationForceSave()) {
|
if (args.getDeobfuscationMapFileMode().shouldRead()) {
|
||||||
deobfPresets.load();
|
if (deobfPresets.load()) {
|
||||||
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
|
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
|
||||||
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
|
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
|
||||||
|
}
|
||||||
|
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
|
||||||
|
initIndexes();
|
||||||
}
|
}
|
||||||
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
|
|
||||||
initIndexes();
|
|
||||||
}
|
}
|
||||||
process();
|
process();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void savePresets() {
|
public void savePresets() {
|
||||||
|
DeobfuscationMapFileMode mode = args.getDeobfuscationMapFileMode();
|
||||||
|
if (!mode.shouldWrite()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Path deobfMapFile = deobfPresets.getDeobfMapFile();
|
Path deobfMapFile = deobfPresets.getDeobfMapFile();
|
||||||
if (Files.exists(deobfMapFile) && !args.isDeobfuscationForceSave()) {
|
if (mode == DeobfuscationMapFileMode.READ_OR_SAVE && Files.exists(deobfMapFile)) {
|
||||||
LOG.info("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
|
|
||||||
deobfMapFile.toAbsolutePath());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -112,16 +116,25 @@ public class Deobfuscator {
|
|||||||
deobfPresets.getPkgPresetMap().put(p.getName(), p.getAlias());
|
deobfPresets.getPkgPresetMap().put(p.getName(), p.getAlias());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
|
for (ClassNode cls : root.getClasses()) {
|
||||||
if (deobfClsInfo.getAlias() != null) {
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
deobfPresets.getClsPresetMap().put(deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias());
|
if (classInfo.hasAlias()) {
|
||||||
|
deobfPresets.getClsPresetMap().put(classInfo.makeRawFullName(), classInfo.getAliasShortName());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (FieldNode fld : cls.getFields()) {
|
||||||
|
FieldInfo fieldInfo = fld.getFieldInfo();
|
||||||
|
if (fieldInfo.hasAlias()) {
|
||||||
|
deobfPresets.getFldPresetMap().put(fieldInfo.getRawFullId(), fld.getAlias());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
|
MethodInfo methodInfo = mth.getMethodInfo();
|
||||||
|
if (methodInfo.hasAlias()) {
|
||||||
|
deobfPresets.getFldPresetMap().put(methodInfo.getRawFullId(), methodInfo.getAlias());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for (FieldInfo fld : fldMap.keySet()) {
|
|
||||||
deobfPresets.getFldPresetMap().put(fld.getRawFullId(), fld.getAlias());
|
|
||||||
}
|
|
||||||
for (MethodInfo mth : mthMap.keySet()) {
|
|
||||||
deobfPresets.getMthPresetMap().put(mth.getRawFullId(), mth.getAlias());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,10 +390,10 @@ public class Deobfuscator {
|
|||||||
String alias = null;
|
String alias = null;
|
||||||
String pkgName = null;
|
String pkgName = null;
|
||||||
if (this.parseKotlinMetadata) {
|
if (this.parseKotlinMetadata) {
|
||||||
ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls);
|
ClsAliasPair kotlinCls = KotlinMetadataUtils.getClassAlias(cls);
|
||||||
if (kotlinCls != null) {
|
if (kotlinCls != null) {
|
||||||
alias = prepareNameFull(kotlinCls.getShortName(), "C");
|
alias = kotlinCls.getName();
|
||||||
pkgName = kotlinCls.getPackage();
|
pkgName = kotlinCls.getPkg();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (alias == null && this.useSourceNameAsAlias) {
|
if (alias == null && this.useSourceNameAsAlias) {
|
||||||
@@ -572,6 +585,7 @@ public class Deobfuscator {
|
|||||||
if (!pkg.hasAlias()) {
|
if (!pkg.hasAlias()) {
|
||||||
String pkgName = pkg.getName();
|
String pkgName = pkg.getName();
|
||||||
if ((args.isDeobfuscationOn() && shouldRename(pkgName))
|
if ((args.isDeobfuscationOn() && shouldRename(pkgName))
|
||||||
|
&& (pkg.getParentPackage() != rootPackage || !TldHelper.contains(pkgName)) // check if first level is a valid tld
|
||||||
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName))
|
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName))
|
||||||
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
|
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
|
||||||
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
|
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
|
||||||
@@ -592,20 +606,6 @@ public class Deobfuscator {
|
|||||||
return NameMapper.removeInvalidCharsMiddle(name);
|
return NameMapper.removeInvalidCharsMiddle(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String prepareNameFull(String name, String prefix) {
|
|
||||||
if (name.length() > maxLength) {
|
|
||||||
return makeHashName(name, prefix);
|
|
||||||
}
|
|
||||||
String result = NameMapper.removeInvalidChars(name, prefix);
|
|
||||||
if (result.isEmpty()) {
|
|
||||||
return makeHashName(name, prefix);
|
|
||||||
}
|
|
||||||
if (NameMapper.isReserved(result)) {
|
|
||||||
return prefix + result;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String makeHashName(String name, String invalidPrefix) {
|
private static String makeHashName(String name, String invalidPrefix) {
|
||||||
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
|
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package jadx.core.deobf;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a list of all top level domains with 3 characters and less,
|
||||||
|
* so we can exclude them from deobfuscation.
|
||||||
|
*/
|
||||||
|
public class TldHelper {
|
||||||
|
|
||||||
|
private static final Set<String> TLD_SET = loadTldFile();
|
||||||
|
|
||||||
|
private static Set<String> loadTldFile() {
|
||||||
|
Set<String> tldNames = new HashSet<>();
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(Deobfuscator.class.getResourceAsStream("tld_3.txt")))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
line = line.trim();
|
||||||
|
if (!line.startsWith("#") && !line.isEmpty()) {
|
||||||
|
tldNames.add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tldNames;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Failed to load top level domain list tld_3.txt", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean contains(String name) {
|
||||||
|
return TLD_SET.contains(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -35,7 +35,6 @@ public enum AFlag {
|
|||||||
SKIP_ARG, // skip argument in invoke call
|
SKIP_ARG, // skip argument in invoke call
|
||||||
NO_SKIP_ARGS,
|
NO_SKIP_ARGS,
|
||||||
ANONYMOUS_CONSTRUCTOR,
|
ANONYMOUS_CONSTRUCTOR,
|
||||||
ANONYMOUS_CLASS,
|
|
||||||
|
|
||||||
THIS,
|
THIS,
|
||||||
SUPER,
|
SUPER,
|
||||||
@@ -77,6 +76,7 @@ public enum AFlag {
|
|||||||
INCONSISTENT_CODE, // warning about incorrect decompilation
|
INCONSISTENT_CODE, // warning about incorrect decompilation
|
||||||
|
|
||||||
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
|
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
|
||||||
|
REQUEST_CODE_SHRINK,
|
||||||
RERUN_SSA_TRANSFORM,
|
RERUN_SSA_TRANSFORM,
|
||||||
|
|
||||||
METHOD_CANDIDATE_FOR_INLINE,
|
METHOD_CANDIDATE_FOR_INLINE,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package jadx.core.dex.attributes;
|
|||||||
|
|
||||||
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
|
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
|
||||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||||
import jadx.core.dex.attributes.nodes.AnonymousClassBaseAttr;
|
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||||
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
|
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
|
||||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||||
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
||||||
@@ -25,6 +25,7 @@ import jadx.core.dex.attributes.nodes.PhiListAttr;
|
|||||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.SpecialEdgeAttr;
|
||||||
import jadx.core.dex.attributes.nodes.TmpEdgeAttr;
|
import jadx.core.dex.attributes.nodes.TmpEdgeAttr;
|
||||||
import jadx.core.dex.nodes.IMethodDetails;
|
import jadx.core.dex.nodes.IMethodDetails;
|
||||||
import jadx.core.dex.trycatch.CatchAttr;
|
import jadx.core.dex.trycatch.CatchAttr;
|
||||||
@@ -53,7 +54,7 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
|
|||||||
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
|
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
|
||||||
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<AnonymousClassBaseAttr> ANONYMOUS_CLASS_BASE = new AType<>();
|
public static final AType<AnonymousClassAttr> ANONYMOUS_CLASS = new AType<>();
|
||||||
|
|
||||||
// field
|
// field
|
||||||
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
|
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
|
||||||
@@ -76,6 +77,7 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
|
|||||||
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
|
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
|
||||||
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
|
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
|
||||||
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
|
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
|
||||||
|
public static final AType<AttrList<SpecialEdgeAttr>> SPECIAL_EDGE = new AType<>();
|
||||||
public static final AType<TmpEdgeAttr> TMP_EDGE = new AType<>();
|
public static final AType<TmpEdgeAttr> TMP_EDGE = new AType<>();
|
||||||
public static final AType<TryCatchBlockAttr> TRY_BLOCK = new AType<>();
|
public static final AType<TryCatchBlockAttr> TRY_BLOCK = new AType<>();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
|
||||||
|
public class AnonymousClassAttr extends PinnedAttribute {
|
||||||
|
|
||||||
|
private final ClassNode outerCls;
|
||||||
|
private final ArgType baseType;
|
||||||
|
|
||||||
|
public AnonymousClassAttr(ClassNode outerCls, ArgType baseType) {
|
||||||
|
this.outerCls = outerCls;
|
||||||
|
this.baseType = baseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassNode getOuterCls() {
|
||||||
|
return outerCls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getBaseType() {
|
||||||
|
return baseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<AnonymousClassAttr> getAttrType() {
|
||||||
|
return AType.ANONYMOUS_CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AnonymousClass{" + outerCls + ", base: " + baseType + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package jadx.core.dex.attributes.nodes;
|
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
|
|
||||||
import jadx.core.dex.attributes.AType;
|
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
|
||||||
|
|
||||||
public class AnonymousClassBaseAttr extends PinnedAttribute {
|
|
||||||
|
|
||||||
private final ArgType baseType;
|
|
||||||
|
|
||||||
public AnonymousClassBaseAttr(ArgType baseType) {
|
|
||||||
this.baseType = baseType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArgType getBaseType() {
|
|
||||||
return baseType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AType<AnonymousClassBaseAttr> getAttrType() {
|
|
||||||
return AType.ANONYMOUS_CLASS_BASE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "AnonymousClassBaseAttr{" + baseType + '}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package jadx.core.dex.attributes.nodes;
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -20,10 +19,10 @@ public class LoopInfo {
|
|||||||
private int id;
|
private int id;
|
||||||
private LoopInfo parentLoop;
|
private LoopInfo parentLoop;
|
||||||
|
|
||||||
public LoopInfo(BlockNode start, BlockNode end) {
|
public LoopInfo(BlockNode start, BlockNode end, Set<BlockNode> loopBlocks) {
|
||||||
this.start = start;
|
this.start = start;
|
||||||
this.end = end;
|
this.end = end;
|
||||||
this.loopBlocks = Collections.unmodifiableSet(BlockUtils.getAllPathsBlocks(start, end));
|
this.loopBlocks = loopBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockNode getStart() {
|
public BlockNode getStart() {
|
||||||
|
|||||||
@@ -6,6 +6,16 @@ import jadx.core.dex.attributes.AttrNode;
|
|||||||
|
|
||||||
public class RenameReasonAttr implements IJadxAttribute {
|
public class RenameReasonAttr implements IJadxAttribute {
|
||||||
|
|
||||||
|
public static RenameReasonAttr forNode(AttrNode node) {
|
||||||
|
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
|
||||||
|
if (renameReasonAttr != null) {
|
||||||
|
return renameReasonAttr;
|
||||||
|
}
|
||||||
|
RenameReasonAttr newAttr = new RenameReasonAttr();
|
||||||
|
node.addAttr(newAttr);
|
||||||
|
return newAttr;
|
||||||
|
}
|
||||||
|
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
public RenameReasonAttr() {
|
public RenameReasonAttr() {
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.AttrList;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
|
||||||
|
public class SpecialEdgeAttr implements IJadxAttribute {
|
||||||
|
|
||||||
|
public enum SpecialEdgeType {
|
||||||
|
BACK_EDGE,
|
||||||
|
CROSS_EDGE
|
||||||
|
}
|
||||||
|
|
||||||
|
private final SpecialEdgeType type;
|
||||||
|
private final BlockNode start;
|
||||||
|
private final BlockNode end;
|
||||||
|
|
||||||
|
public SpecialEdgeAttr(SpecialEdgeType type, BlockNode start, BlockNode end) {
|
||||||
|
this.type = type;
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpecialEdgeType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockNode getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockNode getEnd() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AType<AttrList<SpecialEdgeAttr>> getAttrType() {
|
||||||
|
return AType.SPECIAL_EDGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return type + ": " + start + " -> " + end;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -246,13 +246,13 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void notInner(RootNode root) {
|
public void notInner(RootNode root) {
|
||||||
this.parentClass = null;
|
|
||||||
splitAndApplyNames(root, type, false);
|
splitAndApplyNames(root, type, false);
|
||||||
|
this.parentClass = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void convertToInner(ClassNode parent) {
|
public void convertToInner(ClassNode parent) {
|
||||||
this.parentClass = parent.getClassInfo();
|
|
||||||
splitAndApplyNames(parent.root(), type, true);
|
splitAndApplyNames(parent.root(), type, true);
|
||||||
|
this.parentClass = parent.getClassInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateNames(RootNode root) {
|
public void updateNames(RootNode root) {
|
||||||
|
|||||||
@@ -176,6 +176,9 @@ public class ConstStorage {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
|
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
|
||||||
|
if (!replaceEnabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
PrimitiveType type = arg.getType().getPrimitiveType();
|
PrimitiveType type = arg.getType().getPrimitiveType();
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -196,17 +196,6 @@ public class SSAVar {
|
|||||||
return usedInPhi != null && !usedInPhi.isEmpty();
|
return usedInPhi != null && !usedInPhi.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getVariableUseCount() {
|
|
||||||
int count = useList.size();
|
|
||||||
if (usedInPhi == null) {
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
for (PhiInsn phiInsn : usedInPhi) {
|
|
||||||
count += phiInsn.getResult().getSVar().getUseCount();
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
if (codeVar == null) {
|
if (codeVar == null) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import jadx.api.plugins.input.data.impl.ListConsumer;
|
|||||||
import jadx.core.Consts;
|
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.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;
|
||||||
@@ -42,6 +43,7 @@ import jadx.core.dex.info.MethodInfo;
|
|||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.LiteralArg;
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
import jadx.core.dex.nodes.utils.TypeUtils;
|
import jadx.core.dex.nodes.utils.TypeUtils;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
@@ -358,6 +360,17 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return codeInfo;
|
return codeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public ICodeInfo getCodeFromCache() {
|
||||||
|
ICodeCache codeCache = root().getCodeCache();
|
||||||
|
String clsRawName = getRawName();
|
||||||
|
ICodeInfo codeInfo = codeCache.get(clsRawName);
|
||||||
|
if (codeInfo == ICodeInfo.EMPTY) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return codeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void load() {
|
public void load() {
|
||||||
for (MethodNode mth : getMethods()) {
|
for (MethodNode mth : getMethods()) {
|
||||||
@@ -608,7 +621,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAnonymous() {
|
public boolean isAnonymous() {
|
||||||
return contains(AFlag.ANONYMOUS_CLASS);
|
return contains(AType.ANONYMOUS_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInner() {
|
public boolean isInner() {
|
||||||
@@ -739,6 +752,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
this.dependencies = dependencies;
|
this.dependencies = dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDependency(ClassNode dep) {
|
||||||
|
this.dependencies = ListUtils.safeRemoveAndTrim(this.dependencies, dep);
|
||||||
|
}
|
||||||
|
|
||||||
public List<ClassNode> getCodegenDeps() {
|
public List<ClassNode> getCodegenDeps() {
|
||||||
return codegenDeps;
|
return codegenDeps;
|
||||||
}
|
}
|
||||||
@@ -747,6 +764,14 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
this.codegenDeps = codegenDeps;
|
this.codegenDeps = codegenDeps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addCodegenDep(ClassNode dep) {
|
||||||
|
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalDepsCount() {
|
||||||
|
return dependencies.size() + codegenDeps.size();
|
||||||
|
}
|
||||||
|
|
||||||
public List<ClassNode> getUseIn() {
|
public List<ClassNode> getUseIn() {
|
||||||
return useIn;
|
return useIn;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import jadx.core.dex.info.AccessInfo;
|
|||||||
import jadx.core.dex.info.AccessInfo.AFType;
|
import jadx.core.dex.info.AccessInfo.AFType;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
|
|
||||||
public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
||||||
|
|
||||||
@@ -80,6 +81,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
|||||||
this.useIn = useIn;
|
this.useIn = useIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void addUseIn(MethodNode mth) {
|
||||||
|
useIn = ListUtils.safeAdd(useIn, mth);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String typeName() {
|
public String typeName() {
|
||||||
return "field";
|
return "field";
|
||||||
|
|||||||
@@ -300,20 +300,6 @@ public class InsnNode extends LineAttrNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Visit all args recursively (including inner instructions),
|
|
||||||
* but excluding wrapped args
|
|
||||||
*/
|
|
||||||
public void visitArgs(Consumer<InsnArg> visitor) {
|
|
||||||
for (InsnArg arg : getArguments()) {
|
|
||||||
if (arg.isInsnWrap()) {
|
|
||||||
((InsnWrapArg) arg).getWrapInsn().visitArgs(visitor);
|
|
||||||
} else {
|
|
||||||
visitor.accept(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Visit this instruction and all inner (wrapped) instructions
|
* Visit this instruction and all inner (wrapped) instructions
|
||||||
* To terminate visiting return non-null value
|
* To terminate visiting return non-null value
|
||||||
@@ -336,6 +322,40 @@ public class InsnNode extends LineAttrNode {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visit all args recursively (including inner instructions), but excluding wrapped args
|
||||||
|
*/
|
||||||
|
public void visitArgs(Consumer<InsnArg> visitor) {
|
||||||
|
for (InsnArg arg : getArguments()) {
|
||||||
|
if (arg.isInsnWrap()) {
|
||||||
|
((InsnWrapArg) arg).getWrapInsn().visitArgs(visitor);
|
||||||
|
} else {
|
||||||
|
visitor.accept(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visit all args recursively (including inner instructions), but excluding wrapped args.
|
||||||
|
* To terminate visiting return non-null value
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public <R> R visitArgs(Function<InsnArg, R> visitor) {
|
||||||
|
for (InsnArg arg : getArguments()) {
|
||||||
|
R result;
|
||||||
|
if (arg.isInsnWrap()) {
|
||||||
|
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||||
|
result = wrapInsn.visitArgs(visitor);
|
||||||
|
} else {
|
||||||
|
result = visitor.apply(arg);
|
||||||
|
}
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 'Soft' equals, don't compare arguments, only instruction specific parameters.
|
* 'Soft' equals, don't compare arguments, only instruction specific parameters.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -99,9 +99,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
@Override
|
@Override
|
||||||
public void unload() {
|
public void unload() {
|
||||||
loaded = false;
|
loaded = false;
|
||||||
if (noCode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// don't unload retType, argTypes, typeParameters
|
// don't unload retType, argTypes, typeParameters
|
||||||
thisArg = null;
|
thisArg = null;
|
||||||
argsList = null;
|
argsList = null;
|
||||||
|
|||||||
@@ -378,15 +378,6 @@ public class RootNode {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public MethodNode resolveMethod(@NotNull MethodInfo mth) {
|
public MethodNode resolveMethod(@NotNull MethodInfo mth) {
|
||||||
ClassNode cls = resolveClass(mth.getDeclClass());
|
|
||||||
if (cls != null) {
|
|
||||||
return cls.searchMethod(mth);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public MethodNode deepResolveMethod(@NotNull MethodInfo mth) {
|
|
||||||
ClassNode cls = resolveClass(mth.getDeclClass());
|
ClassNode cls = resolveClass(mth.getDeclClass());
|
||||||
if (cls == null) {
|
if (cls == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -430,19 +421,14 @@ public class RootNode {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public FieldNode resolveField(FieldInfo field) {
|
public FieldNode resolveField(FieldInfo field) {
|
||||||
ClassNode cls = resolveClass(field.getDeclClass());
|
|
||||||
if (cls != null) {
|
|
||||||
return cls.searchField(field);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public FieldNode deepResolveField(@NotNull FieldInfo field) {
|
|
||||||
ClassNode cls = resolveClass(field.getDeclClass());
|
ClassNode cls = resolveClass(field.getDeclClass());
|
||||||
if (cls == null) {
|
if (cls == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
FieldNode fieldNode = cls.searchField(field);
|
||||||
|
if (fieldNode != null) {
|
||||||
|
return fieldNode;
|
||||||
|
}
|
||||||
return deepResolveField(cls, field);
|
return deepResolveField(cls, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package jadx.core.dex.nodes.parser;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -197,6 +196,8 @@ public class SignatureParser {
|
|||||||
String obj = slice();
|
String obj = slice();
|
||||||
if (!innerType) {
|
if (!innerType) {
|
||||||
obj += ';';
|
obj += ';';
|
||||||
|
} else {
|
||||||
|
obj = obj.replace('/', '.');
|
||||||
}
|
}
|
||||||
List<ArgType> typeVars = consumeGenericArgs();
|
List<ArgType> typeVars = consumeGenericArgs();
|
||||||
consume('>');
|
consume('>');
|
||||||
@@ -227,7 +228,7 @@ public class SignatureParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<ArgType> consumeGenericArgs() {
|
private List<ArgType> consumeGenericArgs() {
|
||||||
List<ArgType> list = new LinkedList<>();
|
List<ArgType> list = new ArrayList<>();
|
||||||
ArgType type;
|
ArgType type;
|
||||||
do {
|
do {
|
||||||
if (lookAhead('*')) {
|
if (lookAhead('*')) {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public class MethodUtils {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public IMethodDetails getMethodDetails(MethodInfo callMth) {
|
public IMethodDetails getMethodDetails(MethodInfo callMth) {
|
||||||
MethodNode mthNode = root.deepResolveMethod(callMth);
|
MethodNode mthNode = root.resolveMethod(callMth);
|
||||||
if (mthNode != null) {
|
if (mthNode != null) {
|
||||||
return mthNode;
|
return mthNode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public class TypeUtils {
|
|||||||
|
|
||||||
public ArgType expandTypeVariables(ClassNode cls, ArgType type) {
|
public ArgType expandTypeVariables(ClassNode cls, ArgType type) {
|
||||||
if (type.containsTypeVariable()) {
|
if (type.containsTypeVariable()) {
|
||||||
expandTypeVar(cls, type, cls.getGenericTypeParameters());
|
expandTypeVar(cls, type, getKnownTypeVarsAtClass(cls));
|
||||||
}
|
}
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
@@ -115,11 +115,18 @@ public class TypeUtils {
|
|||||||
return varsAttr.getTypeVars();
|
return varsAttr.getTypeVars();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<ArgType> collectKnownTypeVarsAtMethod(MethodNode mth) {
|
private static Collection<ArgType> getKnownTypeVarsAtClass(ClassNode cls) {
|
||||||
ClassNode declCls = mth.getParentClass();
|
if (cls.isInner()) {
|
||||||
Set<ArgType> typeVars = new HashSet<>(declCls.getGenericTypeParameters());
|
Set<ArgType> typeVars = new HashSet<>(cls.getGenericTypeParameters());
|
||||||
declCls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters()));
|
cls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters()));
|
||||||
|
return typeVars;
|
||||||
|
}
|
||||||
|
return cls.getGenericTypeParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<ArgType> collectKnownTypeVarsAtMethod(MethodNode mth) {
|
||||||
|
Set<ArgType> typeVars = new HashSet<>();
|
||||||
|
typeVars.addAll(getKnownTypeVarsAtClass(mth.getParentClass()));
|
||||||
typeVars.addAll(mth.getTypeParameters());
|
typeVars.addAll(mth.getTypeParameters());
|
||||||
return typeVars.isEmpty() ? Collections.emptySet() : typeVars;
|
return typeVars.isEmpty() ? Collections.emptySet() : typeVars;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jadx.core.dex.trycatch;
|
package jadx.core.dex.trycatch;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||||
@@ -8,9 +9,14 @@ import jadx.core.utils.Utils;
|
|||||||
|
|
||||||
public class CatchAttr implements IJadxAttribute {
|
public class CatchAttr implements IJadxAttribute {
|
||||||
|
|
||||||
|
public static CatchAttr build(List<ExceptionHandler> handlers) {
|
||||||
|
handlers.sort(Comparator.comparingInt(ExceptionHandler::getHandlerOffset));
|
||||||
|
return new CatchAttr(handlers);
|
||||||
|
}
|
||||||
|
|
||||||
private final List<ExceptionHandler> handlers;
|
private final List<ExceptionHandler> handlers;
|
||||||
|
|
||||||
public CatchAttr(List<ExceptionHandler> handlers) {
|
private CatchAttr(List<ExceptionHandler> handlers) {
|
||||||
this.handlers = handlers;
|
this.handlers = handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
@@ -36,7 +37,7 @@ public class AnonymousClassVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(ClassNode cls) throws JadxException {
|
public boolean visit(ClassNode cls) throws JadxException {
|
||||||
if (cls.contains(AFlag.ANONYMOUS_CLASS)) {
|
if (cls.contains(AType.ANONYMOUS_CLASS)) {
|
||||||
for (MethodNode mth : cls.getMethods()) {
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||||
processAnonymousConstructor(mth);
|
processAnonymousConstructor(mth);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package jadx.core.dex.visitors;
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -15,12 +16,16 @@ import jadx.core.dex.attributes.AFlag;
|
|||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
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.ExcHandlerAttr;
|
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
|
import jadx.core.dex.visitors.typeinference.TypeCompare;
|
||||||
|
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset;
|
import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset;
|
||||||
|
|
||||||
@@ -51,11 +56,11 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
|
|||||||
tries.forEach(tryData -> LOG.debug(" - {}", tryData));
|
tries.forEach(tryData -> LOG.debug(" - {}", tryData));
|
||||||
}
|
}
|
||||||
for (ITry tryData : tries) {
|
for (ITry tryData : tries) {
|
||||||
List<ExceptionHandler> handlers = attachHandlers(mth, tryData.getCatch(), insnByOffset);
|
List<ExceptionHandler> handlers = convertToHandlers(mth, tryData.getCatch(), insnByOffset);
|
||||||
if (handlers.isEmpty()) {
|
if (handlers.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
markTryBounds(insnByOffset, tryData, new CatchAttr(handlers));
|
markTryBounds(insnByOffset, tryData, CatchAttr.build(handlers));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,13 +101,13 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
|
|||||||
if (existAttr != null) {
|
if (existAttr != null) {
|
||||||
// merge handlers
|
// merge handlers
|
||||||
List<ExceptionHandler> handlers = Utils.concat(existAttr.getHandlers(), catchAttr.getHandlers());
|
List<ExceptionHandler> handlers = Utils.concat(existAttr.getHandlers(), catchAttr.getHandlers());
|
||||||
insn.addAttr(new CatchAttr(handlers));
|
insn.addAttr(CatchAttr.build(handlers));
|
||||||
} else {
|
} else {
|
||||||
insn.addAttr(catchAttr);
|
insn.addAttr(catchAttr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<ExceptionHandler> attachHandlers(MethodNode mth, ICatch catchBlock, InsnNode[] insnByOffset) {
|
private static List<ExceptionHandler> convertToHandlers(MethodNode mth, ICatch catchBlock, InsnNode[] insnByOffset) {
|
||||||
int[] handlerOffsetArr = catchBlock.getHandlers();
|
int[] handlerOffsetArr = catchBlock.getHandlers();
|
||||||
String[] handlerTypes = catchBlock.getTypes();
|
String[] handlerTypes = catchBlock.getTypes();
|
||||||
|
|
||||||
@@ -117,6 +122,7 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
|
|||||||
if (allHandlerOffset >= 0) {
|
if (allHandlerOffset >= 0) {
|
||||||
Utils.addToList(list, createHandler(mth, insnByOffset, allHandlerOffset, null));
|
Utils.addToList(list, createHandler(mth, insnByOffset, allHandlerOffset, null));
|
||||||
}
|
}
|
||||||
|
checkAndFilterHandlers(mth, list);
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,6 +149,45 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
|
|||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void checkAndFilterHandlers(MethodNode mth, List<ExceptionHandler> list) {
|
||||||
|
if (list.size() <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Remove shadowed handlers (with same or narrow type compared to previous)
|
||||||
|
TypeCompare typeCompare = mth.root().getTypeCompare();
|
||||||
|
Iterator<ExceptionHandler> it = list.iterator();
|
||||||
|
ArgType maxType = null;
|
||||||
|
while (it.hasNext()) {
|
||||||
|
ExceptionHandler handler = it.next();
|
||||||
|
ArgType maxCatch = maxCatchFromHandler(handler, typeCompare);
|
||||||
|
if (maxType == null) {
|
||||||
|
maxType = maxCatch;
|
||||||
|
} else {
|
||||||
|
TypeCompareEnum result = typeCompare.compareObjects(maxType, maxCatch);
|
||||||
|
if (result.isWiderOrEqual()) {
|
||||||
|
if (Consts.DEBUG_EXC_HANDLERS) {
|
||||||
|
LOG.debug("Removed shadowed catch handler: {}, from list: {}", handler, list);
|
||||||
|
}
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ArgType maxCatchFromHandler(ExceptionHandler handler, TypeCompare typeCompare) {
|
||||||
|
List<ClassInfo> catchTypes = handler.getCatchTypes();
|
||||||
|
if (catchTypes.isEmpty()) {
|
||||||
|
return ArgType.THROWABLE;
|
||||||
|
}
|
||||||
|
if (catchTypes.size() == 1) {
|
||||||
|
return catchTypes.get(0).getType();
|
||||||
|
}
|
||||||
|
return catchTypes.stream()
|
||||||
|
.map(ClassInfo::getType)
|
||||||
|
.max(typeCompare.getComparator())
|
||||||
|
.orElseThrow(() -> new JadxRuntimeException("Failed to get max type from catch list: " + catchTypes));
|
||||||
|
}
|
||||||
|
|
||||||
private static InsnNode insertNOP(InsnNode[] insnByOffset, int offset) {
|
private static InsnNode insertNOP(InsnNode[] insnByOffset, int offset) {
|
||||||
InsnNode nop = new InsnNode(InsnType.NOP, 0);
|
InsnNode nop = new InsnNode(InsnType.NOP, 0);
|
||||||
nop.setOffset(offset);
|
nop.setOffset(offset);
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ public class ClassModifier extends AbstractVisitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
MethodInfo callMth = invokeInsn.getCallMth();
|
MethodInfo callMth = invokeInsn.getCallMth();
|
||||||
MethodNode wrappedMth = mth.root().deepResolveMethod(callMth);
|
MethodNode wrappedMth = mth.root().resolveMethod(callMth);
|
||||||
if (wrappedMth == null) {
|
if (wrappedMth == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
SSAVar sVar = insn.getResult().getSVar();
|
SSAVar sVar = insn.getResult().getSVar();
|
||||||
InsnArg constArg;
|
InsnArg constArg;
|
||||||
|
Runnable onSuccess = null;
|
||||||
|
|
||||||
InsnType insnType = insn.getType();
|
InsnType insnType = insn.getType();
|
||||||
if (insnType == InsnType.CONST || insnType == InsnType.MOVE) {
|
if (insnType == InsnType.CONST || insnType == InsnType.MOVE) {
|
||||||
@@ -90,6 +91,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||||
constArg = InsnArg.wrapArg(constGet);
|
constArg = InsnArg.wrapArg(constGet);
|
||||||
constArg.setType(ArgType.STRING);
|
constArg.setType(ArgType.STRING);
|
||||||
|
onSuccess = () -> f.addUseIn(mth);
|
||||||
}
|
}
|
||||||
} else if (insnType == InsnType.CONST_CLASS) {
|
} else if (insnType == InsnType.CONST_CLASS) {
|
||||||
if (sVar.isUsedInPhi()) {
|
if (sVar.isUsedInPhi()) {
|
||||||
@@ -104,6 +106,9 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
// all check passed, run replace
|
// all check passed, run replace
|
||||||
if (replaceConst(mth, insn, constArg)) {
|
if (replaceConst(mth, insn, constArg)) {
|
||||||
toRemove.add(insn);
|
toRemove.add(insn);
|
||||||
|
if (onSuccess != null) {
|
||||||
|
onSuccess.run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +240,10 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
fieldNode = mth.getParentClass().getConstField((int) literal, false);
|
fieldNode = mth.getParentClass().getConstField((int) literal, false);
|
||||||
}
|
}
|
||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
litArg.wrapInstruction(mth, new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0));
|
IndexInsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0);
|
||||||
|
if (litArg.wrapInstruction(mth, sgetInsn) != null) {
|
||||||
|
fieldNode.addUseIn(mth);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (needExplicitCast(useInsn, litArg)) {
|
if (needExplicitCast(useInsn, litArg)) {
|
||||||
litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
|
litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import jadx.api.plugins.input.data.AccessFlags;
|
|||||||
import jadx.core.codegen.TypeGen;
|
import jadx.core.codegen.TypeGen;
|
||||||
import jadx.core.deobf.NameMapper;
|
import jadx.core.deobf.NameMapper;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||||
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
||||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||||
@@ -71,7 +72,14 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(ClassNode cls) throws JadxException {
|
public boolean visit(ClassNode cls) throws JadxException {
|
||||||
if (!convertToEnum(cls)) {
|
boolean converted;
|
||||||
|
try {
|
||||||
|
converted = convertToEnum(cls);
|
||||||
|
} catch (Exception e) {
|
||||||
|
cls.addWarnComment("Enum visitor error", e);
|
||||||
|
converted = false;
|
||||||
|
}
|
||||||
|
if (!converted) {
|
||||||
AccessInfo accessFlags = cls.getAccessFlags();
|
AccessInfo accessFlags = cls.getAccessFlags();
|
||||||
if (accessFlags.isEnum()) {
|
if (accessFlags.isEnum()) {
|
||||||
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
|
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
|
||||||
@@ -179,8 +187,7 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
if (!enumClsInfo.equals(cls.getClassInfo())) {
|
if (!enumClsInfo.equals(cls.getClassInfo())) {
|
||||||
ClassNode enumCls = cls.root().resolveClass(enumClsInfo);
|
ClassNode enumCls = cls.root().resolveClass(enumClsInfo);
|
||||||
if (enumCls != null) {
|
if (enumCls != null) {
|
||||||
processEnumCls(enumField, enumCls);
|
processEnumCls(cls, enumField, enumCls);
|
||||||
cls.addInlinedClass(enumCls);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<RegisterArg> regs = new ArrayList<>();
|
List<RegisterArg> regs = new ArrayList<>();
|
||||||
@@ -381,7 +388,11 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
if (constrCls == null) {
|
if (constrCls == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum()) {
|
if (constrCls.equals(cls)) {
|
||||||
|
// allow same class
|
||||||
|
} else if (constrCls.contains(AType.ANONYMOUS_CLASS)) {
|
||||||
|
// allow external class already marked as anonymous
|
||||||
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
|
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
|
||||||
@@ -466,7 +477,7 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null;
|
return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void processEnumCls(EnumField field, ClassNode innerCls) {
|
private static void processEnumCls(ClassNode cls, EnumField field, ClassNode innerCls) {
|
||||||
// remove constructor, because it is anonymous class
|
// remove constructor, because it is anonymous class
|
||||||
for (MethodNode innerMth : innerCls.getMethods()) {
|
for (MethodNode innerMth : innerCls.getMethods()) {
|
||||||
if (innerMth.getAccessFlags().isConstructor()) {
|
if (innerMth.getAccessFlags().isConstructor()) {
|
||||||
@@ -474,7 +485,11 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
field.setCls(innerCls);
|
field.setCls(innerCls);
|
||||||
innerCls.add(AFlag.DONT_GENERATE);
|
if (!innerCls.getParentClass().equals(cls)) {
|
||||||
|
// not inner
|
||||||
|
cls.addInlinedClass(innerCls);
|
||||||
|
innerCls.add(AFlag.DONT_GENERATE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConstructorInsn getConstructorInsn(InsnNode insn) {
|
private ConstructorInsn getConstructorInsn(InsnNode insn) {
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ public class InitCodeVariables extends AbstractVisitor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(MethodNode mth) throws JadxException {
|
public void visit(MethodNode mth) throws JadxException {
|
||||||
if (mth.isNoCode()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
initCodeVars(mth);
|
initCodeVars(mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,16 +39,24 @@ public class InitCodeVariables extends AbstractVisitor {
|
|||||||
private static void initCodeVars(MethodNode mth) {
|
private static void initCodeVars(MethodNode mth) {
|
||||||
RegisterArg thisArg = mth.getThisArg();
|
RegisterArg thisArg = mth.getThisArg();
|
||||||
if (thisArg != null) {
|
if (thisArg != null) {
|
||||||
initCodeVar(thisArg.getSVar());
|
initCodeVar(mth, thisArg);
|
||||||
}
|
}
|
||||||
for (RegisterArg mthArg : mth.getArgRegs()) {
|
for (RegisterArg mthArg : mth.getArgRegs()) {
|
||||||
initCodeVar(mthArg.getSVar());
|
initCodeVar(mth, mthArg);
|
||||||
}
|
}
|
||||||
for (SSAVar ssaVar : mth.getSVars()) {
|
for (SSAVar ssaVar : mth.getSVars()) {
|
||||||
initCodeVar(ssaVar);
|
initCodeVar(ssaVar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void initCodeVar(MethodNode mth, RegisterArg regArg) {
|
||||||
|
SSAVar ssaVar = regArg.getSVar();
|
||||||
|
if (ssaVar == null) {
|
||||||
|
ssaVar = mth.makeNewSVar(regArg);
|
||||||
|
}
|
||||||
|
initCodeVar(ssaVar);
|
||||||
|
}
|
||||||
|
|
||||||
public static void initCodeVar(SSAVar ssaVar) {
|
public static void initCodeVar(SSAVar ssaVar) {
|
||||||
if (ssaVar.isCodeVarSet()) {
|
if (ssaVar.isCodeVarSet()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
|
|||||||
InsnType insnType = insn.getType();
|
InsnType insnType = insn.getType();
|
||||||
if (insnType == InsnType.INVOKE) {
|
if (insnType == InsnType.INVOKE) {
|
||||||
InvokeNode invoke = (InvokeNode) insn;
|
InvokeNode invoke = (InvokeNode) insn;
|
||||||
MethodNode callMthNode = mth.root().deepResolveMethod(invoke.getCallMth());
|
MethodNode callMthNode = mth.root().resolveMethod(invoke.getCallMth());
|
||||||
if (callMthNode != null) {
|
if (callMthNode != null) {
|
||||||
FixAccessModifiers.changeVisibility(callMthNode, newVisFlag);
|
FixAccessModifiers.changeVisibility(callMthNode, newVisFlag);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SWITCH:
|
case SWITCH:
|
||||||
replaceConstKeys(parentClass, (SwitchInsn) insn);
|
replaceConstKeys(mth, parentClass, (SwitchInsn) insn);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NEW_ARRAY:
|
case NEW_ARRAY:
|
||||||
@@ -228,13 +228,14 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
return result == TypeCompareEnum.NARROW; // true if use class is subclass of field class
|
return result == TypeCompareEnum.NARROW; // true if use class is subclass of field class
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void replaceConstKeys(ClassNode parentClass, SwitchInsn insn) {
|
private static void replaceConstKeys(MethodNode mth, ClassNode parentClass, SwitchInsn insn) {
|
||||||
int[] keys = insn.getKeys();
|
int[] keys = insn.getKeys();
|
||||||
int len = keys.length;
|
int len = keys.length;
|
||||||
for (int k = 0; k < len; k++) {
|
for (int k = 0; k < len; k++) {
|
||||||
FieldNode f = parentClass.getConstField(keys[k]);
|
FieldNode f = parentClass.getConstField(keys[k]);
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
insn.modifyKey(k, f);
|
insn.modifyKey(k, f);
|
||||||
|
f.addUseIn(mth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,6 +292,13 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private EncodedValue replaceConstValue(ClassNode parentCls, EncodedValue encodedValue) {
|
private EncodedValue replaceConstValue(ClassNode parentCls, EncodedValue encodedValue) {
|
||||||
|
if (encodedValue.getType() == EncodedType.ENCODED_ANNOTATION) {
|
||||||
|
IAnnotation annotation = (IAnnotation) encodedValue.getValue();
|
||||||
|
for (Map.Entry<String, EncodedValue> entry : annotation.getValues().entrySet()) {
|
||||||
|
entry.setValue(replaceConstValue(parentCls, entry.getValue()));
|
||||||
|
}
|
||||||
|
return encodedValue;
|
||||||
|
}
|
||||||
if (encodedValue.getType() == EncodedType.ENCODED_ARRAY) {
|
if (encodedValue.getType() == EncodedType.ENCODED_ARRAY) {
|
||||||
List<EncodedValue> listVal = (List<EncodedValue>) encodedValue.getValue();
|
List<EncodedValue> listVal = (List<EncodedValue>) encodedValue.getValue();
|
||||||
if (!listVal.isEmpty()) {
|
if (!listVal.isEmpty()) {
|
||||||
@@ -320,6 +328,7 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
InsnNode inode = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
InsnNode inode = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||||
inode.setResult(insn.getResult());
|
inode.setResult(insn.getResult());
|
||||||
replaceInsn(mth, block, i, inode);
|
replaceInsn(mth, block, i, inode);
|
||||||
|
f.addUseIn(mth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,7 +341,9 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg);
|
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg);
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||||
arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet));
|
if (arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet))) {
|
||||||
|
f.addUseIn(mth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -446,7 +457,7 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
SkipMethodArgsAttr attr = callMth.get(AType.SKIP_MTH_ARGS);
|
SkipMethodArgsAttr attr = callMth.get(AType.SKIP_MTH_ARGS);
|
||||||
if (attr != null) {
|
if (attr != null) {
|
||||||
int argsCount = Math.min(callMth.getArgRegs().size(), co.getArgsCount());
|
int argsCount = Math.min(callMth.getMethodInfo().getArgsCount(), co.getArgsCount());
|
||||||
for (int i = 0; i < argsCount; i++) {
|
for (int i = 0; i < argsCount; i++) {
|
||||||
if (attr.isSkip(i)) {
|
if (attr.isSkip(i)) {
|
||||||
anonymousCallArgMod(co.getArg(i));
|
anonymousCallArgMod(co.getArg(i));
|
||||||
@@ -516,6 +527,7 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
if (f != null) {
|
if (f != null) {
|
||||||
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||||
filledArr.addArg(InsnArg.wrapArg(fGet));
|
filledArr.addArg(InsnArg.wrapArg(fGet));
|
||||||
|
f.addUseIn(mth);
|
||||||
} else {
|
} else {
|
||||||
filledArr.addArg(arg);
|
filledArr.addArg(arg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,24 @@ import java.util.Collections;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.IFieldRef;
|
||||||
|
import jadx.api.plugins.input.data.annotations.AnnotationVisibility;
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
|
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||||
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
|
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
|
||||||
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.AttrNode;
|
||||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||||
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.instructions.ArithNode;
|
import jadx.core.dex.instructions.ArithNode;
|
||||||
import jadx.core.dex.instructions.ArithOp;
|
import jadx.core.dex.instructions.ArithOp;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
@@ -25,6 +34,7 @@ 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.BlockNode;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.InsnContainer;
|
import jadx.core.dex.nodes.InsnContainer;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
@@ -54,6 +64,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
|||||||
if (cls.root().getArgs().isDebugInfo()) {
|
if (cls.root().getArgs().isDebugInfo()) {
|
||||||
setClassSourceLine(cls);
|
setClassSourceLine(cls);
|
||||||
}
|
}
|
||||||
|
collectFieldsUsageInAnnotations(cls);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +84,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
|||||||
checkConstUsage(block);
|
checkConstUsage(block);
|
||||||
}
|
}
|
||||||
moveConstructorInConstructor(mth);
|
moveConstructorInConstructor(mth);
|
||||||
|
collectFieldsUsageInAnnotations(mth, mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void removeInstructions(BlockNode block) {
|
private static void removeInstructions(BlockNode block) {
|
||||||
@@ -310,4 +322,61 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
|||||||
cls.setSourceLine(minLine - 1);
|
cls.setSourceLine(minLine - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void collectFieldsUsageInAnnotations(ClassNode cls) {
|
||||||
|
MethodNode useMth = cls.getDefaultConstructor();
|
||||||
|
if (useMth == null && !cls.getMethods().isEmpty()) {
|
||||||
|
useMth = cls.getMethods().get(0);
|
||||||
|
}
|
||||||
|
if (useMth == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
collectFieldsUsageInAnnotations(useMth, cls);
|
||||||
|
MethodNode finalUseMth = useMth;
|
||||||
|
cls.getFields().forEach(f -> collectFieldsUsageInAnnotations(finalUseMth, f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectFieldsUsageInAnnotations(MethodNode mth, AttrNode attrNode) {
|
||||||
|
AnnotationsAttr annotationsList = attrNode.get(JadxAttrType.ANNOTATION_LIST);
|
||||||
|
if (annotationsList == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (IAnnotation annotation : annotationsList.getAll()) {
|
||||||
|
if (annotation.getVisibility() == AnnotationVisibility.SYSTEM) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, EncodedValue> entry : annotation.getValues().entrySet()) {
|
||||||
|
checkEncodedValue(mth, entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void checkEncodedValue(MethodNode mth, EncodedValue encodedValue) {
|
||||||
|
switch (encodedValue.getType()) {
|
||||||
|
case ENCODED_FIELD:
|
||||||
|
Object fieldData = encodedValue.getValue();
|
||||||
|
FieldInfo fieldInfo;
|
||||||
|
if (fieldData instanceof IFieldRef) {
|
||||||
|
fieldInfo = FieldInfo.fromRef(mth.root(), (IFieldRef) fieldData);
|
||||||
|
} else {
|
||||||
|
fieldInfo = (FieldInfo) fieldData;
|
||||||
|
}
|
||||||
|
FieldNode fieldNode = mth.root().resolveField(fieldInfo);
|
||||||
|
if (fieldNode != null) {
|
||||||
|
fieldNode.addUseIn(mth);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ENCODED_ANNOTATION:
|
||||||
|
IAnnotation annotation = (IAnnotation) encodedValue.getValue();
|
||||||
|
annotation.getValues().forEach((k, v) -> checkEncodedValue(mth, v));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ENCODED_ARRAY:
|
||||||
|
List<EncodedValue> valueList = (List<EncodedValue>) encodedValue.getValue();
|
||||||
|
valueList.forEach(v -> checkEncodedValue(mth, v));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
package jadx.core.dex.visitors;
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.nodes.AnonymousClassBaseAttr;
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
@@ -25,27 +32,36 @@ import jadx.core.utils.exceptions.JadxException;
|
|||||||
)
|
)
|
||||||
public class ProcessAnonymous extends AbstractVisitor {
|
public class ProcessAnonymous extends AbstractVisitor {
|
||||||
|
|
||||||
private boolean inlineAnonymous;
|
private boolean inlineAnonymousClasses;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(RootNode root) {
|
public void init(RootNode root) {
|
||||||
inlineAnonymous = root.getArgs().isInlineAnonymousClasses();
|
inlineAnonymousClasses = root.getArgs().isInlineAnonymousClasses();
|
||||||
|
if (!inlineAnonymousClasses) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (ClassNode cls : root.getClasses()) {
|
||||||
|
markAnonymousClass(cls);
|
||||||
|
}
|
||||||
|
mergeAnonymousDeps(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(ClassNode cls) throws JadxException {
|
public boolean visit(ClassNode cls) throws JadxException {
|
||||||
if (!inlineAnonymous) {
|
if (inlineAnonymousClasses && cls.contains(AFlag.CLASS_UNLOADED)) {
|
||||||
return false;
|
// enter only on class reload
|
||||||
|
visitClassAndInners(cls);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void visitClassAndInners(ClassNode cls) {
|
||||||
markAnonymousClass(cls);
|
markAnonymousClass(cls);
|
||||||
return true;
|
cls.getInnerClasses().forEach(this::visitClassAndInners);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void markAnonymousClass(ClassNode cls) {
|
private static void markAnonymousClass(ClassNode cls) {
|
||||||
boolean synthetic = cls.getAccessFlags().isSynthetic()
|
if (!canBeAnonymous(cls)) {
|
||||||
|| cls.getClassInfo().getShortName().contains("$")
|
|
||||||
|| Character.isDigit(cls.getClassInfo().getShortName().charAt(0));
|
|
||||||
if (!synthetic) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MethodNode anonymousConstructor = checkUsage(cls);
|
MethodNode anonymousConstructor = checkUsage(cls);
|
||||||
@@ -56,27 +72,131 @@ public class ProcessAnonymous extends AbstractVisitor {
|
|||||||
if (baseType == null) {
|
if (baseType == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
|
||||||
cls.add(AFlag.ANONYMOUS_CLASS);
|
cls.addAttr(new AnonymousClassAttr(outerCls, baseType));
|
||||||
cls.addAttr(new AnonymousClassBaseAttr(baseType));
|
|
||||||
cls.add(AFlag.DONT_GENERATE);
|
cls.add(AFlag.DONT_GENERATE);
|
||||||
|
|
||||||
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
||||||
|
|
||||||
// force anonymous class to be processed before outer class,
|
// force anonymous class to be processed before outer class,
|
||||||
// actual usage of outer class will be removed at anonymous class process,
|
// actual usage of outer class will be removed at anonymous class process,
|
||||||
// see ModVisitor.processAnonymousConstructor method
|
// see ModVisitor.processAnonymousConstructor method
|
||||||
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
|
|
||||||
ClassNode topOuterCls = outerCls.getTopParentClass();
|
ClassNode topOuterCls = outerCls.getTopParentClass();
|
||||||
ListUtils.safeRemove(cls.getDependencies(), topOuterCls);
|
cls.removeDependency(topOuterCls);
|
||||||
ListUtils.safeRemove(outerCls.getUseIn(), cls);
|
ListUtils.safeRemove(outerCls.getUseIn(), cls);
|
||||||
|
|
||||||
// move dependency to codegen stage
|
// move dependency to codegen stage
|
||||||
if (cls.isTopClass()) {
|
if (cls.isTopClass()) {
|
||||||
topOuterCls.setDependencies(ListUtils.safeRemoveAndTrim(topOuterCls.getDependencies(), cls));
|
topOuterCls.removeDependency(cls);
|
||||||
topOuterCls.setCodegenDeps(ListUtils.safeAdd(topOuterCls.getCodegenDeps(), cls));
|
topOuterCls.addCodegenDep(cls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void undoAnonymousMark(ClassNode cls) {
|
||||||
|
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
|
||||||
|
ClassNode outerCls = attr.getOuterCls();
|
||||||
|
cls.setDependencies(ListUtils.safeAdd(cls.getDependencies(), outerCls.getTopParentClass()));
|
||||||
|
outerCls.setUseIn(ListUtils.safeAdd(outerCls.getUseIn(), cls));
|
||||||
|
|
||||||
|
cls.remove(AType.ANONYMOUS_CLASS);
|
||||||
|
cls.remove(AFlag.DONT_GENERATE);
|
||||||
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
|
if (mth.isConstructor()) {
|
||||||
|
mth.remove(AFlag.ANONYMOUS_CONSTRUCTOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cls.addDebugComment("Anonymous mark cleared");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeAnonymousDeps(RootNode root) {
|
||||||
|
// Collect edges to build bidirectional tree:
|
||||||
|
// inline edge: anonymous -> outer (one-to-one)
|
||||||
|
// use edges: outer -> *anonymous (one-to-many)
|
||||||
|
Map<ClassNode, ClassNode> inlineMap = new HashMap<>();
|
||||||
|
Map<ClassNode, List<ClassNode>> useMap = new HashMap<>();
|
||||||
|
for (ClassNode anonymousCls : root.getClasses()) {
|
||||||
|
AnonymousClassAttr attr = anonymousCls.get(AType.ANONYMOUS_CLASS);
|
||||||
|
if (attr != null) {
|
||||||
|
ClassNode outerCls = attr.getOuterCls();
|
||||||
|
List<ClassNode> list = useMap.get(outerCls);
|
||||||
|
if (list == null || list.isEmpty()) {
|
||||||
|
list = new ArrayList<>(2);
|
||||||
|
useMap.put(outerCls, list);
|
||||||
|
}
|
||||||
|
list.add(anonymousCls);
|
||||||
|
useMap.putIfAbsent(anonymousCls, Collections.emptyList()); // put leaf explicitly
|
||||||
|
inlineMap.put(anonymousCls, outerCls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inlineMap.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// starting from leaf process deps in nodes up to root
|
||||||
|
Set<ClassNode> added = new HashSet<>();
|
||||||
|
useMap.forEach((key, list) -> {
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
added.clear();
|
||||||
|
updateDeps(key, inlineMap, added);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (ClassNode cls : root.getClasses()) {
|
||||||
|
List<ClassNode> deps = cls.getCodegenDeps();
|
||||||
|
if (deps.size() > 1) {
|
||||||
|
// distinct sorted dep, reusing collections to reduce memory allocations :)
|
||||||
|
added.clear();
|
||||||
|
added.addAll(deps);
|
||||||
|
deps.clear();
|
||||||
|
deps.addAll(added);
|
||||||
|
Collections.sort(deps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDeps(ClassNode leafCls, Map<ClassNode, ClassNode> inlineMap, Set<ClassNode> added) {
|
||||||
|
ClassNode topNode;
|
||||||
|
ClassNode current = leafCls;
|
||||||
|
while (true) {
|
||||||
|
if (!added.add(current)) {
|
||||||
|
current.addWarnComment("Loop in anonymous inline: " + current + ", path: " + added);
|
||||||
|
added.forEach(ProcessAnonymous::undoAnonymousMark);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ClassNode next = inlineMap.get(current);
|
||||||
|
if (next == null) {
|
||||||
|
topNode = current.getTopParentClass();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
if (added.size() <= 2) {
|
||||||
|
// first level deps already processed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<ClassNode> deps = topNode.getCodegenDeps();
|
||||||
|
if (deps.isEmpty()) {
|
||||||
|
deps = new ArrayList<>(added.size());
|
||||||
|
topNode.setCodegenDeps(deps);
|
||||||
|
}
|
||||||
|
for (ClassNode add : added) {
|
||||||
|
deps.add(add.getTopParentClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean canBeAnonymous(ClassNode cls) {
|
||||||
|
if (cls.getAccessFlags().isSynthetic()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String shortName = cls.getClassInfo().getShortName();
|
||||||
|
if (shortName.contains("$") || Character.isDigit(shortName.charAt(0))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (cls.getUseIn().size() == 1 && cls.getUseInMth().size() == 1) {
|
||||||
|
MethodNode useMth = cls.getUseInMth().get(0);
|
||||||
|
// allow use in enum class init
|
||||||
|
return useMth.getMethodInfo().isClassInit() && useMth.getParentClass().isEnum();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks:
|
* Checks:
|
||||||
* - class have only one constructor which used only once (allow common code for field init)
|
* - class have only one constructor which used only once (allow common code for field init)
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -164,28 +193,14 @@ public class ReSugarCode extends AbstractVisitor {
|
|||||||
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg((LiteralArg) valueArg);
|
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg((LiteralArg) valueArg);
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||||
return InsnArg.wrapArg(fGet);
|
InsnArg arg = InsnArg.wrapArg(fGet);
|
||||||
|
f.addUseIn(mth);
|
||||||
|
return arg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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()) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import jadx.api.JadxArgs;
|
|||||||
import jadx.api.plugins.utils.ZipSecurity;
|
import jadx.api.plugins.utils.ZipSecurity;
|
||||||
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.RootNode;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
@@ -34,7 +35,10 @@ public class SaveCode {
|
|||||||
if (codeStr.isEmpty()) {
|
if (codeStr.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String fileName = cls.getClassInfo().getAliasFullPath() + getFileExtension(cls);
|
if (cls.root().getArgs().isSkipFilesSave()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String fileName = cls.getClassInfo().getAliasFullPath() + getFileExtension(cls.root());
|
||||||
save(codeStr, dir, fileName);
|
save(codeStr, dir, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,8 +62,8 @@ public class SaveCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getFileExtension(ClassNode cls) {
|
public static String getFileExtension(RootNode root) {
|
||||||
JadxArgs.OutputFormatEnum outputFormat = cls.root().getArgs().getOutputFormat();
|
JadxArgs.OutputFormatEnum outputFormat = root.getArgs().getOutputFormat();
|
||||||
switch (outputFormat) {
|
switch (outputFormat) {
|
||||||
case JAVA:
|
case JAVA:
|
||||||
return ".java";
|
return ".java";
|
||||||
|
|||||||
@@ -80,11 +80,15 @@ public class SignatureProcessor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
ClassNode cls = field.getParentClass();
|
ClassNode cls = field.getParentClass();
|
||||||
try {
|
try {
|
||||||
ArgType gType = sp.consumeType();
|
ArgType signatureType = sp.consumeType();
|
||||||
if (gType == null) {
|
if (signatureType == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ArgType type = root.getTypeUtils().expandTypeVariables(cls, gType);
|
if (!validateInnerType(signatureType)) {
|
||||||
|
field.addWarnComment("Incorrect inner types in field signature: " + sp.getSignature());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ArgType type = root.getTypeUtils().expandTypeVariables(cls, signatureType);
|
||||||
if (!validateParsedType(type, field.getType())) {
|
if (!validateParsedType(type, field.getType())) {
|
||||||
cls.addWarnComment("Incorrect field signature: " + sp.getSignature());
|
cls.addWarnComment("Incorrect field signature: " + sp.getSignature());
|
||||||
return;
|
return;
|
||||||
@@ -105,6 +109,11 @@ public class SignatureProcessor extends AbstractVisitor {
|
|||||||
List<ArgType> parsedArgTypes = sp.consumeMethodArgs(mth.getMethodInfo().getArgsCount());
|
List<ArgType> parsedArgTypes = sp.consumeMethodArgs(mth.getMethodInfo().getArgsCount());
|
||||||
ArgType parsedRetType = sp.consumeType();
|
ArgType parsedRetType = sp.consumeType();
|
||||||
|
|
||||||
|
if (!validateInnerType(parsedRetType) || !validateInnerType(parsedArgTypes)) {
|
||||||
|
mth.addWarnComment("Incorrect inner types in method signature: " + sp.getSignature());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mth.updateTypeParameters(typeParameters); // apply before expand args
|
mth.updateTypeParameters(typeParameters); // apply before expand args
|
||||||
TypeUtils typeUtils = root.getTypeUtils();
|
TypeUtils typeUtils = root.getTypeUtils();
|
||||||
ArgType retType = typeUtils.expandTypeVariables(mth, parsedRetType);
|
ArgType retType = typeUtils.expandTypeVariables(mth, parsedRetType);
|
||||||
@@ -172,4 +181,54 @@ public class SignatureProcessor extends AbstractVisitor {
|
|||||||
TypeCompareEnum result = root.getTypeCompare().compareTypes(parsedType, currentType);
|
TypeCompareEnum result = root.getTypeCompare().compareTypes(parsedType, currentType);
|
||||||
return result != TypeCompareEnum.CONFLICT;
|
return result != TypeCompareEnum.CONFLICT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean validateInnerType(List<ArgType> types) {
|
||||||
|
for (ArgType type : types) {
|
||||||
|
if (!validateInnerType(type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validateInnerType(ArgType type) {
|
||||||
|
ArgType innerType = type.getInnerType();
|
||||||
|
if (innerType == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// check in outer type has inner type as inner class
|
||||||
|
ArgType outerType = type.getOuterType();
|
||||||
|
ClassNode outerCls = root.resolveClass(outerType);
|
||||||
|
if (outerCls == null) {
|
||||||
|
// can't check class not found
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String innerObj;
|
||||||
|
if (innerType.getOuterType() != null) {
|
||||||
|
innerObj = innerType.getOuterType().getObject();
|
||||||
|
// "next" inner type will be processed at end of method
|
||||||
|
} else {
|
||||||
|
innerObj = innerType.getObject();
|
||||||
|
}
|
||||||
|
if (!innerObj.contains(".")) {
|
||||||
|
// short reference
|
||||||
|
for (ClassNode innerClass : outerCls.getInnerClasses()) {
|
||||||
|
if (innerClass.getShortName().equals(innerObj)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// full name
|
||||||
|
ClassNode innerCls = root.resolveClass(innerObj);
|
||||||
|
if (innerCls == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!innerCls.getParentClass().equals(outerCls)) {
|
||||||
|
// not inner => fixing
|
||||||
|
outerCls.addInnerClass(innerCls);
|
||||||
|
innerCls.getClassInfo().convertToInner(outerCls);
|
||||||
|
}
|
||||||
|
return validateInnerType(innerType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ public class BlockExceptionHandler {
|
|||||||
commonCatchAttr = catchAttr;
|
commonCatchAttr = catchAttr;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (commonCatchAttr != catchAttr) {
|
if (!commonCatchAttr.equals(catchAttr)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,15 +390,23 @@ public class BlockExceptionHandler {
|
|||||||
private static BlockNode searchTopBlock(MethodNode mth, List<BlockNode> blocks) {
|
private static BlockNode searchTopBlock(MethodNode mth, List<BlockNode> blocks) {
|
||||||
BlockNode top = BlockUtils.getTopBlock(blocks);
|
BlockNode top = BlockUtils.getTopBlock(blocks);
|
||||||
if (top != null) {
|
if (top != null) {
|
||||||
return top;
|
return adjustTopBlock(top);
|
||||||
}
|
}
|
||||||
BlockNode topDom = BlockUtils.getCommonDominator(mth, blocks);
|
BlockNode topDom = BlockUtils.getCommonDominator(mth, blocks);
|
||||||
if (topDom != null) {
|
if (topDom != null) {
|
||||||
return topDom;
|
return adjustTopBlock(topDom);
|
||||||
}
|
}
|
||||||
throw new JadxRuntimeException("Failed to find top block for try-catch from: " + blocks);
|
throw new JadxRuntimeException("Failed to find top block for try-catch from: " + blocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static BlockNode adjustTopBlock(BlockNode topBlock) {
|
||||||
|
if (topBlock.getSuccessors().size() == 1 && !topBlock.contains(AType.EXC_CATCH)) {
|
||||||
|
// top block can be lifted by other exception handlers included in blocks list, trying to undo that
|
||||||
|
return topBlock.getSuccessors().get(0);
|
||||||
|
}
|
||||||
|
return topBlock;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static BlockNode searchBottomBlock(MethodNode mth, List<BlockNode> blocks) {
|
private static BlockNode searchBottomBlock(MethodNode mth, List<BlockNode> blocks) {
|
||||||
// search common post-dominator block inside input set
|
// search common post-dominator block inside input set
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
clearBlocksState(mth);
|
clearBlocksState(mth);
|
||||||
computeDominators(mth);
|
computeDominators(mth);
|
||||||
}
|
}
|
||||||
|
if (FixMultiEntryLoops.process(mth)) {
|
||||||
|
clearBlocksState(mth);
|
||||||
|
computeDominators(mth);
|
||||||
|
}
|
||||||
updateCleanSuccessors(mth);
|
updateCleanSuccessors(mth);
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@@ -347,7 +351,8 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
successor.add(AFlag.LOOP_START);
|
successor.add(AFlag.LOOP_START);
|
||||||
block.add(AFlag.LOOP_END);
|
block.add(AFlag.LOOP_END);
|
||||||
|
|
||||||
LoopInfo loop = new LoopInfo(successor, block);
|
Set<BlockNode> loopBlocks = BlockUtils.getAllPathsBlocks(successor, block);
|
||||||
|
LoopInfo loop = new LoopInfo(successor, block, loopBlocks);
|
||||||
successor.addAttr(AType.LOOP, loop);
|
successor.addAttr(AType.LOOP, loop);
|
||||||
block.addAttr(AType.LOOP, loop);
|
block.addAttr(AType.LOOP, loop);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,6 +192,14 @@ public class BlockSplitter extends AbstractVisitor {
|
|||||||
return newBlock;
|
return newBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void copyBlockData(BlockNode from, BlockNode to) {
|
||||||
|
List<InsnNode> toInsns = to.getInstructions();
|
||||||
|
for (InsnNode insn : from.getInstructions()) {
|
||||||
|
toInsns.add(insn.copyWithoutSsa());
|
||||||
|
}
|
||||||
|
to.copyAttributesFrom(from);
|
||||||
|
}
|
||||||
|
|
||||||
static void replaceTarget(BlockNode source, BlockNode oldTarget, BlockNode newTarget) {
|
static void replaceTarget(BlockNode source, BlockNode oldTarget, BlockNode newTarget) {
|
||||||
InsnNode lastInsn = BlockUtils.getLastInsn(source);
|
InsnNode lastInsn = BlockUtils.getLastInsn(source);
|
||||||
if (lastInsn instanceof TargetInsnNode) {
|
if (lastInsn instanceof TargetInsnNode) {
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package jadx.core.dex.visitors.blocks;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.SpecialEdgeAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.SpecialEdgeAttr.SpecialEdgeType;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
|
|
||||||
|
public class FixMultiEntryLoops {
|
||||||
|
|
||||||
|
public static boolean process(MethodNode mth) {
|
||||||
|
try {
|
||||||
|
detectSpecialEdges(mth);
|
||||||
|
} catch (Exception e) {
|
||||||
|
mth.addWarnComment("Failed to detect multi-entry loops", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<SpecialEdgeAttr> specialEdges = mth.getAll(AType.SPECIAL_EDGE);
|
||||||
|
List<SpecialEdgeAttr> multiEntryLoops = specialEdges.stream()
|
||||||
|
.filter(e -> e.getType() == SpecialEdgeType.BACK_EDGE)
|
||||||
|
.filter(e -> !isSingleEntryLoop(e))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (multiEntryLoops.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
List<SpecialEdgeAttr> crossEdges = ListUtils.filter(specialEdges, e -> e.getType() == SpecialEdgeType.CROSS_EDGE);
|
||||||
|
boolean changed = false;
|
||||||
|
for (SpecialEdgeAttr backEdge : multiEntryLoops) {
|
||||||
|
changed |= fixLoop(mth, backEdge, crossEdges);
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
} catch (Exception e) {
|
||||||
|
mth.addWarnComment("Failed to fix multi-entry loops", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean fixLoop(MethodNode mth, SpecialEdgeAttr backEdge, List<SpecialEdgeAttr> crossEdges) {
|
||||||
|
BlockNode header = backEdge.getEnd();
|
||||||
|
BlockNode headerIDom = header.getIDom();
|
||||||
|
SpecialEdgeAttr subEntry = ListUtils.filterOnlyOne(crossEdges, e -> e.getStart() == headerIDom);
|
||||||
|
if (subEntry == null || !isSupportedPattern(header, subEntry)) {
|
||||||
|
// TODO: for now only sub entry in header successor is supported
|
||||||
|
mth.addWarnComment("Unsupported multi-entry loop pattern (" + backEdge + "). Please submit an issue!!!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BlockNode loopEnd = backEdge.getStart();
|
||||||
|
BlockNode subEntryBlock = subEntry.getEnd();
|
||||||
|
BlockNode copyHeader = BlockSplitter.insertBlockBetween(mth, loopEnd, header);
|
||||||
|
BlockSplitter.copyBlockData(header, copyHeader);
|
||||||
|
BlockSplitter.replaceConnection(copyHeader, header, subEntryBlock);
|
||||||
|
mth.addDebugComment("Duplicate block to fix multi-entry loop: " + backEdge);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isSupportedPattern(BlockNode header, SpecialEdgeAttr subEntry) {
|
||||||
|
return ListUtils.isSingleElement(header.getSuccessors(), subEntry.getEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isSingleEntryLoop(SpecialEdgeAttr e) {
|
||||||
|
BlockNode header = e.getEnd();
|
||||||
|
BlockNode loopEnd = e.getStart();
|
||||||
|
return header == loopEnd
|
||||||
|
|| loopEnd.getDoms().get(header.getId()); // header dominates loop end
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum BlockColor {
|
||||||
|
WHITE, GRAY, BLACK
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void detectSpecialEdges(MethodNode mth) {
|
||||||
|
List<BlockNode> blocks = mth.getBasicBlocks();
|
||||||
|
BlockColor[] colors = new BlockColor[blocks.size()];
|
||||||
|
Arrays.fill(colors, BlockColor.WHITE);
|
||||||
|
colorDFS(mth, blocks, colors, mth.getEnterBlock().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: transform to non-recursive form
|
||||||
|
private static void colorDFS(MethodNode mth, List<BlockNode> blocks, BlockColor[] colors, int cur) {
|
||||||
|
colors[cur] = BlockColor.GRAY;
|
||||||
|
BlockNode block = blocks.get(cur);
|
||||||
|
for (BlockNode v : block.getSuccessors()) {
|
||||||
|
int vId = v.getId();
|
||||||
|
switch (colors[vId]) {
|
||||||
|
case WHITE:
|
||||||
|
colorDFS(mth, blocks, colors, vId);
|
||||||
|
break;
|
||||||
|
case GRAY:
|
||||||
|
mth.addAttr(AType.SPECIAL_EDGE, new SpecialEdgeAttr(SpecialEdgeType.BACK_EDGE, block, v));
|
||||||
|
break;
|
||||||
|
case BLACK:
|
||||||
|
mth.addAttr(AType.SPECIAL_EDGE, new SpecialEdgeAttr(SpecialEdgeType.CROSS_EDGE, block, v));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
colors[cur] = BlockColor.BLACK;
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
-33
@@ -3,13 +3,16 @@ package jadx.core.dex.visitors.debuginfo;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.OptionalInt;
|
||||||
import java.util.Set;
|
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,6 +55,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
|||||||
applyDebugInfo(mth);
|
applyDebugInfo(mth);
|
||||||
mth.remove(AType.LOCAL_VARS_DEBUG_INFO);
|
mth.remove(AType.LOCAL_VARS_DEBUG_INFO);
|
||||||
}
|
}
|
||||||
|
processMethodParametersAttribute(mth);
|
||||||
checkTypes(mth);
|
checkTypes(mth);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
mth.addWarnComment("Failed to apply debug info", e);
|
mth.addWarnComment("Failed to apply debug info", e);
|
||||||
@@ -73,31 +78,22 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
mth.getSVars().forEach(ssaVar -> collectVarDebugInfo(mth, ssaVar));
|
mth.getSVars().forEach(ssaVar -> searchAndApplyVarDebugInfo(mth, ssaVar));
|
||||||
|
|
||||||
fixLinesForReturn(mth);
|
fixLinesForReturn(mth);
|
||||||
fixNamesForPhiInsns(mth);
|
fixNamesForPhiInsns(mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void collectVarDebugInfo(MethodNode mth, SSAVar ssaVar) {
|
private static void searchAndApplyVarDebugInfo(MethodNode mth, SSAVar ssaVar) {
|
||||||
Set<RegDebugInfoAttr> debugInfoSet = new HashSet<>(ssaVar.getUseCount() + 1);
|
if (applyDebugInfo(mth, ssaVar, ssaVar.getAssign())) {
|
||||||
addRegDbdInfo(debugInfoSet, ssaVar.getAssign());
|
|
||||||
ssaVar.getUseList().forEach(registerArg -> addRegDbdInfo(debugInfoSet, registerArg));
|
|
||||||
|
|
||||||
int dbgCount = debugInfoSet.size();
|
|
||||||
if (dbgCount == 0) {
|
|
||||||
searchDebugInfoByOffset(mth, ssaVar);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (dbgCount == 1) {
|
for (RegisterArg useArg : ssaVar.getUseList()) {
|
||||||
RegDebugInfoAttr debugInfo = debugInfoSet.iterator().next();
|
if (applyDebugInfo(mth, ssaVar, useArg)) {
|
||||||
applyDebugInfo(mth, ssaVar, debugInfo.getRegType(), debugInfo.getName());
|
return;
|
||||||
} else {
|
|
||||||
mth.addInfoComment("Multiple debug info for " + ssaVar + ": " + debugInfoSet);
|
|
||||||
for (RegDebugInfoAttr debugInfo : debugInfoSet) {
|
|
||||||
applyDebugInfo(mth, ssaVar, debugInfo.getRegType(), debugInfo.getName());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
searchDebugInfoByOffset(mth, ssaVar);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void searchDebugInfoByOffset(MethodNode mth, SSAVar ssaVar) {
|
private static void searchDebugInfoByOffset(MethodNode mth, SSAVar ssaVar) {
|
||||||
@@ -105,14 +101,12 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
|||||||
if (debugInfoAttr == null) {
|
if (debugInfoAttr == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Optional<Integer> max = ssaVar.getUseList().stream()
|
OptionalInt max = ssaVar.getUseList().stream().mapToInt(DebugInfoApplyVisitor::getInsnOffsetByArg).max();
|
||||||
.map(DebugInfoApplyVisitor::getInsnOffsetByArg)
|
|
||||||
.max(Integer::compareTo);
|
|
||||||
if (!max.isPresent()) {
|
if (!max.isPresent()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int startOffset = getInsnOffsetByArg(ssaVar.getAssign());
|
int startOffset = getInsnOffsetByArg(ssaVar.getAssign());
|
||||||
int endOffset = max.get();
|
int endOffset = max.getAsInt();
|
||||||
int regNum = ssaVar.getRegNum();
|
int regNum = ssaVar.getRegNum();
|
||||||
for (ILocalVar localVar : debugInfoAttr.getLocalVars()) {
|
for (ILocalVar localVar : debugInfoAttr.getLocalVars()) {
|
||||||
if (localVar.getRegNum() == regNum) {
|
if (localVar.getRegNum() == regNum) {
|
||||||
@@ -144,24 +138,26 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType type, String varName) {
|
public static boolean applyDebugInfo(MethodNode mth, SSAVar ssaVar, RegisterArg arg) {
|
||||||
TypeUpdateResult result = mth.root().getTypeUpdate().applyWithWiderAllow(mth, ssaVar, type);
|
RegDebugInfoAttr debugInfoAttr = arg.get(AType.REG_DEBUG_INFO);
|
||||||
|
if (debugInfoAttr == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return applyDebugInfo(mth, ssaVar, debugInfoAttr.getRegType(), debugInfoAttr.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType type, String varName) {
|
||||||
|
TypeUpdateResult result = mth.root().getTypeUpdate().applyWithWiderIgnoreUnknown(mth, ssaVar, type);
|
||||||
if (result == TypeUpdateResult.REJECT) {
|
if (result == TypeUpdateResult.REJECT) {
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth);
|
LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth);
|
||||||
}
|
}
|
||||||
} else {
|
return false;
|
||||||
if (NameMapper.isValidAndPrintable(varName)) {
|
|
||||||
ssaVar.setName(varName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
if (NameMapper.isValidAndPrintable(varName)) {
|
||||||
|
ssaVar.setName(varName);
|
||||||
private static void addRegDbdInfo(Set<RegDebugInfoAttr> debugInfo, RegisterArg reg) {
|
|
||||||
RegDebugInfoAttr debugInfoAttr = reg.get(AType.REG_DEBUG_INFO);
|
|
||||||
if (debugInfoAttr != null) {
|
|
||||||
debugInfo.add(debugInfoAttr);
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -230,4 +226,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-6
@@ -89,25 +89,33 @@ public class DebugInfoAttachVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
for (int i = start; i <= end; i++) {
|
for (int i = start; i <= end; i++) {
|
||||||
InsnNode insn = insnArr[i];
|
InsnNode insn = insnArr[i];
|
||||||
if (insn != null) {
|
if (insn == null) {
|
||||||
attachDebugInfo(insn.getResult(), debugInfoAttr, regNum);
|
continue;
|
||||||
for (InsnArg arg : insn.getArguments()) {
|
|
||||||
attachDebugInfo(arg, debugInfoAttr, regNum);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
int count = 0;
|
||||||
|
for (InsnArg arg : insn.getArguments()) {
|
||||||
|
count += attachDebugInfo(arg, debugInfoAttr, regNum);
|
||||||
|
}
|
||||||
|
if (count != 0) {
|
||||||
|
// don't apply same info for result if applied to args
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
attachDebugInfo(insn.getResult(), debugInfoAttr, regNum);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mth.addAttr(new LocalVarsDebugInfoAttr(localVars));
|
mth.addAttr(new LocalVarsDebugInfoAttr(localVars));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void attachDebugInfo(InsnArg arg, RegDebugInfoAttr debugInfoAttr, int regNum) {
|
private int attachDebugInfo(InsnArg arg, RegDebugInfoAttr debugInfoAttr, int regNum) {
|
||||||
if (arg instanceof RegisterArg) {
|
if (arg instanceof RegisterArg) {
|
||||||
RegisterArg reg = (RegisterArg) arg;
|
RegisterArg reg = (RegisterArg) arg;
|
||||||
if (regNum == reg.getRegNum()) {
|
if (regNum == reg.getRegNum()) {
|
||||||
reg.addAttr(debugInfoAttr);
|
reg.addAttr(debugInfoAttr);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArgType getVarType(MethodNode mth, ILocalVar var) {
|
public static ArgType getVarType(MethodNode mth, ILocalVar var) {
|
||||||
|
|||||||
@@ -154,7 +154,11 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
// remove 'finally' from 'try' blocks, check all up paths on each exit (connected with finally exit)
|
// remove 'finally' from 'try' blocks, check all up paths on each exit (connected with finally exit)
|
||||||
List<BlockNode> tryBlocks = allHandler.getTryBlock().getBlocks();
|
List<BlockNode> tryBlocks = allHandler.getTryBlock().getBlocks();
|
||||||
BlockNode bottomFinallyBlock = BlockUtils.followEmptyPath(BlockUtils.getBottomBlock(allHandler.getBlocks()));
|
BlockNode bottomBlock = BlockUtils.getBottomBlock(allHandler.getBlocks());
|
||||||
|
if (bottomBlock == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BlockNode bottomFinallyBlock = BlockUtils.followEmptyPath(bottomBlock);
|
||||||
BlockNode bottom = BlockUtils.getNextBlock(bottomFinallyBlock);
|
BlockNode bottom = BlockUtils.getNextBlock(bottomFinallyBlock);
|
||||||
if (bottom == null) {
|
if (bottom == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -382,6 +382,9 @@ public class IfMakerHelper {
|
|||||||
}
|
}
|
||||||
if (useCount > 1) {
|
if (useCount > 1) {
|
||||||
forceInlineInsns.add(insn);
|
forceInlineInsns.add(insn);
|
||||||
|
} else {
|
||||||
|
// allow only forced assign inline
|
||||||
|
pass = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ import jadx.core.utils.RegionUtils;
|
|||||||
import static jadx.core.utils.RegionUtils.insnsCount;
|
import static jadx.core.utils.RegionUtils.insnsCount;
|
||||||
|
|
||||||
public class IfRegionVisitor extends AbstractVisitor {
|
public class IfRegionVisitor extends AbstractVisitor {
|
||||||
|
|
||||||
private static final TernaryMod TERNARY_VISITOR = new TernaryMod();
|
|
||||||
private static final ProcessIfRegionVisitor PROCESS_IF_REGION_VISITOR = new ProcessIfRegionVisitor();
|
private static final ProcessIfRegionVisitor PROCESS_IF_REGION_VISITOR = new ProcessIfRegionVisitor();
|
||||||
private static final RemoveRedundantElseVisitor REMOVE_REDUNDANT_ELSE_VISITOR = new RemoveRedundantElseVisitor();
|
private static final RemoveRedundantElseVisitor REMOVE_REDUNDANT_ELSE_VISITOR = new RemoveRedundantElseVisitor();
|
||||||
|
|
||||||
@@ -30,7 +28,7 @@ public class IfRegionVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void process(MethodNode mth) {
|
public static void process(MethodNode mth) {
|
||||||
DepthRegionTraversal.traverseIterative(mth, TERNARY_VISITOR);
|
TernaryMod.process(mth);
|
||||||
DepthRegionTraversal.traverse(mth, PROCESS_IF_REGION_VISITOR);
|
DepthRegionTraversal.traverse(mth, PROCESS_IF_REGION_VISITOR);
|
||||||
DepthRegionTraversal.traverseIterative(mth, REMOVE_REDUNDANT_ELSE_VISITOR);
|
DepthRegionTraversal.traverseIterative(mth, REMOVE_REDUNDANT_ELSE_VISITOR);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,38 @@ import jadx.core.utils.InsnRemover;
|
|||||||
/**
|
/**
|
||||||
* Convert 'if' to ternary operation
|
* Convert 'if' to ternary operation
|
||||||
*/
|
*/
|
||||||
public class TernaryMod implements IRegionIterativeVisitor {
|
public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativeVisitor {
|
||||||
|
|
||||||
|
private static final TernaryMod INSTANCE = new TernaryMod();
|
||||||
|
|
||||||
|
public static void process(MethodNode mth) {
|
||||||
|
// first: convert all found ternary nodes in one iteration
|
||||||
|
DepthRegionTraversal.traverse(mth, INSTANCE);
|
||||||
|
if (mth.contains(AFlag.REQUEST_CODE_SHRINK)) {
|
||||||
|
CodeShrinkVisitor.shrinkMethod(mth);
|
||||||
|
}
|
||||||
|
// second: iterative runs with shrink after each change
|
||||||
|
DepthRegionTraversal.traverseIterative(mth, INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean enterRegion(MethodNode mth, IRegion region) {
|
||||||
|
if (processRegion(mth, region)) {
|
||||||
|
mth.add(AFlag.REQUEST_CODE_SHRINK);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visitRegion(MethodNode mth, IRegion region) {
|
public boolean visitRegion(MethodNode mth, IRegion region) {
|
||||||
|
if (processRegion(mth, region)) {
|
||||||
|
CodeShrinkVisitor.shrinkMethod(mth);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean processRegion(MethodNode mth, IRegion region) {
|
||||||
if (region instanceof IfRegion) {
|
if (region instanceof IfRegion) {
|
||||||
return makeTernaryInsn(mth, (IfRegion) region);
|
return makeTernaryInsn(mth, (IfRegion) region);
|
||||||
}
|
}
|
||||||
@@ -115,9 +143,6 @@ public class TernaryMod implements IRegionIterativeVisitor {
|
|||||||
header.getInstructions().add(ternInsn);
|
header.getInstructions().add(ternInsn);
|
||||||
|
|
||||||
clearConditionBlocks(conditionBlocks, header);
|
clearConditionBlocks(conditionBlocks, header);
|
||||||
|
|
||||||
// shrink method again
|
|
||||||
CodeShrinkVisitor.shrinkMethod(mth);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,8 +176,6 @@ public class TernaryMod implements IRegionIterativeVisitor {
|
|||||||
header.add(AFlag.RETURN);
|
header.add(AFlag.RETURN);
|
||||||
|
|
||||||
clearConditionBlocks(conditionBlocks, header);
|
clearConditionBlocks(conditionBlocks, header);
|
||||||
|
|
||||||
CodeShrinkVisitor.shrinkMethod(mth);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -291,4 +314,7 @@ public class TernaryMod implements IRegionIterativeVisitor {
|
|||||||
// shrink method again
|
// shrink method again
|
||||||
CodeShrinkVisitor.shrinkMethod(mth);
|
CodeShrinkVisitor.shrinkMethod(mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TernaryMod() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ import jadx.core.dex.nodes.RootNode;
|
|||||||
import jadx.core.dex.visitors.AbstractVisitor;
|
import jadx.core.dex.visitors.AbstractVisitor;
|
||||||
|
|
||||||
public class RenameVisitor extends AbstractVisitor {
|
public class RenameVisitor extends AbstractVisitor {
|
||||||
|
private static final Pattern ANONYMOUS_CLASS_PATTERN = Pattern.compile("^\\d+$");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(RootNode root) {
|
public void init(RootNode root) {
|
||||||
@@ -130,11 +132,12 @@ public class RenameVisitor extends AbstractVisitor {
|
|||||||
private static String fixClsShortName(JadxArgs args, String clsName) {
|
private static String fixClsShortName(JadxArgs args, String clsName) {
|
||||||
boolean renameValid = args.isRenameValid();
|
boolean renameValid = args.isRenameValid();
|
||||||
if (renameValid) {
|
if (renameValid) {
|
||||||
char firstChar = clsName.charAt(0);
|
if (ANONYMOUS_CLASS_PATTERN.matcher(clsName).matches()) {
|
||||||
if (Character.isDigit(firstChar)) {
|
|
||||||
return Consts.ANONYMOUS_CLASS_PREFIX + NameMapper.removeInvalidCharsMiddle(clsName);
|
return Consts.ANONYMOUS_CLASS_PREFIX + NameMapper.removeInvalidCharsMiddle(clsName);
|
||||||
}
|
}
|
||||||
if (firstChar == '$') {
|
|
||||||
|
char firstChar = clsName.charAt(0);
|
||||||
|
if (firstChar == '$' || Character.isDigit(firstChar)) {
|
||||||
return 'C' + NameMapper.removeInvalidCharsMiddle(clsName);
|
return 'C' + NameMapper.removeInvalidCharsMiddle(clsName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import java.util.ArrayList;
|
|||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||||
|
import jadx.core.dex.instructions.args.Named;
|
||||||
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.BlockNode;
|
||||||
@@ -40,6 +42,7 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
|||||||
if (mth.isNoCode()) {
|
if (mth.isNoCode()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
mth.remove(AFlag.REQUEST_CODE_SHRINK);
|
||||||
for (BlockNode block : mth.getBasicBlocks()) {
|
for (BlockNode block : mth.getBasicBlocks()) {
|
||||||
shrinkBlock(mth, block);
|
shrinkBlock(mth, block);
|
||||||
simplifyMoveInsns(mth, block);
|
simplifyMoveInsns(mth, block);
|
||||||
@@ -76,7 +79,9 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
private static void checkInline(MethodNode mth, BlockNode block, InsnList insnList,
|
private static void checkInline(MethodNode mth, BlockNode block, InsnList insnList,
|
||||||
List<WrapInfo> wrapList, ArgsInfo argsInfo, RegisterArg arg) {
|
List<WrapInfo> wrapList, ArgsInfo argsInfo, RegisterArg arg) {
|
||||||
if (arg.contains(AFlag.DONT_INLINE)) {
|
if (arg.contains(AFlag.DONT_INLINE)
|
||||||
|
|| arg.getParentInsn() == null
|
||||||
|
|| arg.getParentInsn().contains(AFlag.DONT_GENERATE)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SSAVar sVar = arg.getSVar();
|
SSAVar sVar = arg.getSVar();
|
||||||
@@ -89,21 +94,34 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
|||||||
|| assignInsn.contains(AFlag.WRAPPED)) {
|
|| assignInsn.contains(AFlag.WRAPPED)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// allow inline only one use arg
|
|
||||||
boolean assignInline = assignInsn.contains(AFlag.FORCE_ASSIGN_INLINE);
|
boolean assignInline = assignInsn.contains(AFlag.FORCE_ASSIGN_INLINE);
|
||||||
if (!assignInline && sVar.getVariableUseCount() != 1) {
|
if (!assignInline && sVar.isUsedInPhi()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<RegisterArg> useList = sVar.getUseList();
|
// allow inline only one use arg
|
||||||
if (!useList.isEmpty()) {
|
int useCount = 0;
|
||||||
RegisterArg useArg = useList.get(0);
|
for (RegisterArg useArg : sVar.getUseList()) {
|
||||||
InsnNode parentInsn = useArg.getParentInsn();
|
InsnNode parentInsn = useArg.getParentInsn();
|
||||||
if (parentInsn != null && parentInsn.contains(AFlag.DONT_GENERATE)) {
|
if (parentInsn != null && parentInsn.contains(AFlag.DONT_GENERATE)) {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
if (!assignInline && useArg.contains(AFlag.DONT_INLINE_CONST)) {
|
if (!assignInline && useArg.contains(AFlag.DONT_INLINE_CONST)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
useCount++;
|
||||||
|
}
|
||||||
|
if (!assignInline && useCount != 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!assignInline && sVar.getName() != null) {
|
||||||
|
if (searchArgWithName(assignInsn, sVar.getName())) {
|
||||||
|
// allow inline if name is reused in result
|
||||||
|
} else if (varWithSameNameExists(mth, sVar)) {
|
||||||
|
// allow inline if var name is duplicated
|
||||||
|
} else {
|
||||||
|
// reject inline of named variable
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int assignPos = insnList.getIndex(assignInsn);
|
int assignPos = insnList.getIndex(assignInsn);
|
||||||
@@ -127,6 +145,31 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean varWithSameNameExists(MethodNode mth, SSAVar inlineVar) {
|
||||||
|
for (SSAVar ssaVar : mth.getSVars()) {
|
||||||
|
if (ssaVar == inlineVar || ssaVar.getCodeVar() == inlineVar.getCodeVar()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Objects.equals(ssaVar.getName(), inlineVar.getName())) {
|
||||||
|
return ssaVar.getUseCount() > inlineVar.getUseCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean searchArgWithName(InsnNode assignInsn, String varName) {
|
||||||
|
InsnArg result = assignInsn.visitArgs(insnArg -> {
|
||||||
|
if (insnArg instanceof Named) {
|
||||||
|
String argName = ((Named) insnArg).getName();
|
||||||
|
if (Objects.equals(argName, varName)) {
|
||||||
|
return insnArg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return result != null;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean assignInline(MethodNode mth, RegisterArg arg, InsnNode assignInsn, BlockNode assignBlock) {
|
private static boolean assignInline(MethodNode mth, RegisterArg arg, InsnNode assignInsn, BlockNode assignBlock) {
|
||||||
RegisterArg useArg = arg.getSVar().getUseList().get(0);
|
RegisterArg useArg = arg.getSVar().getUseList().get(0);
|
||||||
InsnNode useInsn = useArg.getParentInsn();
|
InsnNode useInsn = useArg.getParentInsn();
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ public class SSATransform extends AbstractVisitor {
|
|||||||
stack.push(initState);
|
stack.push(initState);
|
||||||
while (!stack.isEmpty()) {
|
while (!stack.isEmpty()) {
|
||||||
RenameState state = stack.pop();
|
RenameState state = stack.pop();
|
||||||
renameVarsInBlock(state);
|
renameVarsInBlock(mth, state);
|
||||||
for (BlockNode dominated : state.getBlock().getDominatesOn()) {
|
for (BlockNode dominated : state.getBlock().getDominatesOn()) {
|
||||||
stack.push(RenameState.copyFrom(state, dominated));
|
stack.push(RenameState.copyFrom(state, dominated));
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ public class SSATransform extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void renameVarsInBlock(RenameState state) {
|
private static void renameVarsInBlock(MethodNode mth, RenameState state) {
|
||||||
BlockNode block = state.getBlock();
|
BlockNode block = state.getBlock();
|
||||||
for (InsnNode insn : block.getInstructions()) {
|
for (InsnNode insn : block.getInstructions()) {
|
||||||
if (insn.getType() != InsnType.PHI) {
|
if (insn.getType() != InsnType.PHI) {
|
||||||
@@ -168,8 +168,9 @@ public class SSATransform extends AbstractVisitor {
|
|||||||
int regNum = reg.getRegNum();
|
int regNum = reg.getRegNum();
|
||||||
SSAVar var = state.getVar(regNum);
|
SSAVar var = state.getVar(regNum);
|
||||||
if (var == null) {
|
if (var == null) {
|
||||||
throw new JadxRuntimeException("Not initialized variable reg: " + regNum
|
// TODO: in most cases issue in incorrectly attached exception handlers
|
||||||
+ ", insn: " + insn + ", block:" + block);
|
mth.addWarnComment("Not initialized variable reg: " + regNum + ", insn: " + insn + ", block:" + block);
|
||||||
|
var = state.startVar(reg);
|
||||||
}
|
}
|
||||||
var.use(reg);
|
var.use(reg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import java.util.Objects;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.ArgType.WildcardBound;
|
import jadx.core.dex.instructions.args.ArgType.WildcardBound;
|
||||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
@@ -42,6 +43,17 @@ public class TypeCompare {
|
|||||||
return compareObjects(first.getType(), second.getType());
|
return compareObjects(first.getType(), second.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TypeCompareEnum compareTypes(ClassInfo first, ClassInfo second) {
|
||||||
|
return compareObjects(first.getType(), second.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypeCompareEnum compareObjects(ArgType first, ArgType second) {
|
||||||
|
if (first == second || Objects.equals(first, second)) {
|
||||||
|
return TypeCompareEnum.EQUAL;
|
||||||
|
}
|
||||||
|
return compareObjectsNoPreCheck(first, second);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare two type and return result for first argument (narrow, wider or conflict)
|
* Compare two type and return result for first argument (narrow, wider or conflict)
|
||||||
*/
|
*/
|
||||||
@@ -81,7 +93,7 @@ public class TypeCompare {
|
|||||||
boolean firstObj = first.isObject();
|
boolean firstObj = first.isObject();
|
||||||
boolean secondObj = second.isObject();
|
boolean secondObj = second.isObject();
|
||||||
if (firstObj && secondObj) {
|
if (firstObj && secondObj) {
|
||||||
return compareObjects(first, second);
|
return compareObjectsNoPreCheck(first, second);
|
||||||
} else {
|
} else {
|
||||||
// primitive types conflicts with objects
|
// primitive types conflicts with objects
|
||||||
if (firstObj && secondPrimitive) {
|
if (firstObj && secondPrimitive) {
|
||||||
@@ -159,7 +171,7 @@ public class TypeCompare {
|
|||||||
return CONFLICT;
|
return CONFLICT;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeCompareEnum compareObjects(ArgType first, ArgType second) {
|
private TypeCompareEnum compareObjectsNoPreCheck(ArgType first, ArgType second) {
|
||||||
boolean objectsEquals = first.getObject().equals(second.getObject());
|
boolean objectsEquals = first.getObject().equals(second.getObject());
|
||||||
boolean firstGenericType = first.isGenericType();
|
boolean firstGenericType = first.isGenericType();
|
||||||
boolean secondGenericType = second.isGenericType();
|
boolean secondGenericType = second.isGenericType();
|
||||||
@@ -262,7 +274,7 @@ public class TypeCompare {
|
|||||||
return NARROW;
|
return NARROW;
|
||||||
}
|
}
|
||||||
for (ArgType extendType : extendTypes) {
|
for (ArgType extendType : extendTypes) {
|
||||||
TypeCompareEnum res = compareObjects(extendType, objType);
|
TypeCompareEnum res = compareObjectsNoPreCheck(extendType, objType);
|
||||||
if (!res.isNarrow()) {
|
if (!res.isNarrow()) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ public enum TypeCompareEnum {
|
|||||||
return this == WIDER || this == WIDER_BY_GENERIC;
|
return this == WIDER || this == WIDER_BY_GENERIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isWiderOrEqual() {
|
||||||
|
return isEqual() || isWider();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isNarrow() {
|
public boolean isNarrow() {
|
||||||
return this == NARROW || this == NARROW_BY_GENERIC;
|
return this == NARROW || this == NARROW_BY_GENERIC;
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-2
@@ -19,7 +19,7 @@ import jadx.core.Consts;
|
|||||||
import jadx.core.clsp.ClspGraph;
|
import jadx.core.clsp.ClspGraph;
|
||||||
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.AnonymousClassBaseAttr;
|
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.instructions.ArithNode;
|
import jadx.core.dex.instructions.ArithNode;
|
||||||
@@ -87,6 +87,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
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);
|
||||||
@@ -321,7 +322,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
if (ctr.isNewInstance()) {
|
if (ctr.isNewInstance()) {
|
||||||
ClassNode ctrCls = root.resolveClass(ctr.getClassType());
|
ClassNode ctrCls = root.resolveClass(ctr.getClassType());
|
||||||
if (ctrCls != null && ctrCls.contains(AFlag.DONT_GENERATE)) {
|
if (ctrCls != null && ctrCls.contains(AFlag.DONT_GENERATE)) {
|
||||||
AnonymousClassBaseAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS_BASE);
|
AnonymousClassAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS);
|
||||||
if (baseTypeAttr != null) {
|
if (baseTypeAttr != null) {
|
||||||
return baseTypeAttr.getBaseType();
|
return baseTypeAttr.getBaseType();
|
||||||
}
|
}
|
||||||
@@ -835,6 +836,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 +844,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 +853,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 +938,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);
|
||||||
|
|||||||
@@ -66,7 +66,11 @@ public final class TypeUpdate {
|
|||||||
* Force type setting
|
* Force type setting
|
||||||
*/
|
*/
|
||||||
public TypeUpdateResult applyWithWiderIgnSame(MethodNode mth, SSAVar ssaVar, ArgType candidateType) {
|
public TypeUpdateResult applyWithWiderIgnSame(MethodNode mth, SSAVar ssaVar, ArgType candidateType) {
|
||||||
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNSAME);
|
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNORE_SAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypeUpdateResult applyWithWiderIgnoreUnknown(MethodNode mth, SSAVar ssaVar, ArgType candidateType) {
|
||||||
|
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNORE_UNKNOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeUpdateResult apply(MethodNode mth, SSAVar ssaVar, ArgType candidateType, TypeUpdateFlags flags) {
|
private TypeUpdateResult apply(MethodNode mth, SSAVar ssaVar, ArgType candidateType, TypeUpdateFlags flags) {
|
||||||
@@ -110,6 +114,9 @@ public final class TypeUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType);
|
TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType);
|
||||||
|
if (compareResult == TypeCompareEnum.UNKNOWN && updateInfo.getFlags().isIgnoreUnknown()) {
|
||||||
|
return REJECT;
|
||||||
|
}
|
||||||
if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) {
|
if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) {
|
||||||
// don't changed type
|
// don't changed type
|
||||||
if (compareResult == TypeCompareEnum.EQUAL) {
|
if (compareResult == TypeCompareEnum.EQUAL) {
|
||||||
@@ -506,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) {
|
||||||
@@ -514,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;
|
||||||
@@ -531,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,39 +1,58 @@
|
|||||||
package jadx.core.dex.visitors.typeinference;
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class TypeUpdateFlags {
|
public class TypeUpdateFlags {
|
||||||
|
private static final int ALLOW_WIDER = 1;
|
||||||
|
private static final int IGNORE_SAME = 2;
|
||||||
|
private static final int IGNORE_UNKNOWN = 4;
|
||||||
|
|
||||||
public static final TypeUpdateFlags FLAGS_EMPTY = new TypeUpdateFlags(false, false);
|
public static final TypeUpdateFlags FLAGS_EMPTY = build(0);
|
||||||
public static final TypeUpdateFlags FLAGS_WIDER = new TypeUpdateFlags(true, false);
|
public static final TypeUpdateFlags FLAGS_WIDER = build(ALLOW_WIDER);
|
||||||
public static final TypeUpdateFlags FLAGS_WIDER_IGNSAME = new TypeUpdateFlags(true, true);
|
public static final TypeUpdateFlags FLAGS_WIDER_IGNORE_SAME = build(ALLOW_WIDER | IGNORE_SAME);
|
||||||
|
public static final TypeUpdateFlags FLAGS_WIDER_IGNORE_UNKNOWN = build(ALLOW_WIDER | IGNORE_UNKNOWN);
|
||||||
|
|
||||||
private final boolean allowWider;
|
private final int flags;
|
||||||
private final boolean ignoreSame;
|
|
||||||
|
|
||||||
private TypeUpdateFlags(boolean allowWider, boolean ignoreSame) {
|
@NotNull
|
||||||
this.allowWider = allowWider;
|
private static TypeUpdateFlags build(int flags) {
|
||||||
this.ignoreSame = ignoreSame;
|
return new TypeUpdateFlags(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeUpdateFlags(int flags) {
|
||||||
|
this.flags = flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAllowWider() {
|
public boolean isAllowWider() {
|
||||||
return allowWider;
|
return (flags & ALLOW_WIDER) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isIgnoreSame() {
|
public boolean isIgnoreSame() {
|
||||||
return ignoreSame;
|
return (flags & IGNORE_SAME) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIgnoreUnknown() {
|
||||||
|
return (flags & IGNORE_UNKNOWN) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
if (allowWider) {
|
if (isAllowWider()) {
|
||||||
sb.append("ALLOW_WIDER");
|
sb.append("ALLOW_WIDER");
|
||||||
}
|
}
|
||||||
if (ignoreSame) {
|
if (isIgnoreSame()) {
|
||||||
if (sb.length() != 0) {
|
if (sb.length() != 0) {
|
||||||
sb.append('|');
|
sb.append('|');
|
||||||
}
|
}
|
||||||
sb.append("IGNORE_SAME");
|
sb.append("IGNORE_SAME");
|
||||||
}
|
}
|
||||||
|
if (isIgnoreUnknown()) {
|
||||||
|
if (sb.length() != 0) {
|
||||||
|
sb.append('|');
|
||||||
|
}
|
||||||
|
sb.append("IGNORE_UNKNOWN");
|
||||||
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,8 @@ public class ExportGradleProject {
|
|||||||
Integer versionCode = Integer.valueOf(manifest.getAttribute("android:versionCode"));
|
Integer versionCode = Integer.valueOf(manifest.getAttribute("android:versionCode"));
|
||||||
String versionName = manifest.getAttribute("android:versionName");
|
String versionName = manifest.getAttribute("android:versionName");
|
||||||
Integer minSdk = Integer.valueOf(usesSdk.getAttribute("android:minSdkVersion"));
|
Integer minSdk = Integer.valueOf(usesSdk.getAttribute("android:minSdkVersion"));
|
||||||
Integer targetSdk = Integer.valueOf(usesSdk.getAttribute("android:targetSdkVersion"));
|
String stringTargetSdk = usesSdk.getAttribute("android:targetSdkVersion");
|
||||||
|
Integer targetSdk = stringTargetSdk.isEmpty() ? minSdk : Integer.valueOf(stringTargetSdk);
|
||||||
String appName = "UNKNOWN";
|
String appName = "UNKNOWN";
|
||||||
|
|
||||||
if (application.hasAttribute("android:label")) {
|
if (application.hasAttribute("android:label")) {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import jadx.core.dex.nodes.IBlock;
|
|||||||
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.regions.conditions.IfCondition;
|
import jadx.core.dex.regions.conditions.IfCondition;
|
||||||
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public class BlockUtils {
|
public class BlockUtils {
|
||||||
@@ -382,6 +383,7 @@ public class BlockUtils {
|
|||||||
/**
|
/**
|
||||||
* Return first successor which not exception handler and not follow loop back edge
|
* Return first successor which not exception handler and not follow loop back edge
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
public static BlockNode getNextBlock(BlockNode block) {
|
public static BlockNode getNextBlock(BlockNode block) {
|
||||||
List<BlockNode> s = block.getCleanSuccessors();
|
List<BlockNode> s = block.getCleanSuccessors();
|
||||||
return s.isEmpty() ? null : s.get(0);
|
return s.isEmpty() ? null : s.get(0);
|
||||||
@@ -594,6 +596,7 @@ public class BlockUtils {
|
|||||||
/**
|
/**
|
||||||
* Search last block in control flow graph from input set.
|
* Search last block in control flow graph from input set.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
public static BlockNode getBottomBlock(List<BlockNode> blocks) {
|
public static BlockNode getBottomBlock(List<BlockNode> blocks) {
|
||||||
if (blocks.size() == 1) {
|
if (blocks.size() == 1) {
|
||||||
return blocks.get(0);
|
return blocks.get(0);
|
||||||
@@ -701,7 +704,7 @@ public class BlockUtils {
|
|||||||
mth.getLoops().forEach(l -> excluded.set(l.getStart().getId()));
|
mth.getLoops().forEach(l -> excluded.set(l.getStart().getId()));
|
||||||
if (!mth.isNoExceptionHandlers()) {
|
if (!mth.isNoExceptionHandlers()) {
|
||||||
// exclude exception handlers paths
|
// exclude exception handlers paths
|
||||||
mth.getExceptionHandlers().forEach(h -> excluded.or(h.getHandlerBlock().getDomFrontier()));
|
mth.getExceptionHandlers().forEach(h -> mergeExcHandlerDomFrontier(mth, h, excluded));
|
||||||
}
|
}
|
||||||
domFrontBS.andNot(excluded);
|
domFrontBS.andNot(excluded);
|
||||||
oneBlock = bitSetToOneBlock(mth, domFrontBS);
|
oneBlock = bitSetToOneBlock(mth, domFrontBS);
|
||||||
@@ -709,6 +712,7 @@ public class BlockUtils {
|
|||||||
return oneBlock;
|
return oneBlock;
|
||||||
}
|
}
|
||||||
BitSet combinedDF = newBlocksBitSet(mth);
|
BitSet combinedDF = newBlocksBitSet(mth);
|
||||||
|
int k = mth.getBasicBlocks().size();
|
||||||
while (true) {
|
while (true) {
|
||||||
// collect dom frontier blocks from current set until only one block left
|
// collect dom frontier blocks from current set until only one block left
|
||||||
forEachBlockFromBitSet(mth, domFrontBS, block -> {
|
forEachBlockFromBitSet(mth, domFrontBS, block -> {
|
||||||
@@ -726,6 +730,10 @@ public class BlockUtils {
|
|||||||
if (cardinality == 0) {
|
if (cardinality == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (k-- < 0) {
|
||||||
|
mth.addWarnComment("Path cross not found for " + blocks + ", limit reached: " + mth.getBasicBlocks().size());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
// replace domFrontBS with combinedDF
|
// replace domFrontBS with combinedDF
|
||||||
domFrontBS.clear();
|
domFrontBS.clear();
|
||||||
domFrontBS.or(combinedDF);
|
domFrontBS.or(combinedDF);
|
||||||
@@ -733,6 +741,20 @@ public class BlockUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void mergeExcHandlerDomFrontier(MethodNode mth, ExceptionHandler handler, BitSet set) {
|
||||||
|
BlockNode handlerBlock = handler.getHandlerBlock();
|
||||||
|
if (handlerBlock == null) {
|
||||||
|
mth.addDebugComment("Null handler block in: " + handler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BitSet domFrontier = handlerBlock.getDomFrontier();
|
||||||
|
if (domFrontier == null) {
|
||||||
|
mth.addDebugComment("Null dom frontier in handler: " + handler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
set.or(domFrontier);
|
||||||
|
}
|
||||||
|
|
||||||
public static BlockNode getPathCross(MethodNode mth, BlockNode b1, BlockNode b2) {
|
public static BlockNode getPathCross(MethodNode mth, BlockNode b1, BlockNode b2) {
|
||||||
if (b1 == b2) {
|
if (b1 == b2) {
|
||||||
return b1;
|
return b1;
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ package jadx.core.utils;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -17,12 +16,13 @@ import jadx.api.IDecompileScheduler;
|
|||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public class DecompilerScheduler implements IDecompileScheduler {
|
public class DecompilerScheduler implements IDecompileScheduler {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DecompilerScheduler.class);
|
private static final Logger LOG = LoggerFactory.getLogger(DecompilerScheduler.class);
|
||||||
|
|
||||||
private static final int MERGED_BATCH_SIZE = 16;
|
private static final int MERGED_BATCH_SIZE = 16;
|
||||||
private static final boolean DUMP_STATS = false;
|
private static final boolean DEBUG_BATCHES = false;
|
||||||
|
|
||||||
private final JadxDecompiler decompiler;
|
private final JadxDecompiler decompiler;
|
||||||
|
|
||||||
@@ -32,13 +32,21 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<List<JavaClass>> buildBatches(List<JavaClass> classes) {
|
public List<List<JavaClass>> buildBatches(List<JavaClass> classes) {
|
||||||
long start = System.currentTimeMillis();
|
try {
|
||||||
List<List<ClassNode>> batches = internalBatches(Utils.collectionMap(classes, JavaClass::getClassNode));
|
long start = System.currentTimeMillis();
|
||||||
List<List<JavaClass>> result = Utils.collectionMap(batches, l -> Utils.collectionMapNoNull(l, decompiler::getJavaClassByNode));
|
List<List<ClassNode>> batches = internalBatches(Utils.collectionMap(classes, JavaClass::getClassNode));
|
||||||
if (LOG.isDebugEnabled()) {
|
List<List<JavaClass>> result = Utils.collectionMap(batches, l -> Utils.collectionMapNoNull(l, decompiler::getJavaClassByNode));
|
||||||
LOG.debug("Build decompilation batches in {}ms", System.currentTimeMillis() - start);
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Build decompilation batches in {}ms", System.currentTimeMillis() - start);
|
||||||
|
}
|
||||||
|
if (DEBUG_BATCHES) {
|
||||||
|
check(result, classes);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOG.warn("Build batches failed (continue with fallback)", e);
|
||||||
|
return buildFallback(classes);
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,26 +54,20 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
|||||||
* Build batches for dependencies of single class to avoid locking from another thread.
|
* Build batches for dependencies of single class to avoid locking from another thread.
|
||||||
*/
|
*/
|
||||||
public List<List<ClassNode>> internalBatches(List<ClassNode> classes) {
|
public List<List<ClassNode>> internalBatches(List<ClassNode> classes) {
|
||||||
Map<ClassNode, DepInfo> depsMap = new HashMap<>(classes.size());
|
List<DepInfo> deps = sumDependencies(classes);
|
||||||
Set<ClassNode> visited = new HashSet<>();
|
|
||||||
for (ClassNode classNode : classes) {
|
|
||||||
visited.clear();
|
|
||||||
sumDeps(classNode, depsMap, visited);
|
|
||||||
}
|
|
||||||
List<DepInfo> deps = new ArrayList<>(depsMap.values());
|
|
||||||
Collections.sort(deps);
|
|
||||||
|
|
||||||
Set<ClassNode> added = new HashSet<>(classes.size());
|
Set<ClassNode> added = new HashSet<>(classes.size());
|
||||||
Comparator<ClassNode> cmpDepSize = Comparator.comparingInt(c -> c.getDependencies().size());
|
Comparator<ClassNode> cmpDepSize = Comparator.comparingInt(ClassNode::getTotalDepsCount);
|
||||||
List<List<ClassNode>> result = new ArrayList<>();
|
List<List<ClassNode>> result = new ArrayList<>();
|
||||||
List<ClassNode> mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
List<ClassNode> mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
||||||
for (DepInfo depInfo : deps) {
|
for (DepInfo depInfo : deps) {
|
||||||
ClassNode cls = depInfo.getCls();
|
ClassNode cls = depInfo.getCls();
|
||||||
int depsSize = cls.getDependencies().size();
|
if (!added.add(cls)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int depsSize = cls.getTotalDepsCount();
|
||||||
if (depsSize == 0) {
|
if (depsSize == 0) {
|
||||||
// add classes without dependencies in merged batch
|
// add classes without dependencies in merged batch
|
||||||
mergedBatch.add(cls);
|
mergedBatch.add(cls);
|
||||||
added.add(cls);
|
|
||||||
if (mergedBatch.size() >= MERGED_BATCH_SIZE) {
|
if (mergedBatch.size() >= MERGED_BATCH_SIZE) {
|
||||||
result.add(mergedBatch);
|
result.add(mergedBatch);
|
||||||
mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
||||||
@@ -76,38 +78,34 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
|||||||
ClassNode topDep = dep.getTopParentClass();
|
ClassNode topDep = dep.getTopParentClass();
|
||||||
if (!added.contains(topDep)) {
|
if (!added.contains(topDep)) {
|
||||||
batch.add(topDep);
|
batch.add(topDep);
|
||||||
|
added.add(topDep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
batch.sort(cmpDepSize);
|
batch.sort(cmpDepSize);
|
||||||
batch.add(cls);
|
batch.add(cls);
|
||||||
added.addAll(batch);
|
|
||||||
result.add(batch);
|
result.add(batch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mergedBatch.size() > 0) {
|
if (mergedBatch.size() > 0) {
|
||||||
result.add(mergedBatch);
|
result.add(mergedBatch);
|
||||||
}
|
}
|
||||||
if (DUMP_STATS) {
|
if (DEBUG_BATCHES) {
|
||||||
dumpBatchesStats(classes, result, deps);
|
dumpBatchesStats(classes, result, deps);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int sumDeps(ClassNode cls, Map<ClassNode, DepInfo> depsMap, Set<ClassNode> visited) {
|
private static List<DepInfo> sumDependencies(List<ClassNode> classes) {
|
||||||
visited.add(cls);
|
List<DepInfo> deps = new ArrayList<>(classes.size());
|
||||||
DepInfo depInfo = depsMap.get(cls);
|
for (ClassNode cls : classes) {
|
||||||
if (depInfo != null) {
|
int count = 0;
|
||||||
return depInfo.getDepsCount();
|
for (ClassNode dep : cls.getDependencies()) {
|
||||||
}
|
count += 1 + dep.getTotalDepsCount();
|
||||||
List<ClassNode> deps = cls.getDependencies();
|
|
||||||
int count = deps.size();
|
|
||||||
for (ClassNode dep : deps) {
|
|
||||||
if (!visited.contains(dep)) {
|
|
||||||
count += sumDeps(dep, depsMap, visited);
|
|
||||||
}
|
}
|
||||||
|
deps.add(new DepInfo(cls, count));
|
||||||
}
|
}
|
||||||
depsMap.put(cls, new DepInfo(cls, count));
|
Collections.sort(deps);
|
||||||
return count;
|
return deps;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class DepInfo implements Comparable<DepInfo> {
|
private static final class DepInfo implements Comparable<DepInfo> {
|
||||||
@@ -129,19 +127,43 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(@NotNull DecompilerScheduler.DepInfo o) {
|
public int compareTo(@NotNull DecompilerScheduler.DepInfo o) {
|
||||||
return Integer.compare(depsCount, o.depsCount);
|
int deps = Integer.compare(depsCount, o.depsCount);
|
||||||
|
if (deps == 0) {
|
||||||
|
return cls.compareTo(o.cls);
|
||||||
|
}
|
||||||
|
return deps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return cls + ":" + depsCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<List<JavaClass>> buildFallback(List<JavaClass> classes) {
|
||||||
|
return classes.stream()
|
||||||
|
.sorted(Comparator.comparingInt(c -> c.getClassNode().getTotalDepsCount()))
|
||||||
|
.map(Collections::singletonList)
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dumpBatchesStats(List<ClassNode> classes, List<List<ClassNode>> result, List<DepInfo> deps) {
|
private void dumpBatchesStats(List<ClassNode> classes, List<List<ClassNode>> result, List<DepInfo> deps) {
|
||||||
double avg = result.stream().mapToInt(List::size).average().orElse(-1);
|
double avg = result.stream().mapToInt(List::size).average().orElse(-1);
|
||||||
int maxSingleDeps = classes.stream().mapToInt(c -> c.getDependencies().size()).max().orElse(-1);
|
int maxSingleDeps = classes.stream().mapToInt(ClassNode::getTotalDepsCount).max().orElse(-1);
|
||||||
int maxRecursiveDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1);
|
int maxSubDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1);
|
||||||
LOG.info("Batches stats:"
|
LOG.info("Batches stats:"
|
||||||
+ "\n input classes: " + classes.size()
|
+ "\n input classes: " + classes.size()
|
||||||
+ ",\n batches: " + result.size()
|
+ ",\n batches: " + result.size()
|
||||||
+ ",\n average batch size: " + avg
|
+ ",\n average batch size: " + String.format("%.2f", avg)
|
||||||
+ ",\n max single deps count: " + maxSingleDeps
|
+ ",\n max single deps count: " + maxSingleDeps
|
||||||
+ ",\n max recursive deps count: " + maxRecursiveDeps);
|
+ ",\n max sub deps count: " + maxSubDeps);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void check(List<List<JavaClass>> result, List<JavaClass> classes) {
|
||||||
|
int classInBatches = result.stream().mapToInt(List::size).sum();
|
||||||
|
if (classes.size() != classInBatches) {
|
||||||
|
throw new JadxRuntimeException(
|
||||||
|
"Incorrect number of classes in result batch: " + classInBatches + ", expected: " + classes.size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public class InsnUtils {
|
|||||||
return ((ConstClassNode) insn).getClsType();
|
return ((ConstClassNode) insn).getClsType();
|
||||||
case SGET:
|
case SGET:
|
||||||
FieldInfo f = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
FieldInfo f = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||||
FieldNode fieldNode = root.deepResolveField(f);
|
FieldNode fieldNode = root.resolveField(f);
|
||||||
if (fieldNode == null) {
|
if (fieldNode == null) {
|
||||||
LOG.warn("Field {} not found", f);
|
LOG.warn("Field {} not found", f);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import java.util.Collections;
|
|||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
|
||||||
|
|
||||||
public class ListUtils {
|
public class ListUtils {
|
||||||
|
|
||||||
public static <T> boolean isSingleElement(@Nullable List<T> list, T obj) {
|
public static <T> boolean isSingleElement(@Nullable List<T> list, T obj) {
|
||||||
@@ -48,7 +48,19 @@ public class ListUtils {
|
|||||||
return list.get(list.size() - 1);
|
return list.get(list.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<BlockNode> distinctList(List<BlockNode> list) {
|
public static <T extends Comparable<T>> List<T> distinctMergeSortedLists(List<T> first, List<T> second) {
|
||||||
|
if (first.isEmpty()) {
|
||||||
|
return second;
|
||||||
|
}
|
||||||
|
if (second.isEmpty()) {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
Set<T> set = new TreeSet<>(first);
|
||||||
|
set.addAll(second);
|
||||||
|
return new ArrayList<>(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> distinctList(List<T> list) {
|
||||||
return new ArrayList<>(new LinkedHashSet<>(list));
|
return new ArrayList<>(new LinkedHashSet<>(list));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +112,19 @@ public class ListUtils {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> filter(List<T> list, Predicate<T> filter) {
|
||||||
|
if (list == null || list.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<T> result = new ArrayList<>();
|
||||||
|
for (T element : list) {
|
||||||
|
if (filter.test(element)) {
|
||||||
|
result.add(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search exactly one element in list by filter
|
* Search exactly one element in list by filter
|
||||||
*
|
*
|
||||||
@@ -134,4 +159,16 @@ public class ListUtils {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> boolean anyMatch(List<T> list, Predicate<T> test) {
|
||||||
|
if (list == null || list.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (T element : list) {
|
||||||
|
if (test.test(element)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import java.io.OutputStream;
|
|||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,16 +33,19 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
|||||||
*/
|
*/
|
||||||
public class Res9patchStreamDecoder {
|
public class Res9patchStreamDecoder {
|
||||||
|
|
||||||
public void decode(InputStream in, OutputStream out) {
|
public boolean decode(InputStream in, OutputStream out) {
|
||||||
try {
|
try {
|
||||||
BufferedImage im = ImageIO.read(in);
|
BufferedImage im = ImageIO.read(in);
|
||||||
|
NinePatch np = getNinePatch(in);
|
||||||
|
if (np == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
int w = im.getWidth();
|
int w = im.getWidth();
|
||||||
int h = im.getHeight();
|
int h = im.getHeight();
|
||||||
|
|
||||||
BufferedImage im2 = new BufferedImage(w + 2, h + 2, BufferedImage.TYPE_INT_ARGB);
|
BufferedImage im2 = new BufferedImage(w + 2, h + 2, BufferedImage.TYPE_INT_ARGB);
|
||||||
im2.createGraphics().drawImage(im, 1, 1, w, h, null);
|
im2.createGraphics().drawImage(im, 1, 1, w, h, null);
|
||||||
|
|
||||||
NinePatch np = getNinePatch(in);
|
|
||||||
drawHLine(im2, h + 1, np.padLeft + 1, w - np.padRight);
|
drawHLine(im2, h + 1, np.padLeft + 1, w - np.padRight);
|
||||||
drawVLine(im2, w + 1, np.padTop + 1, h - np.padBottom);
|
drawVLine(im2, w + 1, np.padTop + 1, h - np.padBottom);
|
||||||
|
|
||||||
@@ -55,28 +60,32 @@ public class Res9patchStreamDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImageIO.write(im2, "png", out);
|
ImageIO.write(im2, "png", out);
|
||||||
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new JadxRuntimeException("9patch image decode error", e);
|
throw new JadxRuntimeException("9patch image decode error", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private NinePatch getNinePatch(InputStream in) throws IOException {
|
private NinePatch getNinePatch(InputStream in) throws IOException {
|
||||||
ExtDataInput di = new ExtDataInput(in);
|
ExtDataInput di = new ExtDataInput(in);
|
||||||
find9patchChunk(di);
|
if (!find9patchChunk(di)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return NinePatch.decode(di);
|
return NinePatch.decode(di);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void find9patchChunk(DataInput di) throws IOException {
|
private boolean find9patchChunk(DataInput di) throws IOException {
|
||||||
di.skipBytes(8);
|
di.skipBytes(8);
|
||||||
while (true) {
|
while (true) {
|
||||||
int size;
|
int size;
|
||||||
try {
|
try {
|
||||||
size = di.readInt();
|
size = di.readInt();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new JadxRuntimeException("Cant find nine patch chunk", ex);
|
return false;
|
||||||
}
|
}
|
||||||
if (di.readInt() == NP_CHUNK_TYPE) {
|
if (di.readInt() == NP_CHUNK_TYPE) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
di.skipBytes(size + 4);
|
di.skipBytes(size + 4);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,12 @@ import org.slf4j.LoggerFactory;
|
|||||||
import jadx.api.plugins.input.data.annotations.EncodedType;
|
import jadx.api.plugins.input.data.annotations.EncodedType;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
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.core.deobf.ClsAliasPair;
|
||||||
|
import jadx.core.deobf.NameMapper;
|
||||||
|
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
// TODO: parse data from d1 (protobuf encoded) to get original method names and other useful info
|
// TODO: parse data from d1 (protobuf encoded) to get original method names and other useful info
|
||||||
public class KotlinMetadataUtils {
|
public class KotlinMetadataUtils {
|
||||||
@@ -18,13 +22,12 @@ public class KotlinMetadataUtils {
|
|||||||
|
|
||||||
private static final String KOTLIN_METADATA_ANNOTATION = "Lkotlin/Metadata;";
|
private static final String KOTLIN_METADATA_ANNOTATION = "Lkotlin/Metadata;";
|
||||||
private static final String KOTLIN_METADATA_D2_PARAMETER = "d2";
|
private static final String KOTLIN_METADATA_D2_PARAMETER = "d2";
|
||||||
private static final String KOTLIN_METADATA_CLASSNAME_REGEX = "(L.*;)";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to get class info from Kotlin Metadata annotation
|
* Try to get class info from Kotlin Metadata annotation
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static ClassInfo getClassName(ClassNode cls) {
|
public static ClsAliasPair getClassAlias(ClassNode cls) {
|
||||||
IAnnotation metadataAnnotation = cls.getAnnotation(KOTLIN_METADATA_ANNOTATION);
|
IAnnotation metadataAnnotation = cls.getAnnotation(KOTLIN_METADATA_ANNOTATION);
|
||||||
List<EncodedValue> d2Param = getParamAsList(metadataAnnotation, KOTLIN_METADATA_D2_PARAMETER);
|
List<EncodedValue> d2Param = getParamAsList(metadataAnnotation, KOTLIN_METADATA_D2_PARAMETER);
|
||||||
if (d2Param == null || d2Param.isEmpty()) {
|
if (d2Param == null || d2Param.isEmpty()) {
|
||||||
@@ -36,8 +39,14 @@ public class KotlinMetadataUtils {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
String rawClassName = ((String) firstValue.getValue()).trim();
|
String rawClassName = ((String) firstValue.getValue()).trim();
|
||||||
if (rawClassName.matches(KOTLIN_METADATA_CLASSNAME_REGEX)) {
|
if (rawClassName.isEmpty()) {
|
||||||
return ClassInfo.fromName(cls.root(), rawClassName);
|
return null;
|
||||||
|
}
|
||||||
|
String clsName = Utils.cleanObjectName(rawClassName);
|
||||||
|
ClsAliasPair alias = splitAndCheckClsName(cls, clsName);
|
||||||
|
if (alias != null) {
|
||||||
|
RenameReasonAttr.forNode(cls).append("from Kotlin metadata");
|
||||||
|
return alias;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to parse kotlin metadata", e);
|
LOG.error("Failed to parse kotlin metadata", e);
|
||||||
@@ -45,6 +54,54 @@ public class KotlinMetadataUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't use ClassInfo facility to not pollute class into cache
|
||||||
|
private static ClsAliasPair splitAndCheckClsName(ClassNode originCls, String fullClsName) {
|
||||||
|
if (!NameMapper.isValidFullIdentifier(fullClsName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String pkg;
|
||||||
|
String name;
|
||||||
|
int dot = fullClsName.lastIndexOf('.');
|
||||||
|
if (dot == -1) {
|
||||||
|
pkg = "";
|
||||||
|
name = fullClsName;
|
||||||
|
} else {
|
||||||
|
pkg = fullClsName.substring(0, dot);
|
||||||
|
name = fullClsName.substring(dot + 1);
|
||||||
|
}
|
||||||
|
ClassInfo originClsInfo = originCls.getClassInfo();
|
||||||
|
String originName = originClsInfo.getShortName();
|
||||||
|
if (originName.equals(name)
|
||||||
|
|| name.contains("$")
|
||||||
|
|| !NameMapper.isValidIdentifier(name)
|
||||||
|
|| countPkgParts(originClsInfo.getPackage()) != countPkgParts(pkg)
|
||||||
|
|| pkg.startsWith("java.")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ClassNode newClsNode = originCls.root().resolveClass(fullClsName);
|
||||||
|
if (newClsNode != null) {
|
||||||
|
// class with alias name already exist
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new ClsAliasPair(pkg, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int countPkgParts(String pkg) {
|
||||||
|
if (pkg.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int count = 1;
|
||||||
|
int pos = 0;
|
||||||
|
while (true) {
|
||||||
|
pos = pkg.indexOf('.', pos);
|
||||||
|
if (pos == -1) {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static List<EncodedValue> getParamAsList(IAnnotation annotation, String paramName) {
|
private static List<EncodedValue> getParamAsList(IAnnotation annotation, String paramName) {
|
||||||
if (annotation == null) {
|
if (annotation == null) {
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
package jadx.core.xmlgen;
|
package jadx.core.xmlgen;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
|
||||||
@@ -171,19 +175,20 @@ public class ManifestAttributes {
|
|||||||
if (attr.getType() == MAttrType.ENUM) {
|
if (attr.getType() == MAttrType.ENUM) {
|
||||||
return attr.getValues().get(value);
|
return attr.getValues().get(value);
|
||||||
} else if (attr.getType() == MAttrType.FLAG) {
|
} else if (attr.getType() == MAttrType.FLAG) {
|
||||||
StringBuilder sb = new StringBuilder();
|
List<String> flagList = new LinkedList<>();
|
||||||
for (Map.Entry<Long, String> entry : attr.getValues().entrySet()) {
|
List<Long> attrKeys = new ArrayList<>(attr.getValues().keySet());
|
||||||
long key = entry.getKey();
|
attrKeys.sort((a, b) -> Long.compare(b, a)); // sort descending
|
||||||
|
for (Long key : attrKeys) {
|
||||||
|
String attrValue = attr.getValues().get(key);
|
||||||
if (value == key) {
|
if (value == key) {
|
||||||
sb = new StringBuilder(entry.getValue() + '|');
|
flagList.add(attrValue);
|
||||||
break;
|
break;
|
||||||
} else if ((key != 0) && ((value & key) == key)) {
|
} else if ((key != 0) && ((value & key) == key)) {
|
||||||
sb.append(entry.getValue()).append('|');
|
flagList.add(attrValue);
|
||||||
|
value ^= key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sb.length() != 0) {
|
return flagList.stream().collect(Collectors.joining("|"));
|
||||||
return sb.deleteCharAt(sb.length() - 1).toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,33 @@
|
|||||||
package jadx.core.xmlgen;
|
package jadx.core.xmlgen;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* modifies android:name attributes and xml tags which are old class names
|
* Modifies android:name attributes and xml tags which were changed during deobfuscation
|
||||||
* but were changed during deobfuscation
|
|
||||||
*/
|
*/
|
||||||
public class XmlDeobf {
|
public class XmlDeobf {
|
||||||
private static final Map<String, String> DEOBF_MAP = new HashMap<>();
|
|
||||||
|
|
||||||
private XmlDeobf() {
|
private XmlDeobf() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static String deobfClassName(RootNode rootNode, String potencialClassName, String packageName) {
|
public static String deobfClassName(RootNode root, String potentialClassName, String packageName) {
|
||||||
potencialClassName = potencialClassName.replace('$', '.');
|
if (potentialClassName.indexOf('.') == -1) {
|
||||||
if (packageName != null && potencialClassName.startsWith(".")) {
|
return null;
|
||||||
potencialClassName = packageName + potencialClassName;
|
|
||||||
}
|
}
|
||||||
return getNewClassName(rootNode, potencialClassName);
|
if (packageName != null && potentialClassName.startsWith(".")) {
|
||||||
}
|
potentialClassName = packageName + potentialClassName;
|
||||||
|
|
||||||
private static String getNewClassName(RootNode rootNode, String old) {
|
|
||||||
if (DEOBF_MAP.isEmpty()) {
|
|
||||||
for (ClassNode classNode : rootNode.getClasses(true)) {
|
|
||||||
ClassInfo classInfo = classNode.getClassInfo();
|
|
||||||
if (classInfo.hasAlias()) {
|
|
||||||
String oldName = classInfo.getFullName();
|
|
||||||
String newName = classInfo.getAliasFullName();
|
|
||||||
if (!oldName.equals(newName)) {
|
|
||||||
DEOBF_MAP.put(oldName, newName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return DEOBF_MAP.get(old);
|
ArgType clsType = ArgType.object(potentialClassName);
|
||||||
|
ClassInfo classInfo = root.getInfoStorage().getCls(clsType);
|
||||||
|
if (classInfo == null) {
|
||||||
|
// unknown class reference
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return classInfo.getAliasFullName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,469 @@
|
|||||||
|
# All tld domains with 3 or less characters
|
||||||
|
# Created from https://data.iana.org/TLD/tlds-alpha-by-domain.txt version 2022020500
|
||||||
|
aaa
|
||||||
|
abb
|
||||||
|
abc
|
||||||
|
ac
|
||||||
|
aco
|
||||||
|
ad
|
||||||
|
ads
|
||||||
|
ae
|
||||||
|
aeg
|
||||||
|
af
|
||||||
|
afl
|
||||||
|
ag
|
||||||
|
ai
|
||||||
|
aig
|
||||||
|
al
|
||||||
|
am
|
||||||
|
anz
|
||||||
|
ao
|
||||||
|
aol
|
||||||
|
app
|
||||||
|
aq
|
||||||
|
ar
|
||||||
|
art
|
||||||
|
as
|
||||||
|
at
|
||||||
|
au
|
||||||
|
aw
|
||||||
|
aws
|
||||||
|
ax
|
||||||
|
axa
|
||||||
|
az
|
||||||
|
ba
|
||||||
|
bar
|
||||||
|
bb
|
||||||
|
bbc
|
||||||
|
bbt
|
||||||
|
bcg
|
||||||
|
bcn
|
||||||
|
bd
|
||||||
|
be
|
||||||
|
bet
|
||||||
|
bf
|
||||||
|
bg
|
||||||
|
bh
|
||||||
|
bi
|
||||||
|
bid
|
||||||
|
bio
|
||||||
|
biz
|
||||||
|
bj
|
||||||
|
bm
|
||||||
|
bms
|
||||||
|
bmw
|
||||||
|
bn
|
||||||
|
bo
|
||||||
|
bom
|
||||||
|
boo
|
||||||
|
bot
|
||||||
|
box
|
||||||
|
br
|
||||||
|
bs
|
||||||
|
bt
|
||||||
|
buy
|
||||||
|
bv
|
||||||
|
bw
|
||||||
|
by
|
||||||
|
bz
|
||||||
|
bzh
|
||||||
|
ca
|
||||||
|
cab
|
||||||
|
cal
|
||||||
|
cam
|
||||||
|
car
|
||||||
|
cat
|
||||||
|
cba
|
||||||
|
cbn
|
||||||
|
cbs
|
||||||
|
cc
|
||||||
|
cd
|
||||||
|
ceo
|
||||||
|
cf
|
||||||
|
cfa
|
||||||
|
cfd
|
||||||
|
cg
|
||||||
|
ch
|
||||||
|
ci
|
||||||
|
ck
|
||||||
|
cl
|
||||||
|
cm
|
||||||
|
cn
|
||||||
|
co
|
||||||
|
com
|
||||||
|
cpa
|
||||||
|
cr
|
||||||
|
crs
|
||||||
|
cu
|
||||||
|
cv
|
||||||
|
cw
|
||||||
|
cx
|
||||||
|
cy
|
||||||
|
cz
|
||||||
|
dad
|
||||||
|
day
|
||||||
|
dds
|
||||||
|
de
|
||||||
|
dev
|
||||||
|
dhl
|
||||||
|
diy
|
||||||
|
dj
|
||||||
|
dk
|
||||||
|
dm
|
||||||
|
dnp
|
||||||
|
do
|
||||||
|
dog
|
||||||
|
dot
|
||||||
|
dtv
|
||||||
|
dvr
|
||||||
|
dz
|
||||||
|
eat
|
||||||
|
ec
|
||||||
|
eco
|
||||||
|
edu
|
||||||
|
ee
|
||||||
|
eg
|
||||||
|
er
|
||||||
|
es
|
||||||
|
esq
|
||||||
|
et
|
||||||
|
eu
|
||||||
|
eus
|
||||||
|
fan
|
||||||
|
fi
|
||||||
|
fit
|
||||||
|
fj
|
||||||
|
fk
|
||||||
|
fly
|
||||||
|
fm
|
||||||
|
fo
|
||||||
|
foo
|
||||||
|
fox
|
||||||
|
fr
|
||||||
|
frl
|
||||||
|
ftr
|
||||||
|
fun
|
||||||
|
fyi
|
||||||
|
ga
|
||||||
|
gal
|
||||||
|
gap
|
||||||
|
gay
|
||||||
|
gb
|
||||||
|
gd
|
||||||
|
gdn
|
||||||
|
ge
|
||||||
|
gea
|
||||||
|
gf
|
||||||
|
gg
|
||||||
|
gh
|
||||||
|
gi
|
||||||
|
gl
|
||||||
|
gle
|
||||||
|
gm
|
||||||
|
gmo
|
||||||
|
gmx
|
||||||
|
gn
|
||||||
|
goo
|
||||||
|
gop
|
||||||
|
got
|
||||||
|
gov
|
||||||
|
gp
|
||||||
|
gq
|
||||||
|
gr
|
||||||
|
gs
|
||||||
|
gt
|
||||||
|
gu
|
||||||
|
gw
|
||||||
|
gy
|
||||||
|
hbo
|
||||||
|
hiv
|
||||||
|
hk
|
||||||
|
hkt
|
||||||
|
hm
|
||||||
|
hn
|
||||||
|
hot
|
||||||
|
how
|
||||||
|
hr
|
||||||
|
ht
|
||||||
|
hu
|
||||||
|
ibm
|
||||||
|
ice
|
||||||
|
icu
|
||||||
|
id
|
||||||
|
ie
|
||||||
|
ifm
|
||||||
|
il
|
||||||
|
im
|
||||||
|
in
|
||||||
|
inc
|
||||||
|
ing
|
||||||
|
ink
|
||||||
|
int
|
||||||
|
io
|
||||||
|
iq
|
||||||
|
ir
|
||||||
|
is
|
||||||
|
ist
|
||||||
|
it
|
||||||
|
itv
|
||||||
|
jcb
|
||||||
|
je
|
||||||
|
jio
|
||||||
|
jll
|
||||||
|
jm
|
||||||
|
jmp
|
||||||
|
jnj
|
||||||
|
jo
|
||||||
|
jot
|
||||||
|
joy
|
||||||
|
jp
|
||||||
|
ke
|
||||||
|
kfh
|
||||||
|
kg
|
||||||
|
kh
|
||||||
|
ki
|
||||||
|
kia
|
||||||
|
kim
|
||||||
|
km
|
||||||
|
kn
|
||||||
|
kp
|
||||||
|
kpn
|
||||||
|
kr
|
||||||
|
krd
|
||||||
|
kw
|
||||||
|
ky
|
||||||
|
kz
|
||||||
|
la
|
||||||
|
lat
|
||||||
|
law
|
||||||
|
lb
|
||||||
|
lc
|
||||||
|
lds
|
||||||
|
li
|
||||||
|
lk
|
||||||
|
llc
|
||||||
|
llp
|
||||||
|
lol
|
||||||
|
lpl
|
||||||
|
lr
|
||||||
|
ls
|
||||||
|
lt
|
||||||
|
ltd
|
||||||
|
lu
|
||||||
|
lv
|
||||||
|
ly
|
||||||
|
ma
|
||||||
|
man
|
||||||
|
map
|
||||||
|
mba
|
||||||
|
mc
|
||||||
|
md
|
||||||
|
me
|
||||||
|
med
|
||||||
|
men
|
||||||
|
mg
|
||||||
|
mh
|
||||||
|
mil
|
||||||
|
mit
|
||||||
|
mk
|
||||||
|
ml
|
||||||
|
mlb
|
||||||
|
mls
|
||||||
|
mm
|
||||||
|
mma
|
||||||
|
mn
|
||||||
|
mo
|
||||||
|
moe
|
||||||
|
moi
|
||||||
|
mom
|
||||||
|
mov
|
||||||
|
mp
|
||||||
|
mq
|
||||||
|
mr
|
||||||
|
ms
|
||||||
|
msd
|
||||||
|
mt
|
||||||
|
mtn
|
||||||
|
mtr
|
||||||
|
mu
|
||||||
|
mv
|
||||||
|
mw
|
||||||
|
mx
|
||||||
|
my
|
||||||
|
mz
|
||||||
|
na
|
||||||
|
nab
|
||||||
|
nba
|
||||||
|
nc
|
||||||
|
ne
|
||||||
|
nec
|
||||||
|
net
|
||||||
|
new
|
||||||
|
nf
|
||||||
|
nfl
|
||||||
|
ng
|
||||||
|
ngo
|
||||||
|
nhk
|
||||||
|
ni
|
||||||
|
nl
|
||||||
|
no
|
||||||
|
now
|
||||||
|
np
|
||||||
|
nr
|
||||||
|
nra
|
||||||
|
nrw
|
||||||
|
ntt
|
||||||
|
nu
|
||||||
|
nyc
|
||||||
|
nz
|
||||||
|
obi
|
||||||
|
om
|
||||||
|
one
|
||||||
|
ong
|
||||||
|
onl
|
||||||
|
ooo
|
||||||
|
org
|
||||||
|
ott
|
||||||
|
ovh
|
||||||
|
pa
|
||||||
|
pay
|
||||||
|
pe
|
||||||
|
pet
|
||||||
|
pf
|
||||||
|
pg
|
||||||
|
ph
|
||||||
|
phd
|
||||||
|
pid
|
||||||
|
pin
|
||||||
|
pk
|
||||||
|
pl
|
||||||
|
pm
|
||||||
|
pn
|
||||||
|
pnc
|
||||||
|
pr
|
||||||
|
pro
|
||||||
|
pru
|
||||||
|
ps
|
||||||
|
pt
|
||||||
|
pub
|
||||||
|
pw
|
||||||
|
pwc
|
||||||
|
py
|
||||||
|
qa
|
||||||
|
re
|
||||||
|
red
|
||||||
|
ren
|
||||||
|
ril
|
||||||
|
rio
|
||||||
|
rip
|
||||||
|
ro
|
||||||
|
rs
|
||||||
|
ru
|
||||||
|
run
|
||||||
|
rw
|
||||||
|
rwe
|
||||||
|
sa
|
||||||
|
sap
|
||||||
|
sas
|
||||||
|
sb
|
||||||
|
sbi
|
||||||
|
sbs
|
||||||
|
sc
|
||||||
|
sca
|
||||||
|
scb
|
||||||
|
sd
|
||||||
|
se
|
||||||
|
ses
|
||||||
|
sew
|
||||||
|
sex
|
||||||
|
sfr
|
||||||
|
sg
|
||||||
|
sh
|
||||||
|
si
|
||||||
|
sj
|
||||||
|
sk
|
||||||
|
ski
|
||||||
|
sky
|
||||||
|
sl
|
||||||
|
sm
|
||||||
|
sn
|
||||||
|
so
|
||||||
|
soy
|
||||||
|
spa
|
||||||
|
sr
|
||||||
|
srl
|
||||||
|
ss
|
||||||
|
st
|
||||||
|
stc
|
||||||
|
su
|
||||||
|
sv
|
||||||
|
sx
|
||||||
|
sy
|
||||||
|
sz
|
||||||
|
tab
|
||||||
|
tax
|
||||||
|
tc
|
||||||
|
tci
|
||||||
|
td
|
||||||
|
tdk
|
||||||
|
tel
|
||||||
|
tf
|
||||||
|
tg
|
||||||
|
th
|
||||||
|
thd
|
||||||
|
tj
|
||||||
|
tjx
|
||||||
|
tk
|
||||||
|
tl
|
||||||
|
tm
|
||||||
|
tn
|
||||||
|
to
|
||||||
|
top
|
||||||
|
tr
|
||||||
|
trv
|
||||||
|
tt
|
||||||
|
tui
|
||||||
|
tv
|
||||||
|
tvs
|
||||||
|
tw
|
||||||
|
tz
|
||||||
|
ua
|
||||||
|
ubs
|
||||||
|
ug
|
||||||
|
uk
|
||||||
|
uno
|
||||||
|
uol
|
||||||
|
ups
|
||||||
|
us
|
||||||
|
uy
|
||||||
|
uz
|
||||||
|
va
|
||||||
|
vc
|
||||||
|
ve
|
||||||
|
vet
|
||||||
|
vg
|
||||||
|
vi
|
||||||
|
vig
|
||||||
|
vin
|
||||||
|
vip
|
||||||
|
vn
|
||||||
|
vu
|
||||||
|
wed
|
||||||
|
wf
|
||||||
|
win
|
||||||
|
wme
|
||||||
|
wow
|
||||||
|
ws
|
||||||
|
wtc
|
||||||
|
wtf
|
||||||
|
xin
|
||||||
|
xxx
|
||||||
|
xyz
|
||||||
|
ye
|
||||||
|
you
|
||||||
|
yt
|
||||||
|
yun
|
||||||
|
za
|
||||||
|
zip
|
||||||
|
zm
|
||||||
|
zw
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.xmlgen.BinaryXMLParser;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class JadxDecompilerTestUtils {
|
||||||
|
public static JadxDecompiler getMockDecompiler() {
|
||||||
|
JadxDecompiler decompiler = mock(JadxDecompiler.class);
|
||||||
|
RootNode rootNode = new RootNode(new JadxArgs());
|
||||||
|
when(decompiler.getRoot()).thenReturn(rootNode);
|
||||||
|
when(decompiler.getBinaryXmlParser()).thenReturn(new BinaryXMLParser(rootNode));
|
||||||
|
return decompiler;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package jadx.tests.api;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.api.JadxDecompilerTestUtils;
|
||||||
|
import jadx.api.ResourceFile;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.export.ExportGradleProject;
|
||||||
|
import jadx.core.xmlgen.ResContainer;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.fail;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public abstract class ExportGradleTest {
|
||||||
|
|
||||||
|
private static final String MANIFEST_TESTS_DIR = "src/test/manifest";
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
private File exportDir;
|
||||||
|
|
||||||
|
protected ResContainer createResourceContainer(String filename) {
|
||||||
|
final ResContainer container = mock(ResContainer.class);
|
||||||
|
ICodeInfo codeInfo = mock(ICodeInfo.class);
|
||||||
|
when(codeInfo.getCodeStr()).thenReturn(loadFileContent(new File(MANIFEST_TESTS_DIR, filename)));
|
||||||
|
when(container.getText()).thenReturn(codeInfo);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String loadFileContent(File filePath) {
|
||||||
|
StringBuilder contentBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
try (Stream<String> stream = Files.lines(filePath.toPath(), StandardCharsets.UTF_8)) {
|
||||||
|
stream.forEach(s -> contentBuilder.append(s).append("\n"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
fail("Loading file failed: %s", e.getMessage());
|
||||||
|
}
|
||||||
|
return contentBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void exportGradle(String manifestFilename, String stringsFileName) {
|
||||||
|
final JadxDecompiler decompiler = JadxDecompilerTestUtils.getMockDecompiler();
|
||||||
|
ResourceFile androidManifest = mock(ResourceFile.class);
|
||||||
|
final ResContainer androidManifestContainer = createResourceContainer(manifestFilename);
|
||||||
|
when(androidManifest.loadContent()).thenReturn(androidManifestContainer);
|
||||||
|
final ResContainer strings = createResourceContainer(stringsFileName);
|
||||||
|
final RootNode root = decompiler.getRoot();
|
||||||
|
|
||||||
|
final ExportGradleProject export =
|
||||||
|
new ExportGradleProject(root, exportDir, androidManifest, strings);
|
||||||
|
export.init();
|
||||||
|
assertThat(export.getSrcOutDir().exists());
|
||||||
|
assertThat(export.getResOutDir().exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getAppGradleBuild() {
|
||||||
|
File appBuildGradle = new File(exportDir, "app/build.gradle");
|
||||||
|
assertThat(appBuildGradle.exists());
|
||||||
|
return loadFileContent(appBuildGradle);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
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;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
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;
|
||||||
@@ -16,8 +19,11 @@ import java.util.concurrent.Executors;
|
|||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
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;
|
||||||
@@ -32,6 +38,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;
|
||||||
@@ -46,9 +54,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;
|
||||||
@@ -56,12 +64,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 {
|
||||||
@@ -91,9 +99,9 @@ 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;
|
||||||
|
|
||||||
protected Map<Integer, String> resMap = Collections.emptyMap();
|
protected Map<Integer, String> resMap = Collections.emptyMap();
|
||||||
|
|
||||||
@@ -101,9 +109,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
|
||||||
@@ -114,10 +124,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));
|
||||||
@@ -126,13 +137,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,8 +171,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) {
|
||||||
@@ -162,12 +195,30 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public ClassNode searchCls(List<ClassNode> list, String clsName) {
|
public ClassNode searchCls(List<ClassNode> list, String clsName) {
|
||||||
for (ClassNode cls : list) {
|
for (ClassNode cls : list) {
|
||||||
@@ -236,7 +287,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +372,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)) {
|
||||||
@@ -338,16 +389,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;
|
||||||
@@ -369,10 +424,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,8 +451,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);
|
||||||
@@ -414,66 +470,87 @@ 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);
|
||||||
// remove classes which are parents for test class
|
if (saveTestJar) {
|
||||||
String clsName = clsFullName.substring(clsFullName.lastIndexOf('.') + 1);
|
saveToJar(files, outTmp);
|
||||||
files.removeIf(next -> !next.getName().contains(clsName));
|
}
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
private void saveToJar(List<File> files, Path baseDir) throws IOException {
|
||||||
protected static String removeLineComments(ClassNode cls) {
|
Path jarFile = Files.createTempFile("tests-" + getTestName() + '-', ".jar");
|
||||||
String code = cls.getCode().getCodeStr().replaceAll("\\W*//.*", "");
|
try (JarOutputStream jar = new JarOutputStream(Files.newOutputStream(jarFile))) {
|
||||||
System.out.println(code);
|
for (File file : files) {
|
||||||
return code;
|
Path fullPath = file.toPath();
|
||||||
|
Path relativePath = baseDir.relativize(fullPath);
|
||||||
|
JarEntry entry = new JarEntry(relativePath.toString());
|
||||||
|
jar.putNextEntry(entry);
|
||||||
|
jar.write(Files.readAllBytes(fullPath));
|
||||||
|
jar.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.info("Test jar saved to: {}", jarFile.toAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
public JadxArgs getArgs() {
|
public JadxArgs getArgs() {
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -483,16 +560,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() {
|
||||||
@@ -506,7 +584,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);
|
||||||
}
|
}
|
||||||
@@ -532,26 +610,26 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use only for debug purpose
|
public void keepParentClassOnInput() {
|
||||||
@Deprecated
|
this.removeParentClassOnInput = false;
|
||||||
protected void outputCFG() {
|
|
||||||
this.args.setCfgOutput(true);
|
|
||||||
this.args.setRawCFGOutput(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use only for debug purpose
|
// Use only for debug purpose
|
||||||
@Deprecated
|
|
||||||
protected void printDisassemble() {
|
protected void printDisassemble() {
|
||||||
this.printDisassemble = true;
|
this.printDisassemble = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use only for debug purpose
|
// Use only for debug purpose
|
||||||
@Deprecated
|
protected void saveTestJar() {
|
||||||
protected void outputRawCFG() {
|
this.saveTestJar = true;
|
||||||
this.args.setRawCFGOutput(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;
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user