Compare commits
51 Commits
issue-1973
...
v1.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
| f2ea6415c9 | |||
| bc70f8eabb | |||
| be25cbf8c2 | |||
| f9c0cad146 | |||
| b356ff76e1 | |||
| ec9244a635 | |||
| a5bd64461d | |||
| 54bf79ccc5 | |||
| 6182332eef | |||
| 37b57096ec | |||
| 6aab8fabc9 | |||
| 665c1e57d2 | |||
| 6e8affcbdc | |||
| 41d6b0018e | |||
| dbadbb01fc | |||
| 0f52077c5c | |||
| ea861829c7 | |||
| c1de235289 | |||
| 8f969d4e89 | |||
| 0c1f830f94 | |||
| 43c082e4da | |||
| ecdc4e6757 | |||
| b865c9c687 | |||
| 6b4976c593 | |||
| 2807dc5090 | |||
| 463d2b90fa | |||
| bff00d101f | |||
| 1290ef63a2 | |||
| 49d2b34d84 | |||
| eecdfae73f | |||
| 8760b4ddde | |||
| 3599b248a4 | |||
| 2fdd496518 | |||
| 278e3c2d47 | |||
| 881a716b8e | |||
| a73c9e90fc | |||
| 56749b2afb | |||
| d7ec35791b | |||
| d51362ed50 | |||
| 5c0c1daa71 | |||
| 603ea3989a | |||
| 018ff98df7 | |||
| 5fbabdefca | |||
| 13607fc8b6 | |||
| 0c33d723c8 | |||
| 0143423dc9 | |||
| 21b1452485 | |||
| ecb8abb98e | |||
| a3a4fabd5a | |||
| edf6ce273c | |||
| 1bb956a8b0 |
@@ -12,4 +12,4 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: gradle/wrapper-validation-action@v1
|
- uses: gradle/actions/wrapper-validation@v3
|
||||||
|
|||||||
@@ -8,16 +8,19 @@
|
|||||||

|

|
||||||

|

|
||||||
[](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
|
[](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
|
> [!WARNING]
|
||||||
|
> Please note that in most cases **jadx** can't decompile all 100% of the code, so errors will occur.<br />
|
||||||
|
> 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 code from APK, dex, aar, aab and zip files
|
||||||
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
|
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
|
||||||
- deobfuscator included
|
- deobfuscator included
|
||||||
|
|
||||||
@@ -79,7 +82,7 @@ and also packed to `build/jadx-<version>.zip`
|
|||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
```
|
```
|
||||||
jadx[-gui] [command] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk)
|
jadx[-gui] [command] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .jadx.kts)
|
||||||
commands (use '<command> --help' for command options):
|
commands (use '<command> --help' for command options):
|
||||||
plugins - manage jadx plugins
|
plugins - manage jadx plugins
|
||||||
|
|
||||||
@@ -171,10 +174,11 @@ Plugin options (-P<name>=<value>):
|
|||||||
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
|
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
|
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
|
||||||
4) rename-mappings: various mappings support
|
4) rename-mappings: various mappings support
|
||||||
- rename-mappings.format - mapping format, values: [auto, TINY, TINY_2, ENIGMA, ENIGMA_DIR, MCP, SRG, TSRG, TSRG2, PROGUARD], default: auto
|
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, SRG_FILE, XSRG_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, PROGUARD_FILE], default: AUTO
|
||||||
- rename-mappings.invert - invert mapping, values: [yes, no], default: no
|
- rename-mappings.invert - invert mapping on load, values: [yes, no], default: no
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
|
JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files
|
||||||
JADX_DISABLE_ZIP_SECURITY - set to 'true' to disable all security checks for zip files
|
JADX_DISABLE_ZIP_SECURITY - set to 'true' to disable all security checks for zip files
|
||||||
JADX_ZIP_MAX_ENTRIES_COUNT - maximum allowed number of entries in zip files (default: 100 000)
|
JADX_ZIP_MAX_ENTRIES_COUNT - maximum allowed number of entries in zip files (default: 100 000)
|
||||||
JADX_TMP_DIR - custom temp directory, using system by default
|
JADX_TMP_DIR - custom temp directory, using system by default
|
||||||
@@ -186,7 +190,7 @@ Examples:
|
|||||||
jadx --log-level ERROR app.apk
|
jadx --log-level ERROR app.apk
|
||||||
jadx -Pdex-input.verify-checksum=no 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 work in jadx-gui running from command line and override options from preferences' dialog
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
Please check wiki page [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A)
|
Please check wiki page [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
|
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23")
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ group = "io.github.skylot"
|
|||||||
version = jadxVersion
|
version = jadxVersion
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.slf4j:slf4j-api:2.0.11")
|
implementation("org.slf4j:slf4j-api:2.0.13")
|
||||||
compileOnly("org.jetbrains:annotations:24.1.0")
|
compileOnly("org.jetbrains:annotations:24.1.0")
|
||||||
|
|
||||||
testImplementation("ch.qos.logback:logback-classic:1.4.14")
|
testImplementation("ch.qos.logback:logback-classic:1.5.6")
|
||||||
testImplementation("org.hamcrest:hamcrest-library:2.2")
|
testImplementation("org.hamcrest:hamcrest-library:2.2")
|
||||||
testImplementation("org.mockito:mockito-core:5.10.0")
|
testImplementation("org.mockito:mockito-core:5.11.0")
|
||||||
testImplementation("org.assertj:assertj-core:3.25.2")
|
testImplementation("org.assertj:assertj-core:3.25.3")
|
||||||
|
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter:5.10.1")
|
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
|
||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
|
||||||
testCompileOnly("org.jetbrains:annotations:24.1.0")
|
testCompileOnly("org.jetbrains:annotations:24.1.0")
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026
|
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
Vendored
+10
-10
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
|
|||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ dependencies {
|
|||||||
runtimeOnly(project(":jadx-plugins:jadx-xapk-input"))
|
runtimeOnly(project(":jadx-plugins:jadx-xapk-input"))
|
||||||
|
|
||||||
implementation("org.jcommander:jcommander:1.83")
|
implementation("org.jcommander:jcommander:1.83")
|
||||||
implementation("ch.qos.logback:logback-classic:1.4.14")
|
implementation("ch.qos.logback:logback-classic:1.5.6")
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import com.beust.jcommander.ParameterDescription;
|
|||||||
import com.beust.jcommander.ParameterException;
|
import com.beust.jcommander.ParameterException;
|
||||||
import com.beust.jcommander.Parameterized;
|
import com.beust.jcommander.Parameterized;
|
||||||
|
|
||||||
import jadx.api.JadxArgs;
|
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.plugins.JadxPluginInfo;
|
import jadx.api.plugins.JadxPluginInfo;
|
||||||
import jadx.api.plugins.options.JadxPluginOptions;
|
import jadx.api.plugins.options.JadxPluginOptions;
|
||||||
@@ -109,6 +108,7 @@ public class JCommanderWrapper<T> {
|
|||||||
out.println(appendPluginOptions(maxNamesLen));
|
out.println(appendPluginOptions(maxNamesLen));
|
||||||
out.println();
|
out.println();
|
||||||
out.println("Environment variables:");
|
out.println("Environment variables:");
|
||||||
|
out.println(" JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files");
|
||||||
out.println(" JADX_DISABLE_ZIP_SECURITY - set to 'true' to disable all security checks for zip files");
|
out.println(" JADX_DISABLE_ZIP_SECURITY - set to 'true' to disable all security checks for zip files");
|
||||||
out.println(" JADX_ZIP_MAX_ENTRIES_COUNT - maximum allowed number of entries in zip files (default: 100 000)");
|
out.println(" JADX_ZIP_MAX_ENTRIES_COUNT - maximum allowed number of entries in zip files (default: 100 000)");
|
||||||
out.println(" JADX_TMP_DIR - custom temp directory, using system by default");
|
out.println(" JADX_TMP_DIR - custom temp directory, using system by default");
|
||||||
@@ -165,7 +165,7 @@ public class JCommanderWrapper<T> {
|
|||||||
opt.append("- ").append(description);
|
opt.append("- ").append(description);
|
||||||
}
|
}
|
||||||
if (addDefaults) {
|
if (addDefaults) {
|
||||||
String defaultValue = getDefaultValue(args, f, opt);
|
String defaultValue = getDefaultValue(args, f);
|
||||||
if (defaultValue != null && !description.contains("(default)")) {
|
if (defaultValue != null && !description.contains("(default)")) {
|
||||||
opt.append(", default: ").append(defaultValue);
|
opt.append(", default: ").append(defaultValue);
|
||||||
}
|
}
|
||||||
@@ -188,7 +188,7 @@ public class JCommanderWrapper<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String getDefaultValue(Object args, Field f, StringBuilder opt) {
|
private static String getDefaultValue(Object args, Field f) {
|
||||||
try {
|
try {
|
||||||
Class<?> fieldType = f.getType();
|
Class<?> fieldType = f.getType();
|
||||||
if (fieldType == int.class) {
|
if (fieldType == int.class) {
|
||||||
@@ -219,7 +219,7 @@ public class JCommanderWrapper<T> {
|
|||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
int k = 1;
|
int k = 1;
|
||||||
// load and init all options plugins to print all options
|
// load and init all options plugins to print all options
|
||||||
try (JadxDecompiler decompiler = new JadxDecompiler(new JadxArgs())) {
|
try (JadxDecompiler decompiler = new JadxDecompiler(argsObj.toJadxArgs())) {
|
||||||
JadxPluginManager pluginManager = decompiler.getPluginManager();
|
JadxPluginManager pluginManager = decompiler.getPluginManager();
|
||||||
pluginManager.load(new JadxExternalPluginsLoader());
|
pluginManager.load(new JadxExternalPluginsLoader());
|
||||||
pluginManager.initAll();
|
pluginManager.initAll();
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ import jadx.api.args.IntegerFormat;
|
|||||||
import jadx.api.args.ResourceNameSource;
|
import jadx.api.args.ResourceNameSource;
|
||||||
import jadx.api.args.UserRenamesMappingsMode;
|
import jadx.api.args.UserRenamesMappingsMode;
|
||||||
import jadx.core.deobf.conditions.DeobfWhitelist;
|
import jadx.core.deobf.conditions.DeobfWhitelist;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
public class JadxCLIArgs {
|
public class JadxCLIArgs {
|
||||||
|
|
||||||
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk)")
|
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .jadx.kts)")
|
||||||
protected List<String> files = new ArrayList<>(1);
|
protected List<String> files = new ArrayList<>(1);
|
||||||
|
|
||||||
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
||||||
@@ -287,14 +287,13 @@ public class JadxCLIArgs {
|
|||||||
System.out.println(JadxDecompiler.getVersion());
|
System.out.println(JadxDecompiler.getVersion());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
if (threadsCount <= 0) {
|
||||||
if (threadsCount <= 0) {
|
throw new JadxArgsValidateException("Threads count must be positive, got: " + threadsCount);
|
||||||
throw new JadxException("Threads count must be positive, got: " + threadsCount);
|
}
|
||||||
|
for (String fileName : files) {
|
||||||
|
if (fileName.startsWith("-")) {
|
||||||
|
throw new JadxArgsValidateException("Unknown option: " + fileName);
|
||||||
}
|
}
|
||||||
} catch (JadxException e) {
|
|
||||||
System.err.println("ERROR: " + e.getMessage());
|
|
||||||
jcw.printUsage();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -559,8 +558,8 @@ public class JadxCLIArgs {
|
|||||||
for (String s : value.split(",")) {
|
for (String s : value.split(",")) {
|
||||||
try {
|
try {
|
||||||
set.add(RenameEnum.valueOf(s.trim().toUpperCase(Locale.ROOT)));
|
set.add(RenameEnum.valueOf(s.trim().toUpperCase(Locale.ROOT)));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalArgumentException(
|
throw new JadxArgsValidateException(
|
||||||
'\'' + s + "' is unknown for parameter " + paramName
|
'\'' + s + "' is unknown for parameter " + paramName
|
||||||
+ ", possible values are " + enumValuesString(RenameEnum.values()));
|
+ ", possible values are " + enumValuesString(RenameEnum.values()));
|
||||||
}
|
}
|
||||||
@@ -625,7 +624,7 @@ public class JadxCLIArgs {
|
|||||||
try {
|
try {
|
||||||
return parse.apply(stringAsEnumName(value));
|
return parse.apply(stringAsEnumName(value));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalArgumentException(
|
throw new JadxArgsValidateException(
|
||||||
'\'' + value + "' is unknown, possible values are: " + enumValuesString(values.get()));
|
'\'' + value + "' is unknown, possible values are: " + enumValuesString(values.get()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.beust.jcommander.JCommander;
|
|||||||
|
|
||||||
import jadx.cli.commands.CommandPlugins;
|
import jadx.cli.commands.CommandPlugins;
|
||||||
import jadx.cli.commands.ICommand;
|
import jadx.cli.commands.ICommand;
|
||||||
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
|
|
||||||
public class JadxCLICommands {
|
public class JadxCLICommands {
|
||||||
private static final Map<String, ICommand> COMMANDS_MAP = new TreeMap<>();
|
private static final Map<String, ICommand> COMMANDS_MAP = new TreeMap<>();
|
||||||
@@ -26,7 +27,8 @@ public class JadxCLICommands {
|
|||||||
public static boolean process(JCommanderWrapper<?> jcw, JCommander jc, String parsedCommand) {
|
public static boolean process(JCommanderWrapper<?> jcw, JCommander jc, String parsedCommand) {
|
||||||
ICommand command = COMMANDS_MAP.get(parsedCommand);
|
ICommand command = COMMANDS_MAP.get(parsedCommand);
|
||||||
if (command == null) {
|
if (command == null) {
|
||||||
throw new IllegalArgumentException("Unknown command: " + parsedCommand);
|
throw new JadxArgsValidateException("Unknown command: " + parsedCommand
|
||||||
|
+ ". Expected one of: " + COMMANDS_MAP.keySet());
|
||||||
}
|
}
|
||||||
JCommander subCommander = jc.getCommands().get(parsedCommand);
|
JCommander subCommander = jc.getCommands().get(parsedCommand);
|
||||||
command.process(jcw, subCommander);
|
command.process(jcw, subCommander);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import jadx.api.JadxDecompiler;
|
|||||||
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.visitors.SaveCode;
|
import jadx.core.dex.visitors.SaveCode;
|
||||||
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
@@ -33,10 +34,10 @@ public class SingleClassMode {
|
|||||||
.findFirst().orElse(null);
|
.findFirst().orElse(null);
|
||||||
}
|
}
|
||||||
if (clsForProcess == null) {
|
if (clsForProcess == null) {
|
||||||
throw new JadxRuntimeException("Input class not found: " + singleClass);
|
throw new JadxArgsValidateException("Input class not found: " + singleClass);
|
||||||
}
|
}
|
||||||
if (clsForProcess.contains(AFlag.DONT_GENERATE)) {
|
if (clsForProcess.contains(AFlag.DONT_GENERATE)) {
|
||||||
throw new JadxRuntimeException("Input class can't be saved by current jadx settings (marked as DONT_GENERATE)");
|
throw new JadxArgsValidateException("Input class can't be saved by current jadx settings (marked as DONT_GENERATE)");
|
||||||
}
|
}
|
||||||
if (clsForProcess.isInner()) {
|
if (clsForProcess.isInner()) {
|
||||||
clsForProcess = clsForProcess.getTopParentClass();
|
clsForProcess = clsForProcess.getTopParentClass();
|
||||||
@@ -52,7 +53,7 @@ public class SingleClassMode {
|
|||||||
if (size == 1) {
|
if (size == 1) {
|
||||||
clsForProcess = classes.get(0);
|
clsForProcess = classes.get(0);
|
||||||
} else {
|
} else {
|
||||||
throw new JadxRuntimeException("Found " + size + " classes, single class output can't be used");
|
throw new JadxArgsValidateException("Found " + size + " classes, single class output can't be used");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ICodeInfo codeInfo;
|
ICodeInfo codeInfo;
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ public class ConvertToClsSet {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
|
||||||
|
|
||||||
public static void usage() {
|
public static void usage() {
|
||||||
LOG.info("<output .jcst file> <several input dex or jar files> ");
|
LOG.info("<android API level (number)> <output .jcst file> <several input dex or jar files> ");
|
||||||
LOG.info("Arguments to update core.jcst: "
|
LOG.info("Arguments to update core.jcst: "
|
||||||
|
+ "<android API level (number)> "
|
||||||
+ "<jadx root>/jadx-core/src/main/resources/clst/core.jcst "
|
+ "<jadx root>/jadx-core/src/main/resources/clst/core.jcst "
|
||||||
+ "<sdk_root>/platforms/android-<api level>/android.jar"
|
+ "<sdk_root>/platforms/android-<api level>/android.jar"
|
||||||
+ "<sdk_root>/platforms/android-<api level>/optional/android.car.jar "
|
+ "<sdk_root>/platforms/android-<api level>/optional/android.car.jar "
|
||||||
@@ -32,11 +33,12 @@ public class ConvertToClsSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
if (args.length < 2) {
|
if (args.length != 5) {
|
||||||
usage();
|
usage();
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
|
int androidApiLevel = Integer.parseInt(args[0]);
|
||||||
|
List<Path> inputPaths = Stream.of(args).skip(1).map(Paths::get).collect(Collectors.toList());
|
||||||
Path output = inputPaths.remove(0);
|
Path output = inputPaths.remove(0);
|
||||||
|
|
||||||
JadxArgs jadxArgs = new JadxArgs();
|
JadxArgs jadxArgs = new JadxArgs();
|
||||||
@@ -57,6 +59,7 @@ public class ConvertToClsSet {
|
|||||||
decompiler.load();
|
decompiler.load();
|
||||||
RootNode root = decompiler.getRoot();
|
RootNode root = decompiler.getRoot();
|
||||||
ClsSet set = new ClsSet(root);
|
ClsSet set = new ClsSet(root);
|
||||||
|
set.setAndroidApiLevel(androidApiLevel);
|
||||||
set.loadFrom(root);
|
set.loadFrom(root);
|
||||||
set.save(output);
|
set.save(output);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
|
|
||||||
import jadx.api.JadxArgs.RenameEnum;
|
import jadx.api.JadxArgs.RenameEnum;
|
||||||
import jadx.cli.JadxCLIArgs.RenameConverter;
|
import jadx.cli.JadxCLIArgs.RenameConverter;
|
||||||
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
@@ -38,7 +39,7 @@ public class RenameConverterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void wrong() {
|
public void wrong() {
|
||||||
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
|
JadxArgsValidateException thrown = assertThrows(JadxArgsValidateException.class,
|
||||||
() -> converter.convert("wrong"),
|
() -> converter.convert("wrong"),
|
||||||
"Expected convert() to throw, but it didn't");
|
"Expected convert() to throw, but it didn't");
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,12 @@ dependencies {
|
|||||||
implementation("com.google.code.gson:gson:2.10.1")
|
implementation("com.google.code.gson:gson:2.10.1")
|
||||||
|
|
||||||
// TODO: move resources decoding to separate plugin module
|
// TODO: move resources decoding to separate plugin module
|
||||||
implementation("com.android.tools.build:aapt2-proto:8.2.2-10154469")
|
implementation("com.android.tools.build:aapt2-proto:8.3.2-10880808")
|
||||||
implementation("com.google.protobuf:protobuf-java:3.25.2") // forcing latest version
|
implementation("com.google.protobuf:protobuf-java") {
|
||||||
|
version {
|
||||||
|
require("3.25.3") // version 4 conflict with `aapt2-proto`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
testImplementation("org.apache.commons:commons-lang3:3.14.0")
|
testImplementation("org.apache.commons:commons-lang3:3.14.0")
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import jadx.api.metadata.ICodeAnnotation;
|
|||||||
import jadx.api.metadata.ICodeNodeRef;
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
|
||||||
public interface ICodeWriter {
|
public interface ICodeWriter {
|
||||||
String NL = System.getProperty("line.separator");
|
|
||||||
String INDENT_STR = " ";
|
|
||||||
|
|
||||||
boolean isMetadataSupported();
|
boolean isMetadataSupported();
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ public class JadxArgs implements Closeable {
|
|||||||
|
|
||||||
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
|
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
|
||||||
|
|
||||||
|
public static final String DEFAULT_NEW_LINE_STR = System.lineSeparator();
|
||||||
|
public static final String DEFAULT_INDENT_STR = " ";
|
||||||
|
|
||||||
public static final String DEFAULT_OUT_DIR = "jadx-output";
|
public static final String DEFAULT_OUT_DIR = "jadx-output";
|
||||||
public static final String DEFAULT_SRC_DIR = "sources";
|
public static final String DEFAULT_SRC_DIR = "sources";
|
||||||
public static final String DEFAULT_RES_DIR = "resources";
|
public static final String DEFAULT_RES_DIR = "resources";
|
||||||
@@ -144,6 +147,10 @@ public class JadxArgs implements Closeable {
|
|||||||
|
|
||||||
private ICodeData codeData;
|
private ICodeData codeData;
|
||||||
|
|
||||||
|
private String codeNewLineStr = DEFAULT_NEW_LINE_STR;
|
||||||
|
|
||||||
|
private String codeIndentStr = DEFAULT_INDENT_STR;
|
||||||
|
|
||||||
private CommentsLevel commentsLevel = CommentsLevel.INFO;
|
private CommentsLevel commentsLevel = CommentsLevel.INFO;
|
||||||
|
|
||||||
private IntegerFormat integerFormat = IntegerFormat.AUTO;
|
private IntegerFormat integerFormat = IntegerFormat.AUTO;
|
||||||
@@ -622,6 +629,22 @@ public class JadxArgs implements Closeable {
|
|||||||
this.codeData = codeData;
|
this.codeData = codeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCodeIndentStr() {
|
||||||
|
return codeIndentStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCodeIndentStr(String codeIndentStr) {
|
||||||
|
this.codeIndentStr = codeIndentStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCodeNewLineStr() {
|
||||||
|
return codeNewLineStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCodeNewLineStr(String codeNewLineStr) {
|
||||||
|
this.codeNewLineStr = codeNewLineStr;
|
||||||
|
}
|
||||||
|
|
||||||
public CommentsLevel getCommentsLevel() {
|
public CommentsLevel getCommentsLevel() {
|
||||||
return commentsLevel;
|
return commentsLevel;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,12 +28,6 @@ public class JadxArgsValidator {
|
|||||||
if (inputFiles.isEmpty() && jadx.getCustomCodeLoaders().isEmpty()) {
|
if (inputFiles.isEmpty() && jadx.getCustomCodeLoaders().isEmpty()) {
|
||||||
throw new JadxArgsValidateException("Please specify input file");
|
throw new JadxArgsValidateException("Please specify input file");
|
||||||
}
|
}
|
||||||
for (File inputFile : inputFiles) {
|
|
||||||
String fileName = inputFile.getName();
|
|
||||||
if (fileName.startsWith("--")) {
|
|
||||||
throw new JadxArgsValidateException("Unknown argument: " + fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (File file : inputFiles) {
|
for (File file : inputFiles) {
|
||||||
checkFile(file);
|
checkFile(file);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ public class ResourceFile {
|
|||||||
return deobfName != null ? deobfName : name;
|
return deobfName != null ? deobfName : name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDeobfName(String resFullName) {
|
||||||
|
this.deobfName = resFullName;
|
||||||
|
}
|
||||||
|
|
||||||
public ResourceType getType() {
|
public ResourceType getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
@@ -84,7 +88,7 @@ public class ResourceFile {
|
|||||||
}
|
}
|
||||||
String alias = sb.toString();
|
String alias = sb.toString();
|
||||||
if (!alias.equals(name)) {
|
if (!alias.equals(name)) {
|
||||||
deobfName = alias;
|
setDeobfName(alias);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package jadx.api.data;
|
||||||
|
|
||||||
|
public enum CommentStyle {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* // comment
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
LINE("// ", "// ", ""),
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* /*
|
||||||
|
* * comment
|
||||||
|
* */
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
// @formatter:on
|
||||||
|
BLOCK("/*\n * ", " * ", "\n */"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* /* comment */
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
BLOCK_CONDENSED("/* ", " * ", " */"),
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* /**
|
||||||
|
* * comment
|
||||||
|
* */
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
// @formatter:on
|
||||||
|
JAVADOC("/**\n * ", " * ", "\n */"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* /** comment */
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
JAVADOC_CONDENSED("/** ", " * ", " */");
|
||||||
|
|
||||||
|
private final String start;
|
||||||
|
private final String onNewLine;
|
||||||
|
private final String end;
|
||||||
|
|
||||||
|
CommentStyle(String start, String onNewLine, String end) {
|
||||||
|
this.start = start;
|
||||||
|
this.onNewLine = onNewLine;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOnNewLine() {
|
||||||
|
return onNewLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEnd() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,4 +10,6 @@ public interface ICodeComment extends Comparable<ICodeComment> {
|
|||||||
IJavaCodeRef getCodeRef();
|
IJavaCodeRef getCodeRef();
|
||||||
|
|
||||||
String getComment();
|
String getComment();
|
||||||
|
|
||||||
|
CommentStyle getStyle();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package jadx.api.data.impl;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.data.CommentStyle;
|
||||||
import jadx.api.data.ICodeComment;
|
import jadx.api.data.ICodeComment;
|
||||||
import jadx.api.data.IJavaCodeRef;
|
import jadx.api.data.IJavaCodeRef;
|
||||||
import jadx.api.data.IJavaNodeRef;
|
import jadx.api.data.IJavaNodeRef;
|
||||||
@@ -13,15 +14,25 @@ public class JadxCodeComment implements ICodeComment {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private IJavaCodeRef codeRef;
|
private IJavaCodeRef codeRef;
|
||||||
private String comment;
|
private String comment;
|
||||||
|
private CommentStyle style = CommentStyle.LINE;
|
||||||
|
|
||||||
public JadxCodeComment(IJavaNodeRef nodeRef, String comment) {
|
public JadxCodeComment(IJavaNodeRef nodeRef, String comment) {
|
||||||
this(nodeRef, null, comment);
|
this(nodeRef, null, comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JadxCodeComment(IJavaNodeRef nodeRef, String comment, CommentStyle style) {
|
||||||
|
this(nodeRef, null, comment, style);
|
||||||
|
}
|
||||||
|
|
||||||
public JadxCodeComment(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String comment) {
|
public JadxCodeComment(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String comment) {
|
||||||
|
this(nodeRef, codeRef, comment, CommentStyle.LINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxCodeComment(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String comment, CommentStyle style) {
|
||||||
this.nodeRef = nodeRef;
|
this.nodeRef = nodeRef;
|
||||||
this.codeRef = codeRef;
|
this.codeRef = codeRef;
|
||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
|
this.style = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JadxCodeComment() {
|
public JadxCodeComment() {
|
||||||
@@ -56,6 +67,15 @@ public class JadxCodeComment implements ICodeComment {
|
|||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommentStyle getStyle() {
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStyle(CommentStyle style) {
|
||||||
|
this.style = style;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(@NotNull ICodeComment other) {
|
public int compareTo(@NotNull ICodeComment other) {
|
||||||
int cmpNodeRef = this.getNodeRef().compareTo(other.getNodeRef());
|
int cmpNodeRef = this.getNodeRef().compareTo(other.getNodeRef());
|
||||||
@@ -73,6 +93,7 @@ public class JadxCodeComment implements ICodeComment {
|
|||||||
return "JadxCodeComment{" + nodeRef
|
return "JadxCodeComment{" + nodeRef
|
||||||
+ ", ref=" + codeRef
|
+ ", ref=" + codeRef
|
||||||
+ ", comment='" + comment + '\''
|
+ ", comment='" + comment + '\''
|
||||||
|
+ ", style=" + style
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,6 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
|||||||
private Map<Integer, ICodeAnnotation> annotations = Collections.emptyMap();
|
private Map<Integer, ICodeAnnotation> annotations = Collections.emptyMap();
|
||||||
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
||||||
|
|
||||||
public AnnotatedCodeWriter() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnnotatedCodeWriter(JadxArgs args) {
|
public AnnotatedCodeWriter(JadxArgs args) {
|
||||||
super(args);
|
super(args);
|
||||||
}
|
}
|
||||||
@@ -35,9 +32,9 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AnnotatedCodeWriter addMultiLine(String str) {
|
public AnnotatedCodeWriter addMultiLine(String str) {
|
||||||
if (str.contains(NL)) {
|
if (str.contains(newLineStr)) {
|
||||||
buf.append(str.replace(NL, NL + indentStr));
|
buf.append(str.replace(newLineStr, newLineStr + indentStr));
|
||||||
line += StringUtils.countMatches(str, NL);
|
line += StringUtils.countMatches(str, newLineStr);
|
||||||
offset = 0;
|
offset = 0;
|
||||||
} else {
|
} else {
|
||||||
buf.append(str);
|
buf.append(str);
|
||||||
@@ -84,7 +81,7 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addLine() {
|
protected void addLine() {
|
||||||
buf.append(NL);
|
buf.append(newLineStr);
|
||||||
line++;
|
line++;
|
||||||
offset = 0;
|
offset = 0;
|
||||||
}
|
}
|
||||||
@@ -154,7 +151,6 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ICodeInfo finish() {
|
public ICodeInfo finish() {
|
||||||
processDefinitionAnnotations();
|
|
||||||
validateAnnotations();
|
validateAnnotations();
|
||||||
String code = buf.toString();
|
String code = buf.toString();
|
||||||
buf = null;
|
buf = null;
|
||||||
@@ -166,18 +162,6 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
|||||||
return annotations;
|
return annotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processDefinitionAnnotations() {
|
|
||||||
if (!annotations.isEmpty()) {
|
|
||||||
annotations.forEach((k, v) -> {
|
|
||||||
if (v instanceof NodeDeclareRef) {
|
|
||||||
NodeDeclareRef declareRef = (NodeDeclareRef) v;
|
|
||||||
declareRef.setDefPos(k);
|
|
||||||
declareRef.getNode().setDefPosition(k);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateAnnotations() {
|
private void validateAnnotations() {
|
||||||
if (annotations.isEmpty()) {
|
if (annotations.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -14,38 +14,39 @@ import jadx.api.metadata.ICodeNodeRef;
|
|||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CodeWriter implementation without meta information support (only strings builder)
|
* CodeWriter implementation without meta information support
|
||||||
*/
|
*/
|
||||||
public class SimpleCodeWriter implements ICodeWriter {
|
public class SimpleCodeWriter implements ICodeWriter {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SimpleCodeWriter.class);
|
private static final Logger LOG = LoggerFactory.getLogger(SimpleCodeWriter.class);
|
||||||
|
|
||||||
private static final String[] INDENT_CACHE = {
|
|
||||||
"",
|
|
||||||
INDENT_STR,
|
|
||||||
INDENT_STR + INDENT_STR,
|
|
||||||
INDENT_STR + INDENT_STR + INDENT_STR,
|
|
||||||
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
|
||||||
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
|
||||||
};
|
|
||||||
|
|
||||||
protected StringBuilder buf = new StringBuilder();
|
protected StringBuilder buf = new StringBuilder();
|
||||||
protected String indentStr = "";
|
protected String indentStr = "";
|
||||||
protected int indent = 0;
|
protected int indent = 0;
|
||||||
|
|
||||||
private final boolean insertLineNumbers;
|
protected final boolean insertLineNumbers;
|
||||||
|
protected final String singleIndentStr;
|
||||||
public SimpleCodeWriter() {
|
protected final String newLineStr;
|
||||||
this.insertLineNumbers = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SimpleCodeWriter(JadxArgs args) {
|
public SimpleCodeWriter(JadxArgs args) {
|
||||||
this.insertLineNumbers = args.isInsertDebugLines();
|
this.insertLineNumbers = args.isInsertDebugLines();
|
||||||
|
this.singleIndentStr = args.getCodeIndentStr();
|
||||||
|
this.newLineStr = args.getCodeNewLineStr();
|
||||||
if (insertLineNumbers) {
|
if (insertLineNumbers) {
|
||||||
incIndent(3);
|
incIndent(3);
|
||||||
add(indentStr);
|
add(indentStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with JadxArgs should be used.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public SimpleCodeWriter() {
|
||||||
|
this.insertLineNumbers = false;
|
||||||
|
this.singleIndentStr = JadxArgs.DEFAULT_INDENT_STR;
|
||||||
|
this.newLineStr = JadxArgs.DEFAULT_NEW_LINE_STR;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isMetadataSupported() {
|
public boolean isMetadataSupported() {
|
||||||
return false;
|
return false;
|
||||||
@@ -96,8 +97,8 @@ public class SimpleCodeWriter implements ICodeWriter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SimpleCodeWriter addMultiLine(String str) {
|
public SimpleCodeWriter addMultiLine(String str) {
|
||||||
if (str.contains(NL)) {
|
if (str.contains(newLineStr)) {
|
||||||
buf.append(str.replace(NL, NL + indentStr));
|
buf.append(str.replace(newLineStr, newLineStr + indentStr));
|
||||||
} else {
|
} else {
|
||||||
buf.append(str);
|
buf.append(str);
|
||||||
}
|
}
|
||||||
@@ -130,12 +131,12 @@ public class SimpleCodeWriter implements ICodeWriter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SimpleCodeWriter addIndent() {
|
public SimpleCodeWriter addIndent() {
|
||||||
add(INDENT_STR);
|
add(singleIndentStr);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addLine() {
|
protected void addLine() {
|
||||||
buf.append(NL);
|
buf.append(newLineStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SimpleCodeWriter addLineIndent() {
|
protected SimpleCodeWriter addLineIndent() {
|
||||||
@@ -144,12 +145,7 @@ public class SimpleCodeWriter implements ICodeWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateIndent() {
|
private void updateIndent() {
|
||||||
int curIndent = indent;
|
this.indentStr = Utils.strRepeat(singleIndentStr, indent);
|
||||||
if (curIndent < INDENT_CACHE.length) {
|
|
||||||
this.indentStr = INDENT_CACHE[curIndent];
|
|
||||||
} else {
|
|
||||||
this.indentStr = Utils.strRepeat(INDENT_STR, curIndent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -219,17 +215,17 @@ public class SimpleCodeWriter implements ICodeWriter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ICodeInfo finish() {
|
public ICodeInfo finish() {
|
||||||
removeFirstEmptyLine();
|
String code = getStringWithoutFirstEmptyLine();
|
||||||
String code = buf.toString();
|
|
||||||
buf = null;
|
buf = null;
|
||||||
return new SimpleCodeInfo(code);
|
return new SimpleCodeInfo(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void removeFirstEmptyLine() {
|
private String getStringWithoutFirstEmptyLine() {
|
||||||
int len = NL.length();
|
int len = newLineStr.length();
|
||||||
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
|
if (buf.length() > len && buf.substring(0, len).equals(newLineStr)) {
|
||||||
buf.delete(0, len);
|
return buf.substring(len);
|
||||||
}
|
}
|
||||||
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ public enum OptionFlag {
|
|||||||
HIDE_IN_GUI,
|
HIDE_IN_GUI,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do not show this option in jadx-gui (useful if option is configured with custom ui)
|
* Option will be read-only in jadx-gui (can be used for calculated properties)
|
||||||
*/
|
*/
|
||||||
DISABLE_IN_GUI,
|
DISABLE_IN_GUI,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add this flag only if option do not affect generated code.
|
* Add this flag only if the option does not affect generated code.
|
||||||
* If added, option value change will not cause code cache reset.
|
* If added, option value change will not cause code cache reset.
|
||||||
*/
|
*/
|
||||||
NOT_CHANGING_CODE,
|
NOT_CHANGING_CODE,
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package jadx.api.utils;
|
package jadx.api.utils;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
|
||||||
|
|
||||||
public class CodeUtils {
|
public class CodeUtils {
|
||||||
|
|
||||||
public static String getLineForPos(String code, int pos) {
|
public static String getLineForPos(String code, int pos) {
|
||||||
@@ -11,18 +9,32 @@ public class CodeUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int getLineStartForPos(String code, int pos) {
|
public static int getLineStartForPos(String code, int pos) {
|
||||||
String newLine = ICodeWriter.NL;
|
int start = getNewLinePosBefore(code, pos);
|
||||||
int start = code.lastIndexOf(newLine, pos);
|
return start == -1 ? 0 : start + 1;
|
||||||
return start == -1 ? 0 : start + newLine.length();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getLineEndForPos(String code, int pos) {
|
public static int getLineEndForPos(String code, int pos) {
|
||||||
int end = code.indexOf(ICodeWriter.NL, pos);
|
int end = getNewLinePosAfter(code, pos);
|
||||||
return end == -1 ? code.length() : end;
|
return end == -1 ? code.length() : end;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getLineNumForPos(String code, int pos) {
|
public static int getNewLinePosAfter(String code, int startPos) {
|
||||||
String newLine = ICodeWriter.NL;
|
int pos = code.indexOf('\n', startPos);
|
||||||
|
if (pos != -1) {
|
||||||
|
// check for '\r\n'
|
||||||
|
int prev = pos - 1;
|
||||||
|
if (code.charAt(prev) == '\r') {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getNewLinePosBefore(String code, int startPos) {
|
||||||
|
return code.lastIndexOf('\n', startPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getLineNumForPos(String code, int pos, String newLine) {
|
||||||
int newLineLen = newLine.length();
|
int newLineLen = newLine.length();
|
||||||
int line = 1;
|
int line = 1;
|
||||||
int prev = 0;
|
int prev = 0;
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
|||||||
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
|
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
|
||||||
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
|
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
|
||||||
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
|
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
|
||||||
|
import jadx.core.dex.visitors.prepare.AddAndroidConstants;
|
||||||
|
import jadx.core.dex.visitors.prepare.CollectConstValues;
|
||||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||||
import jadx.core.dex.visitors.regions.CleanRegions;
|
import jadx.core.dex.visitors.regions.CleanRegions;
|
||||||
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||||
@@ -67,6 +69,7 @@ import jadx.core.dex.visitors.rename.SourceFileRename;
|
|||||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||||
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
|
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
|
||||||
|
import jadx.core.dex.visitors.typeinference.FixTypesVisitor;
|
||||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||||
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
@@ -95,6 +98,8 @@ public class Jadx {
|
|||||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
passes.add(new SignatureProcessor());
|
passes.add(new SignatureProcessor());
|
||||||
passes.add(new OverrideMethodVisitor());
|
passes.add(new OverrideMethodVisitor());
|
||||||
|
passes.add(new AddAndroidConstants());
|
||||||
|
passes.add(new CollectConstValues());
|
||||||
|
|
||||||
// rename and deobfuscation
|
// rename and deobfuscation
|
||||||
passes.add(new DeobfuscatorVisitor());
|
passes.add(new DeobfuscatorVisitor());
|
||||||
@@ -141,7 +146,9 @@ public class Jadx {
|
|||||||
if (args.isDebugInfo()) {
|
if (args.isDebugInfo()) {
|
||||||
passes.add(new DebugInfoApplyVisitor());
|
passes.add(new DebugInfoApplyVisitor());
|
||||||
}
|
}
|
||||||
|
passes.add(new FixTypesVisitor());
|
||||||
passes.add(new FinishTypeInference());
|
passes.add(new FinishTypeInference());
|
||||||
|
|
||||||
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
|
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
|
||||||
passes.add(new ProcessKotlinInternals());
|
passes.add(new ProcessKotlinInternals());
|
||||||
}
|
}
|
||||||
@@ -216,6 +223,7 @@ public class Jadx {
|
|||||||
if (args.isDebugInfo()) {
|
if (args.isDebugInfo()) {
|
||||||
passes.add(new DebugInfoApplyVisitor());
|
passes.add(new DebugInfoApplyVisitor());
|
||||||
}
|
}
|
||||||
|
passes.add(new FixTypesVisitor());
|
||||||
passes.add(new FinishTypeInference());
|
passes.add(new FinishTypeInference());
|
||||||
passes.add(new CodeRenameVisitor());
|
passes.add(new CodeRenameVisitor());
|
||||||
passes.add(new DeboxingVisitor());
|
passes.add(new DeboxingVisitor());
|
||||||
|
|||||||
@@ -44,14 +44,17 @@ public class ClsSet {
|
|||||||
private static final String CLST_PATH = "/clst/" + CLST_FILENAME;
|
private static final String CLST_PATH = "/clst/" + CLST_FILENAME;
|
||||||
|
|
||||||
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
|
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
|
||||||
private static final int VERSION = 4;
|
private static final int VERSION = 5;
|
||||||
|
|
||||||
private static final String STRING_CHARSET = "US-ASCII";
|
private static final String STRING_CHARSET = "US-ASCII";
|
||||||
|
|
||||||
private static final ArgType[] EMPTY_ARGTYPE_ARRAY = new ArgType[0];
|
private static final ArgType[] EMPTY_ARGTYPE_ARRAY = new ArgType[0];
|
||||||
|
private static final ArgType[] OBJECT_ARGTYPE_ARRAY = new ArgType[] { ArgType.OBJECT };
|
||||||
|
|
||||||
private final RootNode root;
|
private final RootNode root;
|
||||||
|
|
||||||
|
private int androidApiLevel;
|
||||||
|
|
||||||
public ClsSet(RootNode root) {
|
public ClsSet(RootNode root) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
}
|
}
|
||||||
@@ -79,7 +82,8 @@ public class ClsSet {
|
|||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
long time = System.currentTimeMillis() - startTime;
|
long time = System.currentTimeMillis() - startTime;
|
||||||
int methodsCount = Stream.of(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
|
int methodsCount = Stream.of(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
|
||||||
LOG.debug("Clst file loaded in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount);
|
LOG.debug("Clst file loaded in {}ms, android api: {}, classes: {}, methods: {}",
|
||||||
|
time, androidApiLevel, classes.length, methodsCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +97,7 @@ public class ClsSet {
|
|||||||
cls.load();
|
cls.load();
|
||||||
|
|
||||||
ClspClassSource source = getClspClassSource(cls);
|
ClspClassSource source = getClspClassSource(cls);
|
||||||
ClspClass nClass = new ClspClass(clsType, k, source);
|
ClspClass nClass = new ClspClass(clsType, k, cls.getAccessFlags().rawValue(), source);
|
||||||
if (names.put(clsRawName, nClass) != null) {
|
if (names.put(clsRawName, nClass) != null) {
|
||||||
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||||
}
|
}
|
||||||
@@ -151,7 +155,11 @@ public class ClsSet {
|
|||||||
// cls is java.lang.Object
|
// cls is java.lang.Object
|
||||||
return EMPTY_ARGTYPE_ARRAY;
|
return EMPTY_ARGTYPE_ARRAY;
|
||||||
}
|
}
|
||||||
ArgType[] parents = new ArgType[1 + cls.getInterfaces().size()];
|
int interfacesCount = cls.getInterfaces().size();
|
||||||
|
if (interfacesCount == 0 && superClass == ArgType.OBJECT) {
|
||||||
|
return OBJECT_ARGTYPE_ARRAY;
|
||||||
|
}
|
||||||
|
ArgType[] parents = new ArgType[1 + interfacesCount];
|
||||||
parents[0] = superClass;
|
parents[0] = superClass;
|
||||||
int k = 1;
|
int k = 1;
|
||||||
for (ArgType iface : cls.getInterfaces()) {
|
for (ArgType iface : cls.getInterfaces()) {
|
||||||
@@ -193,10 +201,12 @@ public class ClsSet {
|
|||||||
DataOutputStream out = new DataOutputStream(output);
|
DataOutputStream out = new DataOutputStream(output);
|
||||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||||
out.writeByte(VERSION);
|
out.writeByte(VERSION);
|
||||||
|
out.writeInt(androidApiLevel);
|
||||||
|
|
||||||
Map<String, ClspClass> names = new HashMap<>(classes.length);
|
Map<String, ClspClass> names = new HashMap<>(classes.length);
|
||||||
out.writeInt(classes.length);
|
out.writeInt(classes.length);
|
||||||
for (ClspClass cls : classes) {
|
for (ClspClass cls : classes) {
|
||||||
|
out.writeInt(cls.getAccFlags());
|
||||||
writeUnsignedByte(out, cls.getSource().ordinal());
|
writeUnsignedByte(out, cls.getSource().ordinal());
|
||||||
String clsName = cls.getName();
|
String clsName = cls.getName();
|
||||||
writeString(out, clsName);
|
writeString(out, clsName);
|
||||||
@@ -243,6 +253,10 @@ public class ClsSet {
|
|||||||
out.writeByte(-1);
|
out.writeByte(-1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (arr == OBJECT_ARGTYPE_ARRAY) {
|
||||||
|
out.writeByte(-2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
int size = arr.length;
|
int size = arr.length;
|
||||||
out.writeByte(size);
|
out.writeByte(size);
|
||||||
if (size != 0) {
|
if (size != 0) {
|
||||||
@@ -294,22 +308,22 @@ public class ClsSet {
|
|||||||
try (DataInputStream in = new DataInputStream(new BufferedInputStream(input))) {
|
try (DataInputStream in = new DataInputStream(new BufferedInputStream(input))) {
|
||||||
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
|
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
|
||||||
int readHeaderLength = in.read(header);
|
int readHeaderLength = in.read(header);
|
||||||
int version = in.readByte();
|
|
||||||
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|
||||||
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))) {
|
||||||
|| version != VERSION) {
|
|
||||||
throw new DecodeException("Wrong jadx class set header");
|
throw new DecodeException("Wrong jadx class set header");
|
||||||
}
|
}
|
||||||
|
int version = in.readByte();
|
||||||
|
if (version != VERSION) {
|
||||||
|
throw new DecodeException("Wrong jadx class set version, got: " + version + ", expect: " + VERSION);
|
||||||
|
}
|
||||||
|
androidApiLevel = in.readInt();
|
||||||
int clsCount = in.readInt();
|
int clsCount = in.readInt();
|
||||||
classes = new ClspClass[clsCount];
|
classes = new ClspClass[clsCount];
|
||||||
ClspClassSource[] clspClassSources = ClspClassSource.values();
|
|
||||||
for (int i = 0; i < clsCount; i++) {
|
for (int i = 0; i < clsCount; i++) {
|
||||||
int source = readUnsignedByte(in);
|
int accFlags = in.readInt();
|
||||||
if (source < 0 || source > clspClassSources.length) {
|
ClspClassSource clsSource = readClsSource(in);
|
||||||
throw new DecodeException("Wrong jadx source identifier");
|
|
||||||
}
|
|
||||||
String name = readString(in);
|
String name = readString(in);
|
||||||
classes[i] = new ClspClass(ArgType.object(name), i, clspClassSources[source]);
|
classes[i] = new ClspClass(ArgType.object(name), i, accFlags, clsSource);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < clsCount; i++) {
|
for (int i = 0; i < clsCount; i++) {
|
||||||
ClspClass nClass = classes[i];
|
ClspClass nClass = classes[i];
|
||||||
@@ -321,6 +335,15 @@ public class ClsSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ClspClassSource readClsSource(DataInputStream in) throws IOException, DecodeException {
|
||||||
|
int source = readUnsignedByte(in);
|
||||||
|
ClspClassSource[] clspClassSources = ClspClassSource.values();
|
||||||
|
if (source < 0 || source > clspClassSources.length) {
|
||||||
|
throw new DecodeException("Wrong jadx source identifier: " + source);
|
||||||
|
}
|
||||||
|
return clspClassSources[source];
|
||||||
|
}
|
||||||
|
|
||||||
private List<ClspMethod> readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException {
|
private List<ClspMethod> readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException {
|
||||||
int mCount = in.readShort();
|
int mCount = in.readShort();
|
||||||
List<ClspMethod> methods = new ArrayList<>(mCount);
|
List<ClspMethod> methods = new ArrayList<>(mCount);
|
||||||
@@ -366,17 +389,20 @@ public class ClsSet {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private ArgType[] readArgTypesArray(DataInputStream in) throws IOException {
|
private ArgType[] readArgTypesArray(DataInputStream in) throws IOException {
|
||||||
int count = in.readByte();
|
int count = in.readByte();
|
||||||
if (count == -1) {
|
switch (count) {
|
||||||
return null;
|
case -1:
|
||||||
|
return null;
|
||||||
|
case -2:
|
||||||
|
return OBJECT_ARGTYPE_ARRAY;
|
||||||
|
case 0:
|
||||||
|
return EMPTY_ARGTYPE_ARRAY;
|
||||||
|
default:
|
||||||
|
ArgType[] arr = new ArgType[count];
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
arr[i] = readArgType(in);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
}
|
}
|
||||||
if (count == 0) {
|
|
||||||
return EMPTY_ARGTYPE_ARRAY;
|
|
||||||
}
|
|
||||||
ArgType[] arr = new ArgType[count];
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
arr[i] = readArgType(in);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArgType readArgType(DataInputStream in) throws IOException {
|
private ArgType readArgType(DataInputStream in) throws IOException {
|
||||||
@@ -384,9 +410,6 @@ public class ClsSet {
|
|||||||
if (ordinal == -1) {
|
if (ordinal == -1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (ordinal >= TypeEnum.values().length) {
|
|
||||||
throw new JadxRuntimeException("Incorrect ordinal for type enum: " + ordinal);
|
|
||||||
}
|
|
||||||
switch (TypeEnum.values()[ordinal]) {
|
switch (TypeEnum.values()[ordinal]) {
|
||||||
case WILDCARD:
|
case WILDCARD:
|
||||||
ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte());
|
ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte());
|
||||||
@@ -414,7 +437,7 @@ public class ClsSet {
|
|||||||
return classes[in.readInt()].getClsType();
|
return classes[in.readInt()].getClsType();
|
||||||
|
|
||||||
case ARRAY:
|
case ARRAY:
|
||||||
return ArgType.array(readArgType(in));
|
return ArgType.array(Objects.requireNonNull(readArgType(in)));
|
||||||
|
|
||||||
case PRIMITIVE:
|
case PRIMITIVE:
|
||||||
char shortName = (char) in.readByte();
|
char shortName = (char) in.readByte();
|
||||||
@@ -474,4 +497,12 @@ public class ClsSet {
|
|||||||
nameMap.put(cls.getName(), cls);
|
nameMap.put(cls.getName(), cls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getAndroidApiLevel() {
|
||||||
|
return androidApiLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAndroidApiLevel(int androidApiLevel) {
|
||||||
|
this.androidApiLevel = androidApiLevel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.intellij.lang.annotations.MagicConstant;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,21 +19,17 @@ public class ClspClass {
|
|||||||
|
|
||||||
private final ArgType clsType;
|
private final ArgType clsType;
|
||||||
private final int id;
|
private final int id;
|
||||||
|
private final int accFlags;
|
||||||
private ArgType[] parents;
|
private ArgType[] parents;
|
||||||
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
|
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
|
||||||
private List<ArgType> typeParameters = Collections.emptyList();
|
private List<ArgType> typeParameters = Collections.emptyList();
|
||||||
|
|
||||||
private ClspClassSource source;
|
private ClspClassSource source;
|
||||||
|
|
||||||
public ClspClass(ArgType clsType, int id) {
|
public ClspClass(ArgType clsType, int id, int accFlags, ClspClassSource source) {
|
||||||
this.clsType = clsType;
|
|
||||||
this.id = id;
|
|
||||||
this.source = ClspClassSource.APP;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClspClass(ArgType clsType, int id, ClspClassSource source) {
|
|
||||||
this.clsType = clsType;
|
this.clsType = clsType;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.accFlags = accFlags;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +45,18 @@ public class ClspClass {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getAccFlags() {
|
||||||
|
return accFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInterface() {
|
||||||
|
return AccessFlags.hasFlag(accFlags, AccessFlags.INTERFACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAccFlag(@MagicConstant(flagsFromClass = AccessFlags.class) int flags) {
|
||||||
|
return AccessFlags.hasFlag(accFlags, flags);
|
||||||
|
}
|
||||||
|
|
||||||
public ArgType[] getParents() {
|
public ArgType[] getParents() {
|
||||||
return parents;
|
return parents;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
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;
|
||||||
@@ -106,7 +107,7 @@ public class ClspGraph {
|
|||||||
private void addClass(ClassNode cls) {
|
private void addClass(ClassNode cls) {
|
||||||
ArgType clsType = cls.getClassInfo().getType();
|
ArgType clsType = cls.getClassInfo().getType();
|
||||||
String rawName = clsType.getObject();
|
String rawName = clsType.getObject();
|
||||||
ClspClass clspClass = new ClspClass(clsType, -1);
|
ClspClass clspClass = new ClspClass(clsType, -1, cls.getAccessFlags().rawValue(), ClspClassSource.APP);
|
||||||
clspClass.setParents(ClsSet.makeParentsArray(cls));
|
clspClass.setParents(ClsSet.makeParentsArray(cls));
|
||||||
nameMap.put(rawName, clspClass);
|
nameMap.put(rawName, clspClass);
|
||||||
}
|
}
|
||||||
@@ -174,6 +175,8 @@ public class ClspGraph {
|
|||||||
return result == null ? Collections.emptySet() : result;
|
return result == null ? Collections.emptySet() : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Set<String> OBJECT_SINGLE_SET = Collections.singleton(Consts.CLASS_OBJECT);
|
||||||
|
|
||||||
private void fillSuperTypesCache() {
|
private void fillSuperTypesCache() {
|
||||||
Map<String, Set<String>> map = new HashMap<>(nameMap.size());
|
Map<String, Set<String>> map = new HashMap<>(nameMap.size());
|
||||||
Set<String> tmpSet = new HashSet<>();
|
Set<String> tmpSet = new HashSet<>();
|
||||||
@@ -182,10 +185,25 @@ public class ClspGraph {
|
|||||||
tmpSet.clear();
|
tmpSet.clear();
|
||||||
addSuperTypes(cls, tmpSet);
|
addSuperTypes(cls, tmpSet);
|
||||||
Set<String> result;
|
Set<String> result;
|
||||||
if (tmpSet.isEmpty()) {
|
int size = tmpSet.size();
|
||||||
result = Collections.emptySet();
|
switch (size) {
|
||||||
} else {
|
case 0: {
|
||||||
result = new HashSet<>(tmpSet);
|
result = Collections.emptySet();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
String supCls = tmpSet.iterator().next();
|
||||||
|
if (supCls.equals(Consts.CLASS_OBJECT)) {
|
||||||
|
result = OBJECT_SINGLE_SET;
|
||||||
|
} else {
|
||||||
|
result = Collections.singleton(supCls);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
result = new HashSet<>(tmpSet);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
map.put(cls.getName(), result);
|
map.put(cls.getName(), result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ 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.attributes.JadxAttrType;
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
|
import jadx.core.codegen.utils.CodeGenUtils;
|
||||||
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.FieldInitInsnAttr;
|
import jadx.core.dex.attributes.FieldInitInsnAttr;
|
||||||
@@ -45,7 +46,6 @@ import jadx.core.dex.nodes.FieldNode;
|
|||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.utils.CodeGenUtils;
|
|
||||||
import jadx.core.utils.EncodedValueUtils;
|
import jadx.core.utils.EncodedValueUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.android.AndroidResourcesUtils;
|
import jadx.core.utils.android.AndroidResourcesUtils;
|
||||||
@@ -421,18 +421,22 @@ public class ClassGen {
|
|||||||
if (f.contains(AFlag.DONT_GENERATE)) {
|
if (f.contains(AFlag.DONT_GENERATE)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (f.contains(JadxAttrType.ANNOTATION_LIST)
|
||||||
|
|| f.contains(AType.JADX_COMMENTS)
|
||||||
|
|| f.contains(AType.CODE_COMMENTS)
|
||||||
|
|| f.getFieldInfo().hasAlias()) {
|
||||||
|
code.newLine();
|
||||||
|
}
|
||||||
if (Consts.DEBUG_USAGE) {
|
if (Consts.DEBUG_USAGE) {
|
||||||
addFieldUsageInfo(code, f);
|
addFieldUsageInfo(code, f);
|
||||||
}
|
}
|
||||||
|
if (f.getFieldInfo().hasAlias()) {
|
||||||
|
CodeGenUtils.addRenamedComment(code, f, f.getName());
|
||||||
|
}
|
||||||
CodeGenUtils.addComments(code, f);
|
CodeGenUtils.addComments(code, f);
|
||||||
annotationGen.addForField(code, f);
|
annotationGen.addForField(code, f);
|
||||||
|
|
||||||
boolean addInfoComments = f.checkCommentsLevel(CommentsLevel.INFO);
|
code.startLine(f.getAccessFlags().makeString(f.checkCommentsLevel(CommentsLevel.INFO)));
|
||||||
if (f.getFieldInfo().hasAlias() && addInfoComments) {
|
|
||||||
code.newLine();
|
|
||||||
CodeGenUtils.addRenamedComment(code, f, f.getName());
|
|
||||||
}
|
|
||||||
code.startLine(f.getAccessFlags().makeString(addInfoComments));
|
|
||||||
useType(code, f.getType());
|
useType(code, f.getType());
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
code.attachDefinition(f);
|
code.attachDefinition(f);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import jadx.api.metadata.annotations.InsnCodeOffset;
|
|||||||
import jadx.api.metadata.annotations.VarNode;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
import jadx.api.plugins.input.data.MethodHandleType;
|
import jadx.api.plugins.input.data.MethodHandleType;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
|
import jadx.core.codegen.utils.CodeGenUtils;
|
||||||
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.FieldInitInsnAttr;
|
import jadx.core.dex.attributes.FieldInitInsnAttr;
|
||||||
@@ -60,7 +61,6 @@ import jadx.core.dex.nodes.FieldNode;
|
|||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.utils.CodeGenUtils;
|
|
||||||
import jadx.core.utils.RegionUtils;
|
import jadx.core.utils.RegionUtils;
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
@@ -936,7 +936,7 @@ public class InsnGen {
|
|||||||
makeInlinedLambdaMethod(code, customNode, callMth);
|
makeInlinedLambdaMethod(code, customNode, callMth);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeRefLambda(ICodeWriter code, InvokeCustomNode customNode) {
|
private void makeRefLambda(ICodeWriter code, InvokeCustomNode customNode) throws CodegenException {
|
||||||
InsnNode callInsn = customNode.getCallInsn();
|
InsnNode callInsn = customNode.getCallInsn();
|
||||||
if (callInsn instanceof ConstructorInsn) {
|
if (callInsn instanceof ConstructorInsn) {
|
||||||
MethodInfo callMth = ((ConstructorInsn) callInsn).getCallMth();
|
MethodInfo callMth = ((ConstructorInsn) callInsn).getCallMth();
|
||||||
@@ -950,7 +950,7 @@ public class InsnGen {
|
|||||||
if (customNode.getHandleType() == MethodHandleType.INVOKE_STATIC) {
|
if (customNode.getHandleType() == MethodHandleType.INVOKE_STATIC) {
|
||||||
useClass(code, callMth.getDeclClass());
|
useClass(code, callMth.getDeclClass());
|
||||||
} else {
|
} else {
|
||||||
code.add("this");
|
addArg(code, customNode.getArg(0));
|
||||||
}
|
}
|
||||||
code.add("::").add(callMth.getAlias());
|
code.add("::").add(callMth.getAlias());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
|||||||
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
|
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.codegen.utils.CodeGenUtils;
|
||||||
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.JadxError;
|
import jadx.core.dex.attributes.nodes.JadxError;
|
||||||
@@ -43,7 +44,6 @@ import jadx.core.dex.trycatch.CatchAttr;
|
|||||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
import jadx.core.dex.visitors.DepthTraversal;
|
import jadx.core.dex.visitors.DepthTraversal;
|
||||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
import jadx.core.utils.CodeGenUtils;
|
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -12,14 +13,18 @@ import jadx.api.CommentsLevel;
|
|||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
import jadx.api.metadata.annotations.VarNode;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
|
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.core.clsp.ClspClass;
|
||||||
|
import jadx.core.codegen.utils.CodeGenUtils;
|
||||||
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.DeclareVariablesAttr;
|
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.instructions.SwitchInsn;
|
import jadx.core.dex.instructions.SwitchInsn;
|
||||||
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.CodeVar;
|
||||||
@@ -44,7 +49,6 @@ import jadx.core.dex.regions.loops.LoopRegion;
|
|||||||
import jadx.core.dex.regions.loops.LoopType;
|
import jadx.core.dex.regions.loops.LoopType;
|
||||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
import jadx.core.utils.BlockUtils;
|
import jadx.core.utils.BlockUtils;
|
||||||
import jadx.core.utils.CodeGenUtils;
|
|
||||||
import jadx.core.utils.RegionUtils;
|
import jadx.core.utils.RegionUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
@@ -268,19 +272,10 @@ public class RegionGen extends InsnGen {
|
|||||||
|
|
||||||
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) throws CodegenException {
|
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) throws CodegenException {
|
||||||
if (k instanceof FieldNode) {
|
if (k instanceof FieldNode) {
|
||||||
FieldNode fn = (FieldNode) k;
|
FieldNode fld = (FieldNode) k;
|
||||||
if (fn.getParentClass().isEnum()) {
|
useField(code, fld.getFieldInfo(), fld);
|
||||||
code.add(fn.getAlias());
|
} else if (k instanceof FieldInfo) {
|
||||||
} else {
|
useField(code, (FieldInfo) k, null);
|
||||||
staticField(code, fn.getFieldInfo());
|
|
||||||
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
|
|
||||||
// print original value, sometimes replaced with incorrect field
|
|
||||||
EncodedValue constVal = fn.get(JadxAttrType.CONSTANT_VALUE);
|
|
||||||
if (constVal != null && constVal.getValue() != null) {
|
|
||||||
code.add(" /* ").add(constVal.getValue().toString()).add(" */");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (k instanceof Integer) {
|
} else if (k instanceof Integer) {
|
||||||
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
|
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
|
||||||
} else {
|
} else {
|
||||||
@@ -288,6 +283,28 @@ public class RegionGen extends InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void useField(ICodeWriter code, FieldInfo fldInfo, @Nullable FieldNode fld) throws CodegenException {
|
||||||
|
boolean isEnum;
|
||||||
|
if (fld != null) {
|
||||||
|
isEnum = fld.getParentClass().isEnum();
|
||||||
|
} else {
|
||||||
|
ClspClass clsDetails = root.getClsp().getClsDetails(fldInfo.getDeclClass().getType());
|
||||||
|
isEnum = clsDetails != null && clsDetails.hasAccFlag(AccessFlags.ENUM);
|
||||||
|
}
|
||||||
|
if (isEnum) {
|
||||||
|
code.add(fldInfo.getAlias());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
staticField(code, fldInfo);
|
||||||
|
if (fld != null && mth.checkCommentsLevel(CommentsLevel.INFO)) {
|
||||||
|
// print original value, sometimes replaced with incorrect field
|
||||||
|
EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE);
|
||||||
|
if (constVal != null && constVal.getValue() != null) {
|
||||||
|
code.add(" /* ").add(constVal.getValue().toString()).add(" */");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void makeTryCatch(TryCatchRegion region, ICodeWriter code) throws CodegenException {
|
public void makeTryCatch(TryCatchRegion region, ICodeWriter code) throws CodegenException {
|
||||||
code.startLine("try {");
|
code.startLine("try {");
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class SimpleModeHelper {
|
|||||||
if (!block.contains(AFlag.EXC_BOTTOM_SPLITTER)) {
|
if (!block.contains(AFlag.EXC_BOTTOM_SPLITTER)) {
|
||||||
startLabel.set(block.getId());
|
startLabel.set(block.getId());
|
||||||
}
|
}
|
||||||
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) {
|
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlock(prev)) {
|
||||||
endGoto.set(prev.getId());
|
endGoto.set(prev.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ public class SimpleModeHelper {
|
|||||||
if (block.contains(AType.EXC_HANDLER)) {
|
if (block.contains(AType.EXC_HANDLER)) {
|
||||||
startLabel.set(block.getId());
|
startLabel.set(block.getId());
|
||||||
}
|
}
|
||||||
if (nextBlock == null && !mth.isPreExitBlocks(block)) {
|
if (nextBlock == null && !mth.isPreExitBlock(block)) {
|
||||||
endGoto.set(block.getId());
|
endGoto.set(block.getId());
|
||||||
}
|
}
|
||||||
prev = block;
|
prev = block;
|
||||||
@@ -145,7 +145,7 @@ public class SimpleModeHelper {
|
|||||||
// DFS sort blocks to reduce goto count
|
// DFS sort blocks to reduce goto count
|
||||||
private List<BlockNode> getSortedBlocks() {
|
private List<BlockNode> getSortedBlocks() {
|
||||||
List<BlockNode> list = new ArrayList<>(mth.getBasicBlocks().size());
|
List<BlockNode> list = new ArrayList<>(mth.getBasicBlocks().size());
|
||||||
BlockUtils.dfsVisit(mth, list::add);
|
BlockUtils.visitDFS(mth, list::add);
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import jadx.core.codegen.json.cls.JsonClass;
|
|||||||
import jadx.core.codegen.json.cls.JsonCodeLine;
|
import jadx.core.codegen.json.cls.JsonCodeLine;
|
||||||
import jadx.core.codegen.json.cls.JsonField;
|
import jadx.core.codegen.json.cls.JsonField;
|
||||||
import jadx.core.codegen.json.cls.JsonMethod;
|
import jadx.core.codegen.json.cls.JsonMethod;
|
||||||
|
import jadx.core.codegen.utils.CodeGenUtils;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
@@ -31,7 +32,6 @@ import jadx.core.dex.nodes.ClassNode;
|
|||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.utils.CodeGenUtils;
|
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ public class JsonCodeGen {
|
|||||||
jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), clsType -> getTypeAlias(classGen, clsType)));
|
jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), clsType -> getTypeAlias(classGen, clsType)));
|
||||||
}
|
}
|
||||||
|
|
||||||
ICodeWriter cw = new SimpleCodeWriter();
|
ICodeWriter cw = new SimpleCodeWriter(args);
|
||||||
CodeGenUtils.addErrorsAndComments(cw, cls);
|
CodeGenUtils.addErrorsAndComments(cw, cls);
|
||||||
classGen.addClassDeclaration(cw);
|
classGen.addClassDeclaration(cw);
|
||||||
jsonCls.setDeclaration(cw.getCodeStr());
|
jsonCls.setDeclaration(cw.getCodeStr());
|
||||||
@@ -130,7 +130,7 @@ public class JsonCodeGen {
|
|||||||
jsonField.setAlias(field.getAlias());
|
jsonField.setAlias(field.getAlias());
|
||||||
}
|
}
|
||||||
|
|
||||||
ICodeWriter cw = new SimpleCodeWriter();
|
ICodeWriter cw = new SimpleCodeWriter(args);
|
||||||
classGen.addField(cw, field);
|
classGen.addField(cw, field);
|
||||||
jsonField.setDeclaration(cw.getCodeStr());
|
jsonField.setDeclaration(cw.getCodeStr());
|
||||||
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
|
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
|
||||||
@@ -154,7 +154,7 @@ public class JsonCodeGen {
|
|||||||
jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), clsType -> getTypeAlias(classGen, clsType)));
|
jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), clsType -> getTypeAlias(classGen, clsType)));
|
||||||
|
|
||||||
MethodGen mthGen = new MethodGen(classGen, mth);
|
MethodGen mthGen = new MethodGen(classGen, mth);
|
||||||
ICodeWriter cw = new AnnotatedCodeWriter();
|
ICodeWriter cw = new AnnotatedCodeWriter(args);
|
||||||
mthGen.addDefinition(cw);
|
mthGen.addDefinition(cw);
|
||||||
jsonMth.setDeclaration(cw.getCodeStr());
|
jsonMth.setDeclaration(cw.getCodeStr());
|
||||||
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
|
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
|
||||||
@@ -181,7 +181,7 @@ public class JsonCodeGen {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] lines = codeStr.split(ICodeWriter.NL);
|
String[] lines = codeStr.split(args.getCodeNewLineStr());
|
||||||
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
|
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
|
||||||
ICodeMetadata metadata = code.getCodeMetadata();
|
ICodeMetadata metadata = code.getCodeMetadata();
|
||||||
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
|
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
|
||||||
@@ -189,7 +189,7 @@ public class JsonCodeGen {
|
|||||||
int linesCount = lines.length;
|
int linesCount = lines.length;
|
||||||
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
|
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
|
||||||
int lineStartPos = 0;
|
int lineStartPos = 0;
|
||||||
int newLineLen = ICodeWriter.NL.length();
|
int newLineLen = args.getCodeNewLineStr().length();
|
||||||
for (int i = 0; i < linesCount; i++) {
|
for (int i = 0; i < linesCount; i++) {
|
||||||
String codeLine = lines[i];
|
String codeLine = lines[i];
|
||||||
int line = i + 2;
|
int line = i + 2;
|
||||||
@@ -208,7 +208,7 @@ public class JsonCodeGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getTypeAlias(ClassGen classGen, ArgType clsType) {
|
private String getTypeAlias(ClassGen classGen, ArgType clsType) {
|
||||||
ICodeWriter code = new SimpleCodeWriter();
|
ICodeWriter code = new SimpleCodeWriter(args);
|
||||||
classGen.useType(code, clsType);
|
classGen.useType(code, clsType);
|
||||||
return code.getCodeStr();
|
return code.getCodeStr();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package jadx.core.codegen.utils;
|
||||||
|
|
||||||
|
import jadx.api.data.CommentStyle;
|
||||||
|
import jadx.api.data.ICodeComment;
|
||||||
|
|
||||||
|
public class CodeComment {
|
||||||
|
|
||||||
|
private final String comment;
|
||||||
|
private final CommentStyle style;
|
||||||
|
|
||||||
|
public CodeComment(String comment, CommentStyle style) {
|
||||||
|
this.comment = comment;
|
||||||
|
this.style = style;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeComment(ICodeComment comment) {
|
||||||
|
this(comment.getComment(), comment.getStyle());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComment() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommentStyle getStyle() {
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CodeComment{" + style + ": '" + comment + "'}";
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
-39
@@ -1,12 +1,13 @@
|
|||||||
package jadx.core.utils;
|
package jadx.core.codegen.utils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.metadata.ICodeAnnotation;
|
import jadx.api.data.CommentStyle;
|
||||||
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.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
@@ -20,6 +21,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
|
|||||||
import jadx.core.dex.instructions.args.SSAVar;
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.ICodeNode;
|
import jadx.core.dex.nodes.ICodeNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public class CodeGenUtils {
|
public class CodeGenUtils {
|
||||||
|
|
||||||
@@ -72,48 +74,36 @@ public class CodeGenUtils {
|
|||||||
if (node == null) {
|
if (node == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<String> comments = node.getAll(AType.CODE_COMMENTS);
|
boolean startNewLine = node instanceof ICodeNode; // add on same line for instructions
|
||||||
if (comments.isEmpty()) {
|
for (CodeComment comment : node.getAll(AType.CODE_COMMENTS)) {
|
||||||
return;
|
addCodeComment(code, comment, startNewLine);
|
||||||
}
|
}
|
||||||
if (node instanceof ICodeNode) {
|
}
|
||||||
// for classes, fields and methods add one line before node declaration
|
|
||||||
|
private static void addCodeComment(ICodeWriter code, CodeComment comment, boolean startNewLine) {
|
||||||
|
if (startNewLine) {
|
||||||
code.startLine();
|
code.startLine();
|
||||||
} else {
|
} else {
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
}
|
}
|
||||||
if (comments.size() == 1) {
|
CommentStyle style = comment.getStyle();
|
||||||
String comment = comments.get(0);
|
appendMultiLineString(code, "", style.getStart());
|
||||||
if (!comment.contains("\n")) {
|
appendMultiLineString(code, style.getOnNewLine(), comment.getComment());
|
||||||
code.add("// ").add(comment);
|
appendMultiLineString(code, "", style.getEnd());
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addMultiLineComment(code, comments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addMultiLineComment(ICodeWriter code, List<String> comments) {
|
private static final Pattern NEW_LINE_PATTERN = Pattern.compile("\\R");
|
||||||
boolean first = true;
|
|
||||||
String indent = "";
|
private static void appendMultiLineString(ICodeWriter code, String onNewLine, String str) {
|
||||||
ICodeAnnotation lineAnn = null;
|
String[] lines = NEW_LINE_PATTERN.split(str);
|
||||||
for (String comment : comments) {
|
int linesCount = lines.length;
|
||||||
for (String line : comment.split("\n")) {
|
if (linesCount == 0) {
|
||||||
if (first) {
|
return;
|
||||||
first = false;
|
}
|
||||||
StringBuilder buf = code.getRawBuf();
|
code.add(lines[0]);
|
||||||
int startLinePos = buf.lastIndexOf(ICodeWriter.NL) + 1;
|
for (int i = 1; i < linesCount; i++) {
|
||||||
indent = Utils.strRepeat(" ", buf.length() - startLinePos);
|
code.startLine(onNewLine);
|
||||||
if (code.isMetadataSupported()) {
|
code.add(lines[i]);
|
||||||
lineAnn = code.getRawAnnotations().get(startLinePos);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
code.newLine().add(indent);
|
|
||||||
if (lineAnn != null) {
|
|
||||||
code.attachLineAnnotation(lineAnn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
code.add("// ").add(line);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,8 +114,7 @@ public class CodeGenUtils {
|
|||||||
code.startLine("/* renamed from: ").add(origName);
|
code.startLine("/* renamed from: ").add(origName);
|
||||||
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
|
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
|
||||||
if (renameReasonAttr != null) {
|
if (renameReasonAttr != null) {
|
||||||
code.add(" reason: ");
|
code.add(", reason: ").add(renameReasonAttr.getDescription());
|
||||||
code.add(renameReasonAttr.getDescription());
|
|
||||||
}
|
}
|
||||||
code.add(" */");
|
code.add(" */");
|
||||||
}
|
}
|
||||||
@@ -106,4 +106,5 @@ public enum AFlag {
|
|||||||
DONT_UNLOAD_CLASS, // don't unload class after code generation (only for tests and debug!)
|
DONT_UNLOAD_CLASS, // don't unload class after code generation (only for tests and debug!)
|
||||||
|
|
||||||
RESOLVE_JAVA_JSR,
|
RESOLVE_JAVA_JSR,
|
||||||
|
COMPUTE_POST_DOM,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +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.codegen.utils.CodeComment;
|
||||||
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
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;
|
||||||
@@ -44,7 +45,7 @@ import jadx.core.dex.trycatch.TryCatchBlockAttr;
|
|||||||
public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
|
public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
|
||||||
|
|
||||||
// class, method, field, insn
|
// class, method, field, insn
|
||||||
public static final AType<AttrList<String>> CODE_COMMENTS = new AType<>();
|
public static final AType<AttrList<CodeComment>> CODE_COMMENTS = new AType<>();
|
||||||
|
|
||||||
// class, method, field
|
// class, method, field
|
||||||
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
|
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
|
||||||
|
|||||||
@@ -139,14 +139,12 @@ public abstract class AttrNode implements IAttributeNode {
|
|||||||
storage = EMPTY_ATTR_STORAGE;
|
storage = EMPTY_ATTR_STORAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all attribute
|
|
||||||
*/
|
|
||||||
public void unloadAttributes() {
|
public void unloadAttributes() {
|
||||||
if (storage == EMPTY_ATTR_STORAGE) {
|
if (storage == EMPTY_ATTR_STORAGE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
storage.unloadAttributes();
|
storage.unloadAttributes();
|
||||||
|
storage.clearFlags();
|
||||||
unloadIfEmpty();
|
unloadIfEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,6 +102,10 @@ public class AttributeStorage {
|
|||||||
flags.remove(flag);
|
flags.remove(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearFlags() {
|
||||||
|
flags.clear();
|
||||||
|
}
|
||||||
|
|
||||||
public <T extends IJadxAttribute> void remove(IJadxAttrType<T> type) {
|
public <T extends IJadxAttribute> void remove(IJadxAttrType<T> type) {
|
||||||
if (!attributes.isEmpty()) {
|
if (!attributes.isEmpty()) {
|
||||||
writeAttributes(map -> map.remove(type));
|
writeAttributes(map -> map.remove(type));
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public class JadxError implements Comparable<JadxError> {
|
public class JadxError implements Comparable<JadxError> {
|
||||||
@@ -55,7 +54,7 @@ public class JadxError implements Comparable<JadxError> {
|
|||||||
str.append(cause.getClass());
|
str.append(cause.getClass());
|
||||||
str.append(':');
|
str.append(':');
|
||||||
str.append(cause.getMessage());
|
str.append(cause.getMessage());
|
||||||
str.append(ICodeWriter.NL);
|
str.append('\n');
|
||||||
str.append(Utils.getStackTrace(cause));
|
str.append(Utils.getStackTrace(cause));
|
||||||
}
|
}
|
||||||
return str.toString();
|
return str.toString();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package jadx.core.dex.attributes.nodes;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
|
||||||
import jadx.api.plugins.input.data.ILocalVar;
|
import jadx.api.plugins.input.data.ILocalVar;
|
||||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
@@ -26,6 +25,6 @@ public class LocalVarsDebugInfoAttr implements IJadxAttribute {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Debug Info:" + ICodeWriter.NL + " " + Utils.listToString(localVars, ICodeWriter.NL + " ");
|
return "Debug Info:\n " + Utils.listToString(localVars, "\n ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package jadx.core.dex.attributes.nodes;
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.data.CommentStyle;
|
||||||
|
import jadx.core.codegen.utils.CodeComment;
|
||||||
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.nodes.ICodeNode;
|
import jadx.core.dex.nodes.ICodeNode;
|
||||||
@@ -25,7 +26,11 @@ public abstract class NotificationAttrNode extends LineAttrNode implements ICode
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addCodeComment(String comment) {
|
public void addCodeComment(String comment) {
|
||||||
addAttr(AType.CODE_COMMENTS, comment);
|
addAttr(AType.CODE_COMMENTS, new CodeComment(comment, CommentStyle.LINE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCodeComment(String comment, CommentStyle style) {
|
||||||
|
addAttr(AType.CODE_COMMENTS, new CodeComment(comment, style));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addWarnComment(String warn) {
|
public void addWarnComment(String warn) {
|
||||||
@@ -33,7 +38,7 @@ public abstract class NotificationAttrNode extends LineAttrNode implements ICode
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addWarnComment(String warn, Throwable exc) {
|
public void addWarnComment(String warn, Throwable exc) {
|
||||||
String commentStr = warn + ICodeWriter.NL + Utils.getStackTrace(exc);
|
String commentStr = warn + root().getArgs().getCodeNewLineStr() + Utils.getStackTrace(exc);
|
||||||
JadxCommentsAttr.add(this, CommentsLevel.WARN, commentStr);
|
JadxCommentsAttr.add(this, CommentsLevel.WARN, commentStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package jadx.core.dex.attributes.nodes;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
|
||||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.instructions.PhiInsn;
|
import jadx.core.dex.instructions.PhiInsn;
|
||||||
@@ -33,7 +32,7 @@ public class PhiListAttr implements IJadxAttribute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (PhiInsn phiInsn : list) {
|
for (PhiInsn phiInsn : list) {
|
||||||
sb.append(ICodeWriter.NL).append(" ").append(phiInsn);
|
sb.append('\n').append(" ").append(phiInsn);
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,47 +2,44 @@ package jadx.core.dex.info;
|
|||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
|
||||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
|
||||||
import jadx.core.dex.instructions.args.LiteralArg;
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.IFieldInfoRef;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.dex.visitors.prepare.CollectConstValues;
|
||||||
|
|
||||||
public class ConstStorage {
|
public class ConstStorage {
|
||||||
|
|
||||||
private static final class ValueStorage {
|
private static final class ValueStorage {
|
||||||
private final Map<Object, FieldNode> values = new ConcurrentHashMap<>();
|
private final Map<Object, IFieldInfoRef> values = new ConcurrentHashMap<>();
|
||||||
private final Set<Object> duplicates = new HashSet<>();
|
private final Set<Object> duplicates = new HashSet<>();
|
||||||
|
|
||||||
public Map<Object, FieldNode> getValues() {
|
public Map<Object, IFieldInfoRef> getValues() {
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FieldNode get(Object key) {
|
public IFieldInfoRef get(Object key) {
|
||||||
return values.get(key);
|
return values.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if this value is duplicated
|
* @return true if this value is duplicated
|
||||||
*/
|
*/
|
||||||
public boolean put(Object value, FieldNode fld) {
|
public boolean put(Object value, IFieldInfoRef fld) {
|
||||||
if (duplicates.contains(value)) {
|
if (duplicates.contains(value)) {
|
||||||
values.remove(value);
|
values.remove(value);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
FieldNode prev = values.put(value, fld);
|
IFieldInfoRef prev = values.put(value, fld);
|
||||||
if (prev != null) {
|
if (prev != null) {
|
||||||
values.remove(value);
|
values.remove(value);
|
||||||
duplicates.add(value);
|
duplicates.add(value);
|
||||||
@@ -56,14 +53,13 @@ public class ConstStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void removeForCls(ClassNode cls) {
|
void removeForCls(ClassNode cls) {
|
||||||
Iterator<Entry<Object, FieldNode>> it = values.entrySet().iterator();
|
values.entrySet().removeIf(entry -> {
|
||||||
while (it.hasNext()) {
|
IFieldInfoRef field = entry.getValue();
|
||||||
Entry<Object, FieldNode> entry = it.next();
|
if (field instanceof FieldNode) {
|
||||||
FieldNode field = entry.getValue();
|
return ((FieldNode) field).getParentClass().equals(cls);
|
||||||
if (field.getParentClass().equals(cls)) {
|
|
||||||
it.remove();
|
|
||||||
}
|
}
|
||||||
}
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,27 +73,24 @@ public class ConstStorage {
|
|||||||
this.replaceEnabled = args.isReplaceConsts();
|
this.replaceEnabled = args.isReplaceConsts();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processConstFields(ClassNode cls, List<FieldNode> staticFields) {
|
public void addConstField(FieldNode fld, Object value, boolean isPublic) {
|
||||||
if (!replaceEnabled || staticFields.isEmpty()) {
|
if (isPublic) {
|
||||||
return;
|
addGlobalConstField(fld, value);
|
||||||
}
|
} else {
|
||||||
for (FieldNode f : staticFields) {
|
getClsValues(fld.getParentClass()).put(value, fld);
|
||||||
Object value = getFieldConstValue(f);
|
|
||||||
if (value != null) {
|
|
||||||
addConstField(cls, f, value, f.getAccessFlags().isPublic());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addGlobalConstField(IFieldInfoRef fld, Object value) {
|
||||||
|
globalValues.put(value, fld);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use method from CollectConstValues class
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public static @Nullable Object getFieldConstValue(FieldNode fld) {
|
public static @Nullable Object getFieldConstValue(FieldNode fld) {
|
||||||
AccessInfo accFlags = fld.getAccessFlags();
|
return CollectConstValues.getFieldConstValue(fld);
|
||||||
if (accFlags.isStatic() && accFlags.isFinal()) {
|
|
||||||
EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE);
|
|
||||||
if (constVal != null) {
|
|
||||||
return constVal.getValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeForClass(ClassNode cls) {
|
public void removeForClass(ClassNode cls) {
|
||||||
@@ -105,20 +98,11 @@ public class ConstStorage {
|
|||||||
globalValues.removeForCls(cls);
|
globalValues.removeForCls(cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addConstField(ClassNode cls, FieldNode fld, Object value, boolean isPublic) {
|
|
||||||
if (isPublic) {
|
|
||||||
globalValues.put(value, fld);
|
|
||||||
} else {
|
|
||||||
getClsValues(cls).put(value, fld);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValueStorage getClsValues(ClassNode cls) {
|
private ValueStorage getClsValues(ClassNode cls) {
|
||||||
return classes.computeIfAbsent(cls, c -> new ValueStorage());
|
return classes.computeIfAbsent(cls, c -> new ValueStorage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
public @Nullable IFieldInfoRef getConstField(ClassNode cls, Object value, boolean searchGlobal) {
|
||||||
public FieldNode getConstField(ClassNode cls, Object value, boolean searchGlobal) {
|
|
||||||
if (!replaceEnabled) {
|
if (!replaceEnabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -137,7 +121,7 @@ public class ConstStorage {
|
|||||||
while (current != null) {
|
while (current != null) {
|
||||||
ValueStorage classValues = classes.get(current);
|
ValueStorage classValues = classes.get(current);
|
||||||
if (classValues != null) {
|
if (classValues != null) {
|
||||||
FieldNode field = classValues.get(value);
|
IFieldInfoRef field = classValues.get(value);
|
||||||
if (field != null) {
|
if (field != null) {
|
||||||
if (foundInGlobal) {
|
if (foundInGlobal) {
|
||||||
return null;
|
return null;
|
||||||
@@ -182,8 +166,7 @@ public class ConstStorage {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
public @Nullable IFieldInfoRef getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
|
||||||
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
|
|
||||||
if (!replaceEnabled) {
|
if (!replaceEnabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -225,7 +208,7 @@ public class ConstStorage {
|
|||||||
return resourcesNames;
|
return resourcesNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Object, FieldNode> getGlobalConstFields() {
|
public Map<Object, IFieldInfoRef> getGlobalConstFields() {
|
||||||
return globalValues.getValues();
|
return globalValues.getValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import java.util.Objects;
|
|||||||
import jadx.api.plugins.input.data.IFieldRef;
|
import jadx.api.plugins.input.data.IFieldRef;
|
||||||
import jadx.core.codegen.TypeGen;
|
import jadx.core.codegen.TypeGen;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.IFieldInfoRef;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
|
||||||
public final class FieldInfo {
|
public final class FieldInfo implements IFieldInfoRef {
|
||||||
|
|
||||||
private final ClassInfo declClass;
|
private final ClassInfo declClass;
|
||||||
private final String name;
|
private final String name;
|
||||||
@@ -76,6 +77,11 @@ public final class FieldInfo {
|
|||||||
return name.equals(other.name) && type.equals(other.type);
|
return name.equals(other.name) && type.equals(other.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FieldInfo getFieldInfo() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package jadx.core.dex.instructions;
|
|||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.utils.InsnUtils;
|
import jadx.core.utils.InsnUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
@@ -23,6 +24,10 @@ public class IndexInsnNode extends InsnNode {
|
|||||||
this.index = index;
|
this.index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ArgType getIndexAsType() {
|
||||||
|
return (ArgType) index;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IndexInsnNode copy() {
|
public IndexInsnNode copy() {
|
||||||
return copyCommonParams(new IndexInsnNode(insnType, index, getArgsCount()));
|
return copyCommonParams(new IndexInsnNode(insnType, index, getArgsCount()));
|
||||||
|
|||||||
@@ -521,6 +521,7 @@ public class InsnDecoder {
|
|||||||
if (payload != null) {
|
if (payload != null) {
|
||||||
swInsn.attachSwitchData(new SwitchData((ISwitchPayload) payload), insn.getTarget());
|
swInsn.attachSwitchData(new SwitchData((ISwitchPayload) payload), insn.getTarget());
|
||||||
}
|
}
|
||||||
|
method.add(AFlag.COMPUTE_POST_DOM);
|
||||||
return swInsn;
|
return swInsn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,6 @@ public class InvokeNode extends BaseInvokeNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return baseString() + " type: " + type + " call: " + mth + attributesString();
|
return baseString() + " " + type + " call: " + mth + attributesString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.util.List;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
@@ -111,20 +110,20 @@ public class SwitchInsn extends TargetInsnNode {
|
|||||||
int[] keys = switchData.getKeys();
|
int[] keys = switchData.getKeys();
|
||||||
if (targetBlocks != null) {
|
if (targetBlocks != null) {
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
sb.append(ICodeWriter.NL);
|
sb.append('\n');
|
||||||
sb.append(" case ").append(keys[i]).append(": goto ").append(targetBlocks[i]);
|
sb.append(" case ").append(keys[i]).append(": goto ").append(targetBlocks[i]);
|
||||||
}
|
}
|
||||||
if (def != -1) {
|
if (def != -1) {
|
||||||
sb.append(ICodeWriter.NL).append(" default: goto ").append(defTargetBlock);
|
sb.append('\n').append(" default: goto ").append(defTargetBlock);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int[] targets = switchData.getTargets();
|
int[] targets = switchData.getTargets();
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
sb.append(ICodeWriter.NL);
|
sb.append('\n');
|
||||||
sb.append(" case ").append(keys[i]).append(": goto ").append(InsnUtils.formatOffset(targets[i]));
|
sb.append(" case ").append(keys[i]).append(": goto ").append(InsnUtils.formatOffset(targets[i]));
|
||||||
}
|
}
|
||||||
if (def != -1) {
|
if (def != -1) {
|
||||||
sb.append(ICodeWriter.NL);
|
sb.append('\n');
|
||||||
sb.append(" default: goto ").append(InsnUtils.formatOffset(def));
|
sb.append(" default: goto ").append(InsnUtils.formatOffset(def));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import jadx.core.utils.InsnRemover;
|
|||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instruction argument,
|
* Instruction argument.
|
||||||
* argument can be register, literal or instruction
|
* Can be: register, literal, instruction or name
|
||||||
*/
|
*/
|
||||||
public abstract class InsnArg extends Typed {
|
public abstract class InsnArg extends Typed {
|
||||||
|
|
||||||
@@ -132,6 +132,10 @@ public abstract class InsnArg extends Typed {
|
|||||||
}
|
}
|
||||||
InsnArg arg = wrapInsnIntoArg(insn);
|
InsnArg arg = wrapInsnIntoArg(insn);
|
||||||
InsnArg oldArg = parent.getArg(i);
|
InsnArg oldArg = parent.getArg(i);
|
||||||
|
if (arg.getType() == ArgType.UNKNOWN) {
|
||||||
|
// restore arg type if wrapped insn missing result
|
||||||
|
arg.setType(oldArg.getType());
|
||||||
|
}
|
||||||
parent.setArg(i, arg);
|
parent.setArg(i, arg);
|
||||||
InsnRemover.unbindArgUsage(mth, oldArg);
|
InsnRemover.unbindArgUsage(mth, oldArg);
|
||||||
if (unbind) {
|
if (unbind) {
|
||||||
@@ -209,7 +213,20 @@ public abstract class InsnArg extends Typed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isZeroLiteral() {
|
public boolean isZeroLiteral() {
|
||||||
return isLiteral() && (((LiteralArg) this)).getLiteral() == 0;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isZeroConst() {
|
||||||
|
if (isZeroLiteral()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isInsnWrap()) {
|
||||||
|
InsnNode wrapInsn = ((InsnWrapArg) this).getWrapInsn();
|
||||||
|
if (wrapInsn.getType() == InsnType.CONST) {
|
||||||
|
return wrapInsn.getArg(0).isZeroLiteral();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFalse() {
|
public boolean isFalse() {
|
||||||
@@ -265,6 +282,9 @@ public abstract class InsnArg extends Typed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSameVar(RegisterArg arg) {
|
public boolean isSameVar(RegisterArg arg) {
|
||||||
|
if (arg == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (isRegister()) {
|
if (isRegister()) {
|
||||||
return ((RegisterArg) this).sameRegAndSVar(arg);
|
return ((RegisterArg) this).sameRegAndSVar(arg);
|
||||||
}
|
}
|
||||||
@@ -280,4 +300,8 @@ public abstract class InsnArg extends Typed {
|
|||||||
public InsnArg duplicate() {
|
public InsnArg duplicate() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toShortString() {
|
||||||
|
return this.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package jadx.core.dex.instructions.args;
|
package jadx.core.dex.instructions.args;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import jadx.core.dex.instructions.ConstStringNode;
|
import jadx.core.dex.instructions.ConstStringNode;
|
||||||
@@ -75,10 +73,18 @@ public final class InsnWrapArg extends InsnArg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toShortString() {
|
||||||
if (wrappedInsn.getType() == InsnType.CONST_STR && Objects.equals(type, ArgType.STRING)) {
|
if (wrappedInsn.getType() == InsnType.CONST_STR) {
|
||||||
return "(\"" + ((ConstStringNode) wrappedInsn).getString() + "\")";
|
return "(\"" + ((ConstStringNode) wrappedInsn).getString() + "\")";
|
||||||
}
|
}
|
||||||
return "(wrap: " + type + " : " + wrappedInsn + ')';
|
return "(wrap:" + type + ":" + wrappedInsn.getType() + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (wrappedInsn.getType() == InsnType.CONST_STR) {
|
||||||
|
return "(\"" + ((ConstStringNode) wrappedInsn).getString() + "\")";
|
||||||
|
}
|
||||||
|
return "(wrap:" + type + ":" + wrappedInsn + ')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ public final class LiteralArg extends InsnArg {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isZeroLiteral() {
|
||||||
|
return literal == 0;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isInteger() {
|
public boolean isInteger() {
|
||||||
switch (type.getPrimitiveType()) {
|
switch (type.getPrimitiveType()) {
|
||||||
case INT:
|
case INT:
|
||||||
@@ -125,6 +130,11 @@ public final class LiteralArg extends InsnArg {
|
|||||||
return literal == that.literal && getType().equals(that.getType());
|
return literal == that.literal && getType().equals(that.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toShortString() {
|
||||||
|
return Long.toString(literal);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ public final class NamedArg extends InsnArg implements Named {
|
|||||||
return name.equals(((NamedArg) o).name);
|
return name.equals(((NamedArg) o).name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toShortString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return '(' + name + ' ' + type + ')';
|
return '(' + name + ' ' + type + ')';
|
||||||
|
|||||||
@@ -215,6 +215,16 @@ public class RegisterArg extends InsnArg implements Named {
|
|||||||
&& Objects.equals(sVar, other.getSVar());
|
&& Objects.equals(sVar, other.getSVar());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toShortString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("r").append(regNum);
|
||||||
|
if (sVar != null) {
|
||||||
|
sb.append('v').append(sVar.getVersion());
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package jadx.core.dex.instructions.args;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -23,9 +24,12 @@ import jadx.core.dex.visitors.typeinference.TypeInfo;
|
|||||||
import jadx.core.utils.StringUtils;
|
import jadx.core.utils.StringUtils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public class SSAVar {
|
public class SSAVar implements Comparable<SSAVar> {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SSAVar.class);
|
private static final Logger LOG = LoggerFactory.getLogger(SSAVar.class);
|
||||||
|
|
||||||
|
private static final Comparator<SSAVar> SSA_VAR_COMPARATOR =
|
||||||
|
Comparator.comparingInt(SSAVar::getRegNum).thenComparingInt(SSAVar::getVersion);
|
||||||
|
|
||||||
private final int regNum;
|
private final int regNum;
|
||||||
private final int version;
|
private final int version;
|
||||||
|
|
||||||
@@ -256,34 +260,6 @@ public class SSAVar {
|
|||||||
return codeVar != null;
|
return codeVar != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!(o instanceof SSAVar)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
SSAVar ssaVar = (SSAVar) o;
|
|
||||||
return regNum == ssaVar.regNum && version == ssaVar.version;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return 31 * regNum + version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toShortString() {
|
|
||||||
return "r" + regNum + 'v' + version;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return toShortString()
|
|
||||||
+ (StringUtils.notEmpty(getName()) ? " '" + getName() + "' " : "")
|
|
||||||
+ ' ' + typeInfo.getType();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDetailedVarInfo(MethodNode mth) {
|
public String getDetailedVarInfo(MethodNode mth) {
|
||||||
Set<ArgType> types = new HashSet<>();
|
Set<ArgType> types = new HashSet<>();
|
||||||
Set<String> names = Collections.emptySet();
|
Set<String> names = Collections.emptySet();
|
||||||
@@ -323,4 +299,37 @@ public class SSAVar {
|
|||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof SSAVar)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SSAVar ssaVar = (SSAVar) o;
|
||||||
|
return regNum == ssaVar.regNum && version == ssaVar.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return 31 * regNum + version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(@NotNull SSAVar o) {
|
||||||
|
return SSA_VAR_COMPARATOR.compare(this, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toShortString() {
|
||||||
|
return "r" + regNum + 'v' + version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return toShortString()
|
||||||
|
+ (StringUtils.notEmpty(getName()) ? " '" + getName() + "' " : "")
|
||||||
|
+ ' ' + typeInfo.getType();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
|||||||
*/
|
*/
|
||||||
private BitSet doms = EmptyBitSet.EMPTY;
|
private BitSet doms = EmptyBitSet.EMPTY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post dominators, excluding self
|
||||||
|
*/
|
||||||
|
private BitSet postDoms = EmptyBitSet.EMPTY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dominance frontier
|
* Dominance frontier
|
||||||
*/
|
*/
|
||||||
@@ -56,6 +61,11 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
|||||||
*/
|
*/
|
||||||
private BlockNode idom;
|
private BlockNode idom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediate post dominator
|
||||||
|
*/
|
||||||
|
private BlockNode iPostDom;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blocks on which dominates this block
|
* Blocks on which dominates this block
|
||||||
*/
|
*/
|
||||||
@@ -165,6 +175,14 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
|||||||
this.doms = doms;
|
this.doms = doms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BitSet getPostDoms() {
|
||||||
|
return postDoms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPostDoms(BitSet postDoms) {
|
||||||
|
this.postDoms = postDoms;
|
||||||
|
}
|
||||||
|
|
||||||
public BitSet getDomFrontier() {
|
public BitSet getDomFrontier() {
|
||||||
return domFrontier;
|
return domFrontier;
|
||||||
}
|
}
|
||||||
@@ -184,6 +202,14 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
|||||||
this.idom = idom;
|
this.idom = idom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BlockNode getIPostDom() {
|
||||||
|
return iPostDom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIPostDom(BlockNode iPostDom) {
|
||||||
|
this.iPostDom = iPostDom;
|
||||||
|
}
|
||||||
|
|
||||||
public List<BlockNode> getDominatesOn() {
|
public List<BlockNode> getDominatesOn() {
|
||||||
return dominatesOn;
|
return dominatesOn;
|
||||||
}
|
}
|
||||||
@@ -233,6 +259,6 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "B:" + cid + ':' + InsnUtils.formatOffset(startOffset);
|
return "B:" + id + ':' + InsnUtils.formatOffset(startOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -19,10 +18,12 @@ import org.slf4j.LoggerFactory;
|
|||||||
import jadx.api.DecompilationMode;
|
import jadx.api.DecompilationMode;
|
||||||
import jadx.api.ICodeCache;
|
import jadx.api.ICodeCache;
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
import jadx.api.impl.SimpleCodeInfo;
|
import jadx.api.impl.SimpleCodeInfo;
|
||||||
|
import jadx.api.impl.SimpleCodeWriter;
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||||
import jadx.api.plugins.input.data.IClassData;
|
import jadx.api.plugins.input.data.IClassData;
|
||||||
import jadx.api.plugins.input.data.IFieldData;
|
import jadx.api.plugins.input.data.IFieldData;
|
||||||
import jadx.api.plugins.input.data.IMethodData;
|
import jadx.api.plugins.input.data.IMethodData;
|
||||||
@@ -246,19 +247,15 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
if (fields.isEmpty()) {
|
if (fields.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<FieldNode> staticFields = fields.stream().filter(FieldNode::isStatic).collect(Collectors.toList());
|
// bytecode can omit field initialization to 0 (of any type)
|
||||||
for (FieldNode f : staticFields) {
|
// add explicit init to all static final fields
|
||||||
if (f.getAccessFlags().isFinal() && f.get(JadxAttrType.CONSTANT_VALUE) == null) {
|
// incorrect initializations will be removed if assign found in class init
|
||||||
// incorrect initialization will be removed if assign found in constructor
|
for (FieldNode fld : fields) {
|
||||||
f.addAttr(EncodedValue.NULL);
|
AccessInfo accFlags = fld.getAccessFlags();
|
||||||
|
if (accFlags.isStatic() && accFlags.isFinal() && fld.get(JadxAttrType.CONSTANT_VALUE) == null) {
|
||||||
|
fld.addAttr(EncodedValue.NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
// process const fields
|
|
||||||
root().getConstValues().processConstFields(this, staticFields);
|
|
||||||
} catch (Exception e) {
|
|
||||||
this.addWarnComment("Failed to load initial values for static fields", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkSourceFilenameAttr() {
|
private boolean checkSourceFilenameAttr() {
|
||||||
@@ -312,6 +309,8 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
return decompile(true);
|
return decompile(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Object DECOMPILE_WITH_MODE_SYNC = new Object();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WARNING: Slow operation! Use with caution!
|
* WARNING: Slow operation! Use with caution!
|
||||||
*/
|
*/
|
||||||
@@ -320,15 +319,18 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
if (mode == baseMode) {
|
if (mode == baseMode) {
|
||||||
return decompile(true);
|
return decompile(true);
|
||||||
}
|
}
|
||||||
JadxArgs args = root.getArgs();
|
synchronized (DECOMPILE_WITH_MODE_SYNC) {
|
||||||
try {
|
JadxArgs args = root.getArgs();
|
||||||
unload();
|
try {
|
||||||
args.setDecompilationMode(mode);
|
unload();
|
||||||
ProcessClass process = new ProcessClass(args);
|
args.setDecompilationMode(mode);
|
||||||
process.initPasses(root);
|
ProcessClass process = new ProcessClass(args);
|
||||||
return process.generateCode(this);
|
process.initPasses(root);
|
||||||
} finally {
|
return process.generateCode(this);
|
||||||
args.setDecompilationMode(baseMode);
|
} finally {
|
||||||
|
args.setDecompilationMode(baseMode);
|
||||||
|
unload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,22 +385,46 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ICodeInfo codeInfo;
|
ICodeInfo codeInfo = generateClassCode();
|
||||||
try {
|
|
||||||
if (Consts.DEBUG) {
|
|
||||||
LOG.debug("Decompiling class: {}", this);
|
|
||||||
}
|
|
||||||
codeInfo = root.getProcessClasses().generateCode(this);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
addError("Code generation failed", e);
|
|
||||||
codeInfo = new SimpleCodeInfo(Utils.getStackTrace(e));
|
|
||||||
}
|
|
||||||
if (codeInfo != ICodeInfo.EMPTY) {
|
if (codeInfo != ICodeInfo.EMPTY) {
|
||||||
codeCache.add(clsRawName, codeInfo);
|
codeCache.add(clsRawName, codeInfo);
|
||||||
}
|
}
|
||||||
return codeInfo;
|
return codeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ICodeInfo generateClassCode() {
|
||||||
|
try {
|
||||||
|
if (Consts.DEBUG) {
|
||||||
|
LOG.debug("Decompiling class: {}", this);
|
||||||
|
}
|
||||||
|
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
|
||||||
|
processDefinitionAnnotations(codeInfo);
|
||||||
|
return codeInfo;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
addError("Code generation failed", e);
|
||||||
|
return new SimpleCodeInfo(Utils.getStackTrace(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save node definition positions found in code
|
||||||
|
*/
|
||||||
|
private static void processDefinitionAnnotations(ICodeInfo codeInfo) {
|
||||||
|
Map<Integer, ICodeAnnotation> annotations = codeInfo.getCodeMetadata().getAsMap();
|
||||||
|
if (annotations.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Map.Entry<Integer, ICodeAnnotation> entry : annotations.entrySet()) {
|
||||||
|
ICodeAnnotation ann = entry.getValue();
|
||||||
|
if (ann.getAnnType() == AnnType.DECLARATION) {
|
||||||
|
NodeDeclareRef declareRef = (NodeDeclareRef) ann;
|
||||||
|
int pos = entry.getKey();
|
||||||
|
declareRef.setDefPos(pos);
|
||||||
|
declareRef.getNode().setDefPosition(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public ICodeInfo getCodeFromCache() {
|
public ICodeInfo getCodeFromCache() {
|
||||||
ICodeCache codeCache = root().getCodeCache();
|
ICodeCache codeCache = root().getCodeCache();
|
||||||
@@ -432,7 +458,7 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
}
|
}
|
||||||
methods.forEach(MethodNode::unload);
|
methods.forEach(MethodNode::unload);
|
||||||
innerClasses.forEach(ClassNode::unload);
|
innerClasses.forEach(ClassNode::unload);
|
||||||
fields.forEach(FieldNode::unloadAttributes);
|
fields.forEach(FieldNode::unload);
|
||||||
unloadAttributes();
|
unloadAttributes();
|
||||||
setState(NOT_LOADED);
|
setState(NOT_LOADED);
|
||||||
this.loadStage = LoadStage.NONE;
|
this.loadStage = LoadStage.NONE;
|
||||||
@@ -482,17 +508,15 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
fields.add(fld);
|
fields.add(fld);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FieldNode getConstField(Object obj) {
|
public @Nullable IFieldInfoRef getConstField(Object obj) {
|
||||||
return getConstField(obj, true);
|
return getConstField(obj, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
public @Nullable IFieldInfoRef getConstField(Object obj, boolean searchGlobal) {
|
||||||
public FieldNode getConstField(Object obj, boolean searchGlobal) {
|
|
||||||
return root().getConstValues().getConstField(this, obj, searchGlobal);
|
return root().getConstValues().getConstField(this, obj, searchGlobal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
public @Nullable IFieldInfoRef getConstFieldByLiteralArg(LiteralArg arg) {
|
||||||
public FieldNode getConstFieldByLiteralArg(LiteralArg arg) {
|
|
||||||
return root().getConstValues().getConstFieldByLiteralArg(this, arg);
|
return root().getConstValues().getConstFieldByLiteralArg(this, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -807,33 +831,29 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
|
|
||||||
public String getDisassembledCode() {
|
public String getDisassembledCode() {
|
||||||
if (smali == null) {
|
if (smali == null) {
|
||||||
StringBuilder sb = new StringBuilder();
|
SimpleCodeWriter code = new SimpleCodeWriter(root.getArgs());
|
||||||
getDisassembledCode(sb);
|
getDisassembledCode(code);
|
||||||
sb.append(ICodeWriter.NL);
|
|
||||||
Set<ClassNode> allInlinedClasses = new LinkedHashSet<>();
|
Set<ClassNode> allInlinedClasses = new LinkedHashSet<>();
|
||||||
getInnerAndInlinedClassesRecursive(allInlinedClasses);
|
getInnerAndInlinedClassesRecursive(allInlinedClasses);
|
||||||
for (ClassNode innerClass : allInlinedClasses) {
|
for (ClassNode innerClass : allInlinedClasses) {
|
||||||
innerClass.getDisassembledCode(sb);
|
innerClass.getDisassembledCode(code);
|
||||||
sb.append(ICodeWriter.NL);
|
|
||||||
}
|
}
|
||||||
smali = sb.toString();
|
smali = code.finish().getCodeStr();
|
||||||
}
|
}
|
||||||
return smali;
|
return smali;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void getDisassembledCode(StringBuilder sb) {
|
protected void getDisassembledCode(SimpleCodeWriter code) {
|
||||||
if (clsData == null) {
|
if (clsData == null) {
|
||||||
sb.append(String.format("###### Class %s is created by jadx", getFullName()));
|
code.startLine(String.format("###### Class %s is created by jadx", getFullName()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sb.append(String.format("###### Class %s (%s)", getFullName(), getRawName()));
|
code.startLine(String.format("###### Class %s (%s)", getFullName(), getRawName()));
|
||||||
sb.append(ICodeWriter.NL);
|
|
||||||
try {
|
try {
|
||||||
sb.append(clsData.getDisassembledCode());
|
code.startLine(clsData.getDisassembledCode());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
sb.append("Failed to disassemble class:");
|
code.startLine("Failed to disassemble class:");
|
||||||
sb.append(ICodeWriter.NL);
|
code.startLine(Utils.getStackTrace(e));
|
||||||
sb.append(Utils.getStackTrace(e));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ 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;
|
import jadx.core.utils.ListUtils;
|
||||||
|
|
||||||
public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
public class FieldNode extends NotificationAttrNode implements ICodeNode, IFieldInfoRef {
|
||||||
|
|
||||||
private final ClassNode parentClass;
|
private final ClassNode parentClass;
|
||||||
private final FieldInfo fieldInfo;
|
private final FieldInfo fieldInfo;
|
||||||
@@ -38,10 +38,15 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
|||||||
this.accFlags = new AccessInfo(accessFlags, AFType.FIELD);
|
this.accFlags = new AccessInfo(accessFlags, AFType.FIELD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void unload() {
|
||||||
|
unloadAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
public void updateType(ArgType type) {
|
public void updateType(ArgType type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public FieldInfo getFieldInfo() {
|
public FieldInfo getFieldInfo() {
|
||||||
return fieldInfo;
|
return fieldInfo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package jadx.core.dex.nodes;
|
||||||
|
|
||||||
|
import jadx.core.dex.info.FieldInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common interface for FieldInfo and FieldNode
|
||||||
|
*/
|
||||||
|
public interface IFieldInfoRef {
|
||||||
|
FieldInfo getFieldInfo();
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
|
||||||
import jadx.api.plugins.input.insns.InsnData;
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
@@ -563,9 +562,9 @@ public class InsnNode extends LineAttrNode {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// wrap args
|
// wrap args
|
||||||
String separator = ICodeWriter.NL + " ";
|
String separator = "\n ";
|
||||||
sb.append(separator).append(Utils.listToString(arguments, separator));
|
sb.append(separator).append(Utils.listToString(arguments, separator));
|
||||||
sb.append(ICodeWriter.NL);
|
sb.append('\n');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
return mthInfo.getReturnType().equals(ArgType.VOID);
|
return mthInfo.getReturnType().equals(ArgType.VOID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<VarNode> collectArgsWithoutLoading() {
|
public List<VarNode> collectArgNodes() {
|
||||||
ICodeInfo codeInfo = getTopParentClass().getCode();
|
ICodeInfo codeInfo = getTopParentClass().getCode();
|
||||||
int mthDefPos = getDefPosition();
|
int mthDefPos = getDefPosition();
|
||||||
int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
|
int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
|
||||||
@@ -360,10 +360,13 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
|
|
||||||
public void setBasicBlocks(List<BlockNode> blocks) {
|
public void setBasicBlocks(List<BlockNode> blocks) {
|
||||||
this.blocks = blocks;
|
this.blocks = blocks;
|
||||||
int i = 0;
|
updateBlockIds(blocks);
|
||||||
for (BlockNode block : blocks) {
|
}
|
||||||
block.setId(i);
|
|
||||||
i++;
|
public void updateBlockIds(List<BlockNode> blocks) {
|
||||||
|
int count = blocks.size();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
blocks.get(i).setId(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,7 +394,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
return exitBlock.getPredecessors();
|
return exitBlock.getPredecessors();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPreExitBlocks(BlockNode block) {
|
public boolean isPreExitBlock(BlockNode block) {
|
||||||
List<BlockNode> successors = block.getSuccessors();
|
List<BlockNode> successors = block.getSuccessors();
|
||||||
if (successors.size() == 1) {
|
if (successors.size() == 1) {
|
||||||
return successors.get(0).equals(exitBlock);
|
return successors.get(0).equals(exitBlock);
|
||||||
|
|||||||
@@ -15,7 +15,12 @@ import jadx.core.utils.exceptions.CodegenException;
|
|||||||
|
|
||||||
public final class SwitchRegion extends AbstractRegion implements IBranchRegion {
|
public final class SwitchRegion extends AbstractRegion implements IBranchRegion {
|
||||||
|
|
||||||
public static final Object DEFAULT_CASE_KEY = new Object();
|
public static final Object DEFAULT_CASE_KEY = new Object() {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final BlockNode header;
|
private final BlockNode header;
|
||||||
|
|
||||||
@@ -91,7 +96,7 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
|
|||||||
for (CaseInfo caseInfo : cases) {
|
for (CaseInfo caseInfo : cases) {
|
||||||
List<String> keyStrings = Utils.collectionMap(caseInfo.getKeys(),
|
List<String> keyStrings = Utils.collectionMap(caseInfo.getKeys(),
|
||||||
k -> k == DEFAULT_CASE_KEY ? "default" : k.toString());
|
k -> k == DEFAULT_CASE_KEY ? "default" : k.toString());
|
||||||
sb.append(ICodeWriter.NL).append(" case ")
|
sb.append("\n case ")
|
||||||
.append(Utils.listToString(keyStrings))
|
.append(Utils.listToString(keyStrings))
|
||||||
.append(" -> ").append(caseInfo.getContainer());
|
.append(" -> ").append(caseInfo.getContainer());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import jadx.api.data.ICodeComment;
|
|||||||
import jadx.api.data.ICodeData;
|
import jadx.api.data.ICodeData;
|
||||||
import jadx.api.data.IJavaCodeRef;
|
import jadx.api.data.IJavaCodeRef;
|
||||||
import jadx.api.data.IJavaNodeRef;
|
import jadx.api.data.IJavaNodeRef;
|
||||||
|
import jadx.core.codegen.utils.CodeComment;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.IAttributeNode;
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
@@ -58,7 +59,7 @@ public class AttachCommentsVisitor extends AbstractVisitor {
|
|||||||
IJavaNodeRef nodeRef = comment.getNodeRef();
|
IJavaNodeRef nodeRef = comment.getNodeRef();
|
||||||
switch (nodeRef.getType()) {
|
switch (nodeRef.getType()) {
|
||||||
case CLASS:
|
case CLASS:
|
||||||
addComment(cls, comment.getComment());
|
addComment(cls, comment);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FIELD:
|
case FIELD:
|
||||||
@@ -66,7 +67,7 @@ public class AttachCommentsVisitor extends AbstractVisitor {
|
|||||||
if (fieldNode == null) {
|
if (fieldNode == null) {
|
||||||
LOG.warn("Field reference not found: {}", nodeRef);
|
LOG.warn("Field reference not found: {}", nodeRef);
|
||||||
} else {
|
} else {
|
||||||
addComment(fieldNode, comment.getComment());
|
addComment(fieldNode, comment);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ public class AttachCommentsVisitor extends AbstractVisitor {
|
|||||||
} else {
|
} else {
|
||||||
IJavaCodeRef codeRef = comment.getCodeRef();
|
IJavaCodeRef codeRef = comment.getCodeRef();
|
||||||
if (codeRef == null) {
|
if (codeRef == null) {
|
||||||
addComment(methodNode, comment.getComment());
|
addComment(methodNode, comment);
|
||||||
} else {
|
} else {
|
||||||
processCustomAttach(methodNode, codeRef, comment);
|
processCustomAttach(methodNode, codeRef, comment);
|
||||||
}
|
}
|
||||||
@@ -101,7 +102,7 @@ public class AttachCommentsVisitor extends AbstractVisitor {
|
|||||||
switch (attachType) {
|
switch (attachType) {
|
||||||
case INSN: {
|
case INSN: {
|
||||||
InsnNode insn = getInsnByOffset(mth, codeRef.getIndex());
|
InsnNode insn = getInsnByOffset(mth, codeRef.getIndex());
|
||||||
addComment(insn, comment.getComment());
|
addComment(insn, comment);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -109,11 +110,11 @@ public class AttachCommentsVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addComment(@Nullable IAttributeNode node, String comment) {
|
private static void addComment(@Nullable IAttributeNode node, ICodeComment comment) {
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
node.addAttr(AType.CODE_COMMENTS, comment);
|
node.addAttr(AType.CODE_COMMENTS, new CodeComment(comment));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ICodeComment> getCommentsData(ClassNode cls) {
|
private List<ICodeComment> getCommentsData(ClassNode cls) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
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;
|
||||||
@@ -16,16 +15,12 @@ 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;
|
||||||
|
|
||||||
@@ -122,7 +117,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,45 +143,6 @@ 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);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
|
|||||||
import jadx.core.dex.instructions.args.SSAVar;
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.IFieldInfoRef;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
|
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
|
||||||
@@ -26,6 +26,7 @@ import jadx.core.dex.visitors.ssa.SSATransform;
|
|||||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||||
import jadx.core.utils.InsnRemover;
|
import jadx.core.utils.InsnRemover;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
@JadxVisitor(
|
@JadxVisitor(
|
||||||
name = "Constants Inline",
|
name = "Constants Inline",
|
||||||
@@ -73,8 +74,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
if (!constArg.isLiteral()) {
|
if (!constArg.isLiteral()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long lit = ((LiteralArg) constArg).getLiteral();
|
if (constArg.isZeroLiteral() && forbidNullInlines(sVar)) {
|
||||||
if (lit == 0 && forbidNullInlines(sVar)) {
|
|
||||||
// all usages forbids inlining
|
// all usages forbids inlining
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
case CONST_STR: {
|
case CONST_STR: {
|
||||||
String s = ((ConstStringNode) insn).getString();
|
String s = ((ConstStringNode) insn).getString();
|
||||||
FieldNode f = mth.getParentClass().getConstField(s);
|
IFieldInfoRef f = mth.getParentClass().getConstField(s);
|
||||||
if (f == null) {
|
if (f == null) {
|
||||||
InsnNode copy = insn.copyWithoutResult();
|
InsnNode copy = insn.copyWithoutResult();
|
||||||
constArg = InsnArg.wrapArg(copy);
|
constArg = InsnArg.wrapArg(copy);
|
||||||
@@ -90,7 +90,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);
|
onSuccess = () -> ModVisitor.addFieldUsage(f, mth);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -134,20 +134,15 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean forbidNullArgInline(InsnNode insn, RegisterArg useArg) {
|
private static boolean forbidNullArgInline(InsnNode insn, RegisterArg useArg) {
|
||||||
switch (insn.getType()) {
|
if (insn.getType() == InsnType.MOVE) {
|
||||||
case MOVE:
|
// result is null, chain checks
|
||||||
case CAST:
|
return forbidNullInlines(insn.getResult().getSVar());
|
||||||
case CHECK_CAST:
|
|
||||||
// result is null, chain checks
|
|
||||||
return forbidNullInlines(insn.getResult().getSVar());
|
|
||||||
|
|
||||||
default:
|
|
||||||
if (!canUseNull(insn, useArg)) {
|
|
||||||
useArg.add(AFlag.DONT_INLINE_CONST);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
if (!canUseNull(insn, useArg)) {
|
||||||
|
useArg.add(AFlag.DONT_INLINE_CONST);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean canUseNull(InsnNode insn, RegisterArg useArg) {
|
private static boolean canUseNull(InsnNode insn, RegisterArg useArg) {
|
||||||
@@ -256,7 +251,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// arg replaced, made some optimizations
|
// arg replaced, made some optimizations
|
||||||
FieldNode fieldNode = null;
|
IFieldInfoRef fieldNode = null;
|
||||||
ArgType litArgType = litArg.getType();
|
ArgType litArgType = litArg.getType();
|
||||||
if (litArgType.isTypeKnown()) {
|
if (litArgType.isTypeKnown()) {
|
||||||
fieldNode = mth.getParentClass().getConstFieldByLiteralArg(litArg);
|
fieldNode = mth.getParentClass().getConstFieldByLiteralArg(litArg);
|
||||||
@@ -266,12 +261,10 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
IndexInsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0);
|
IndexInsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0);
|
||||||
if (litArg.wrapInstruction(mth, sgetInsn) != null) {
|
if (litArg.wrapInstruction(mth, sgetInsn) != null) {
|
||||||
fieldNode.addUseIn(mth);
|
ModVisitor.addFieldUsage(fieldNode, mth);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (needExplicitCast(useInsn, litArg)) {
|
addExplicitCast(useInsn, litArg);
|
||||||
litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!useInsn.replaceArg(arg, constArg.duplicate())) {
|
if (!useInsn.replaceArg(arg, constArg.duplicate())) {
|
||||||
@@ -282,18 +275,33 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean needExplicitCast(InsnNode insn, LiteralArg arg) {
|
private static void addExplicitCast(InsnNode insn, LiteralArg arg) {
|
||||||
if (insn instanceof BaseInvokeNode) {
|
if (insn instanceof BaseInvokeNode) {
|
||||||
BaseInvokeNode callInsn = (BaseInvokeNode) insn;
|
BaseInvokeNode callInsn = (BaseInvokeNode) insn;
|
||||||
MethodInfo callMth = callInsn.getCallMth();
|
MethodInfo callMth = callInsn.getCallMth();
|
||||||
int offset = callInsn.getFirstArgOffset();
|
if (callInsn.getInstanceArg() == arg) {
|
||||||
int argIndex = insn.getArgIndex(arg);
|
// instance arg is null, force cast
|
||||||
ArgType argType = callMth.getArgumentsTypes().get(argIndex - offset);
|
if (!arg.isZeroLiteral()) {
|
||||||
if (argType.isPrimitive()) {
|
throw new JadxRuntimeException("Unexpected instance arg in invoke");
|
||||||
arg.setType(argType);
|
}
|
||||||
return argType.equals(ArgType.BYTE);
|
ArgType castType = callMth.getDeclClass().getType();
|
||||||
|
InsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1);
|
||||||
|
castInsn.addArg(arg);
|
||||||
|
castInsn.add(AFlag.EXPLICIT_CAST);
|
||||||
|
InsnArg wrapCast = InsnArg.wrapArg(castInsn);
|
||||||
|
wrapCast.setType(castType);
|
||||||
|
insn.replaceArg(arg, wrapCast);
|
||||||
|
} else {
|
||||||
|
int offset = callInsn.getFirstArgOffset();
|
||||||
|
int argIndex = insn.getArgIndex(arg);
|
||||||
|
ArgType argType = callMth.getArgumentsTypes().get(argIndex - offset);
|
||||||
|
if (argType.isPrimitive()) {
|
||||||
|
arg.setType(argType);
|
||||||
|
if (argType.equals(ArgType.BYTE)) {
|
||||||
|
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
|||||||
dot.add("color=red,");
|
dot.add("color=red,");
|
||||||
}
|
}
|
||||||
dot.add("label=\"{");
|
dot.add("label=\"{");
|
||||||
dot.add(String.valueOf(block.getCId())).add("\\:\\ ");
|
dot.add(String.valueOf(block.getId())).add("\\:\\ ");
|
||||||
dot.add(InsnUtils.formatOffset(block.getStartOffset()));
|
dot.add(InsnUtils.formatOffset(block.getStartOffset()));
|
||||||
if (!attrs.isEmpty()) {
|
if (!attrs.isEmpty()) {
|
||||||
dot.add('|').add(attrs);
|
dot.add('|').add(attrs);
|
||||||
@@ -208,6 +208,8 @@ public class DotGraphVisitor extends AbstractVisitor {
|
|||||||
dot.add('|');
|
dot.add('|');
|
||||||
dot.startLine("doms: ").add(escape(block.getDoms()));
|
dot.startLine("doms: ").add(escape(block.getDoms()));
|
||||||
dot.startLine("\\lidom: ").add(escape(block.getIDom()));
|
dot.startLine("\\lidom: ").add(escape(block.getIDom()));
|
||||||
|
dot.startLine("\\lpost-doms: ").add(escape(block.getPostDoms()));
|
||||||
|
dot.startLine("\\lpost-idom: ").add(escape(block.getIPostDom()));
|
||||||
dot.startLine("\\ldom-f: ").add(escape(block.getDomFrontier()));
|
dot.startLine("\\ldom-f: ").add(escape(block.getDomFrontier()));
|
||||||
dot.startLine("\\ldoms-on: ").add(escape(Utils.listToString(block.getDominatesOn())));
|
dot.startLine("\\ldoms-on: ").add(escape(Utils.listToString(block.getDominatesOn())));
|
||||||
dot.startLine("\\l");
|
dot.startLine("\\l");
|
||||||
@@ -230,10 +232,10 @@ public class DotGraphVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
if (PRINT_DOMINATORS) {
|
if (PRINT_DOMINATORS) {
|
||||||
for (BlockNode c : block.getDominatesOn()) {
|
for (BlockNode c : block.getDominatesOn()) {
|
||||||
conn.startLine(block.getCId() + " -> " + c.getCId() + "[color=green];");
|
conn.startLine(block.getId() + " -> " + c.getId() + "[color=green];");
|
||||||
}
|
}
|
||||||
for (BlockNode dom : BlockUtils.bitSetToBlocks(mth, block.getDomFrontier())) {
|
for (BlockNode dom : BlockUtils.bitSetToBlocks(mth, block.getDomFrontier())) {
|
||||||
conn.startLine("f_" + block.getCId() + " -> f_" + dom.getCId() + "[color=blue];");
|
conn.startLine("f_" + block.getId() + " -> f_" + dom.getId() + "[color=blue];");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,7 +275,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
|||||||
private String makeName(IContainer c) {
|
private String makeName(IContainer c) {
|
||||||
String name;
|
String name;
|
||||||
if (c instanceof BlockNode) {
|
if (c instanceof BlockNode) {
|
||||||
name = "Node_" + ((BlockNode) c).getCId();
|
name = "Node_" + ((BlockNode) c).getId();
|
||||||
} else if (c instanceof IBlock) {
|
} else if (c instanceof IBlock) {
|
||||||
name = "Node_" + c.getClass().getSimpleName() + '_' + c.hashCode();
|
name = "Node_" + c.getClass().getSimpleName() + '_' + c.hashCode();
|
||||||
} else {
|
} else {
|
||||||
@@ -317,8 +319,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
|||||||
.replace("\"", "\\\"")
|
.replace("\"", "\\\"")
|
||||||
.replace("-", "\\-")
|
.replace("-", "\\-")
|
||||||
.replace("|", "\\|")
|
.replace("|", "\\|")
|
||||||
.replace(ICodeWriter.NL, NL)
|
.replaceAll("\\R", NL);
|
||||||
.replace("\n", NL);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
|
|||||||
&& retInsn.getArg(0).isSameVar(firstInsn.getResult())
|
&& retInsn.getArg(0).isSameVar(firstInsn.getResult())
|
||||||
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
|
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
|
||||||
case SGET:
|
case SGET:
|
||||||
return mthRegs.size() == 0
|
return mthRegs.isEmpty()
|
||||||
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
|
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
|
||||||
|
|
||||||
case IPUT:
|
case IPUT:
|
||||||
@@ -113,7 +113,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
|
|||||||
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
|
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
|
||||||
|
|
||||||
case INVOKE:
|
case INVOKE:
|
||||||
return mthRegs.size() >= 1
|
return !mthRegs.isEmpty()
|
||||||
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0))
|
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0))
|
||||||
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
|
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
@@ -28,6 +30,7 @@ import jadx.core.dex.visitors.methods.MutableMethodDetails;
|
|||||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||||
import jadx.core.dex.visitors.typeinference.TypeCompare;
|
import jadx.core.dex.visitors.typeinference.TypeCompare;
|
||||||
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
@@ -43,6 +46,8 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
public class MethodInvokeVisitor extends AbstractVisitor {
|
public class MethodInvokeVisitor extends AbstractVisitor {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MethodInvokeVisitor.class);
|
||||||
|
|
||||||
private RootNode root;
|
private RootNode root;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -116,7 +121,8 @@ public class MethodInvokeVisitor extends AbstractVisitor {
|
|||||||
int argsOffset = invokeInsn.getFirstArgOffset();
|
int argsOffset = invokeInsn.getFirstArgOffset();
|
||||||
List<ArgType> compilerVarTypes = collectCompilerVarTypes(invokeInsn, argsOffset);
|
List<ArgType> compilerVarTypes = collectCompilerVarTypes(invokeInsn, argsOffset);
|
||||||
List<ArgType> castTypes = searchCastTypes(parentMth, effectiveMthDetails, effectiveOverloadMethods, compilerVarTypes);
|
List<ArgType> castTypes = searchCastTypes(parentMth, effectiveMthDetails, effectiveOverloadMethods, compilerVarTypes);
|
||||||
applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, castTypes);
|
List<ArgType> resultCastTypes = expandTypes(parentMth, effectiveMthDetails, castTypes);
|
||||||
|
applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, resultCastTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,7 +186,13 @@ public class MethodInvokeVisitor extends AbstractVisitor {
|
|||||||
if (arg.isLiteral() && compilerType.isPrimitive() && castType.isPrimitive()) {
|
if (arg.isLiteral() && compilerType.isPrimitive() && castType.isPrimitive()) {
|
||||||
arg.setType(castType);
|
arg.setType(castType);
|
||||||
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
|
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
|
||||||
|
} else if (InsnUtils.isWrapped(arg, InsnType.CHECK_CAST)) {
|
||||||
|
IndexInsnNode wrapInsn = ((IndexInsnNode) ((InsnWrapArg) arg).getWrapInsn());
|
||||||
|
wrapInsn.updateIndex(castType);
|
||||||
} else {
|
} else {
|
||||||
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
|
LOG.info("Insert cast for invoke insn arg: {}, insn: {}", arg, invokeInsn);
|
||||||
|
}
|
||||||
InsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1);
|
InsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1);
|
||||||
castInsn.addArg(arg);
|
castInsn.addArg(arg);
|
||||||
castInsn.add(AFlag.EXPLICIT_CAST);
|
castInsn.add(AFlag.EXPLICIT_CAST);
|
||||||
@@ -243,7 +255,7 @@ public class MethodInvokeVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
private List<ArgType> searchCastTypes(MethodNode parentMth, IMethodDetails mthDetails, List<IMethodDetails> overloadedMethods,
|
private List<ArgType> searchCastTypes(MethodNode parentMth, IMethodDetails mthDetails, List<IMethodDetails> overloadedMethods,
|
||||||
List<ArgType> compilerVarTypes) {
|
List<ArgType> compilerVarTypes) {
|
||||||
// try compile types
|
// try compiler types
|
||||||
if (isOverloadResolved(mthDetails, overloadedMethods, compilerVarTypes)) {
|
if (isOverloadResolved(mthDetails, overloadedMethods, compilerVarTypes)) {
|
||||||
return compilerVarTypes;
|
return compilerVarTypes;
|
||||||
}
|
}
|
||||||
@@ -277,10 +289,10 @@ public class MethodInvokeVisitor extends AbstractVisitor {
|
|||||||
if (Consts.DEBUG_OVERLOADED_CASTS) {
|
if (Consts.DEBUG_OVERLOADED_CASTS) {
|
||||||
// TODO: try to minimize casts count
|
// TODO: try to minimize casts count
|
||||||
parentMth.addDebugComment("Failed to find minimal casts for resolve overloaded methods, cast all args instead"
|
parentMth.addDebugComment("Failed to find minimal casts for resolve overloaded methods, cast all args instead"
|
||||||
+ ICodeWriter.NL + " method: " + mthDetails
|
+ "\n method: " + mthDetails
|
||||||
+ ICodeWriter.NL + " arg types: " + compilerVarTypes
|
+ "\n arg types: " + compilerVarTypes
|
||||||
+ ICodeWriter.NL + " candidates:"
|
+ "\n candidates:"
|
||||||
+ ICodeWriter.NL + " " + Utils.listToString(overloadedMethods, ICodeWriter.NL + " "));
|
+ "\n " + Utils.listToString(overloadedMethods, "\n "));
|
||||||
}
|
}
|
||||||
// not resolved -> cast all args
|
// not resolved -> cast all args
|
||||||
return mthDetails.getArgTypes();
|
return mthDetails.getArgTypes();
|
||||||
@@ -300,6 +312,27 @@ public class MethodInvokeVisitor extends AbstractVisitor {
|
|||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use generified types if available
|
||||||
|
*/
|
||||||
|
private List<ArgType> expandTypes(MethodNode parentMth, IMethodDetails methodDetails, List<ArgType> castTypes) {
|
||||||
|
TypeCompare typeCompare = parentMth.root().getTypeCompare();
|
||||||
|
List<ArgType> mthArgTypes = methodDetails.getArgTypes();
|
||||||
|
int argsCount = castTypes.size();
|
||||||
|
List<ArgType> list = new ArrayList<>(argsCount);
|
||||||
|
for (int i = 0; i < argsCount; i++) {
|
||||||
|
ArgType mthType = mthArgTypes.get(i);
|
||||||
|
ArgType castType = castTypes.get(i);
|
||||||
|
TypeCompareEnum result = typeCompare.compareTypes(mthType, castType);
|
||||||
|
if (result == TypeCompareEnum.NARROW_BY_GENERIC) {
|
||||||
|
list.add(mthType);
|
||||||
|
} else {
|
||||||
|
list.add(castType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isOverloadResolved(IMethodDetails expectedMthDetails, List<IMethodDetails> overloadedMethods, List<ArgType> castTypes) {
|
private boolean isOverloadResolved(IMethodDetails expectedMthDetails, List<IMethodDetails> overloadedMethods, List<ArgType> castTypes) {
|
||||||
if (overloadedMethods.isEmpty()) {
|
if (overloadedMethods.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -387,12 +420,22 @@ public class MethodInvokeVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
if (arg instanceof InsnWrapArg) {
|
if (arg instanceof InsnWrapArg) {
|
||||||
InsnWrapArg wrapArg = (InsnWrapArg) arg;
|
InsnWrapArg wrapArg = (InsnWrapArg) arg;
|
||||||
InsnNode wrapInsn = wrapArg.getWrapInsn();
|
return getInsnCompilerType(arg, wrapArg.getWrapInsn());
|
||||||
if (wrapInsn.getResult() != null) {
|
|
||||||
return wrapInsn.getResult().getType();
|
|
||||||
}
|
|
||||||
return arg.getType();
|
|
||||||
}
|
}
|
||||||
throw new JadxRuntimeException("Unknown var type for: " + arg);
|
throw new JadxRuntimeException("Unknown var type for: " + arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ArgType getInsnCompilerType(InsnArg arg, InsnNode insn) {
|
||||||
|
switch (insn.getType()) {
|
||||||
|
case CAST:
|
||||||
|
case CHECK_CAST:
|
||||||
|
return ((IndexInsnNode) insn).getIndexAsType();
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (insn.getResult() != null) {
|
||||||
|
return insn.getResult().getType();
|
||||||
|
}
|
||||||
|
return arg.getType();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ 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.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.IFieldInfoRef;
|
||||||
import jadx.core.dex.nodes.IMethodDetails;
|
import jadx.core.dex.nodes.IMethodDetails;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
@@ -239,10 +240,10 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
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]);
|
IFieldInfoRef f = parentClass.getConstField(keys[k]);
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
insn.modifyKey(k, f);
|
insn.modifyKey(k, f);
|
||||||
f.addUseIn(mth);
|
addFieldUsage(f, mth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,7 +314,7 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
return new EncodedValue(EncodedType.ENCODED_ARRAY, listVal);
|
return new EncodedValue(EncodedType.ENCODED_ARRAY, listVal);
|
||||||
}
|
}
|
||||||
FieldNode constField = parentCls.getConstField(encodedValue.getValue());
|
IFieldInfoRef constField = parentCls.getConstField(encodedValue.getValue());
|
||||||
if (constField != null) {
|
if (constField != null) {
|
||||||
return new EncodedValue(EncodedType.ENCODED_FIELD, constField.getFieldInfo());
|
return new EncodedValue(EncodedType.ENCODED_FIELD, constField.getFieldInfo());
|
||||||
}
|
}
|
||||||
@@ -321,7 +322,7 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void replaceConst(MethodNode mth, ClassNode parentClass, BlockNode block, int i, InsnNode insn) {
|
private static void replaceConst(MethodNode mth, ClassNode parentClass, BlockNode block, int i, InsnNode insn) {
|
||||||
FieldNode f;
|
IFieldInfoRef f;
|
||||||
if (insn.getType() == InsnType.CONST_STR) {
|
if (insn.getType() == InsnType.CONST_STR) {
|
||||||
String s = ((ConstStringNode) insn).getString();
|
String s = ((ConstStringNode) insn).getString();
|
||||||
f = parentClass.getConstField(s);
|
f = parentClass.getConstField(s);
|
||||||
@@ -335,7 +336,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);
|
addFieldUsage(f, mth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,11 +346,11 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
InsnArg litArg = arithNode.getArg(1);
|
InsnArg litArg = arithNode.getArg(1);
|
||||||
if (litArg.isLiteral()) {
|
if (litArg.isLiteral()) {
|
||||||
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg);
|
IFieldInfoRef 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);
|
||||||
if (arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet))) {
|
if (arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet))) {
|
||||||
f.addUseIn(mth);
|
addFieldUsage(f, mth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -389,6 +390,11 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
private static void removeCheckCast(MethodNode mth, BlockNode block, int i, IndexInsnNode insn) {
|
private static void removeCheckCast(MethodNode mth, BlockNode block, int i, IndexInsnNode insn) {
|
||||||
InsnArg castArg = insn.getArg(0);
|
InsnArg castArg = insn.getArg(0);
|
||||||
|
if (castArg.isZeroLiteral()) {
|
||||||
|
// always keep cast for 'null'
|
||||||
|
insn.add(AFlag.EXPLICIT_CAST);
|
||||||
|
return;
|
||||||
|
}
|
||||||
ArgType castType = (ArgType) insn.getIndex();
|
ArgType castType = (ArgType) insn.getIndex();
|
||||||
if (!ArgType.isCastNeeded(mth.root(), castArg.getType(), castType)) {
|
if (!ArgType.isCastNeeded(mth.root(), castArg.getType(), castType)) {
|
||||||
RegisterArg result = insn.getResult();
|
RegisterArg result = insn.getResult();
|
||||||
@@ -561,11 +567,11 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
InsnNode filledArr = new FilledNewArrayNode(elType, list.size());
|
InsnNode filledArr = new FilledNewArrayNode(elType, list.size());
|
||||||
filledArr.setResult(newArrayNode.getResult().duplicate());
|
filledArr.setResult(newArrayNode.getResult().duplicate());
|
||||||
for (LiteralArg arg : list) {
|
for (LiteralArg arg : list) {
|
||||||
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg(arg);
|
IFieldInfoRef f = mth.getParentClass().getConstFieldByLiteralArg(arg);
|
||||||
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);
|
addFieldUsage(f, mth);
|
||||||
} else {
|
} else {
|
||||||
filledArr.addArg(arg.duplicate());
|
filledArr.addArg(arg.duplicate());
|
||||||
}
|
}
|
||||||
@@ -602,4 +608,10 @@ public class ModVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
block.copyAttributeFrom(insn, AType.CODE_COMMENTS); // save comment
|
block.copyAttributeFrom(insn, AType.CODE_COMMENTS); // save comment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void addFieldUsage(IFieldInfoRef fieldData, MethodNode mth) {
|
||||||
|
if (fieldData instanceof FieldNode) {
|
||||||
|
((FieldNode) fieldData).addUseIn(mth);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ import jadx.core.dex.attributes.nodes.LineAttrNode;
|
|||||||
import jadx.core.dex.info.FieldInfo;
|
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.IndexInsnNode;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
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;
|
||||||
@@ -82,6 +84,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
|||||||
removeParenthesis(block);
|
removeParenthesis(block);
|
||||||
modifyArith(block);
|
modifyArith(block);
|
||||||
checkConstUsage(block);
|
checkConstUsage(block);
|
||||||
|
addNullCasts(mth, block);
|
||||||
}
|
}
|
||||||
moveConstructorInConstructor(mth);
|
moveConstructorInConstructor(mth);
|
||||||
collectFieldsUsageInAnnotations(mth, mth);
|
collectFieldsUsageInAnnotations(mth, mth);
|
||||||
@@ -379,4 +382,27 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addNullCasts(MethodNode mth, BlockNode block) {
|
||||||
|
for (InsnNode insn : block.getInstructions()) {
|
||||||
|
switch (insn.getType()) {
|
||||||
|
case INVOKE:
|
||||||
|
verifyNullCast(mth, ((InvokeNode) insn).getInstanceArg());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARRAY_LENGTH:
|
||||||
|
verifyNullCast(mth, insn.getArg(0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyNullCast(MethodNode mth, InsnArg arg) {
|
||||||
|
if (arg != null && arg.isZeroConst()) {
|
||||||
|
ArgType castType = arg.getType();
|
||||||
|
IndexInsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1);
|
||||||
|
castInsn.addArg(InsnArg.lit(0, castType));
|
||||||
|
arg.wrapInstruction(mth, castInsn);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,9 +43,7 @@ public class ProcessAnonymous extends AbstractVisitor {
|
|||||||
if (!inlineAnonymousClasses) {
|
if (!inlineAnonymousClasses) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (ClassNode cls : root.getClasses()) {
|
root.getClasses().forEach(ProcessAnonymous::processClass);
|
||||||
markAnonymousClass(cls);
|
|
||||||
}
|
|
||||||
mergeAnonymousDeps(root);
|
mergeAnonymousDeps(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,10 +57,18 @@ public class ProcessAnonymous extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void visitClassAndInners(ClassNode cls) {
|
private void visitClassAndInners(ClassNode cls) {
|
||||||
markAnonymousClass(cls);
|
processClass(cls);
|
||||||
cls.getInnerClasses().forEach(this::visitClassAndInners);
|
cls.getInnerClasses().forEach(this::visitClassAndInners);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void processClass(ClassNode cls) {
|
||||||
|
try {
|
||||||
|
markAnonymousClass(cls);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
cls.addError("Anonymous visitor error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void markAnonymousClass(ClassNode cls) {
|
private static void markAnonymousClass(ClassNode cls) {
|
||||||
if (!canBeAnonymous(cls)) {
|
if (!canBeAnonymous(cls)) {
|
||||||
return;
|
return;
|
||||||
@@ -273,6 +279,10 @@ public class ProcessAnonymous extends AbstractVisitor {
|
|||||||
if (!ctrUseMth.getMethodInfo().isClassInit()) {
|
if (!ctrUseMth.getMethodInfo().isClassInit()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (cls.getUseInMth().isEmpty()) {
|
||||||
|
// no outside usage, inline not needed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
FieldNode instFld = ListUtils.filterOnlyOne(cls.getFields(),
|
FieldNode instFld = ListUtils.filterOnlyOne(cls.getFields(),
|
||||||
f -> f.getAccessFlags().containsFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL)
|
f -> f.getAccessFlags().containsFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL)
|
||||||
&& f.getFieldInfo().getType().equals(cls.getClassInfo().getType()));
|
&& f.getFieldInfo().getType().equals(cls.getClassInfo().getType()));
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import jadx.core.dex.instructions.args.InsnArg;
|
|||||||
import jadx.core.dex.instructions.args.LiteralArg;
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.IFieldInfoRef;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||||
@@ -167,11 +167,11 @@ public class ReplaceNewArray extends AbstractVisitor {
|
|||||||
|
|
||||||
private static InsnArg replaceConstInArg(MethodNode mth, InsnArg valueArg) {
|
private static InsnArg replaceConstInArg(MethodNode mth, InsnArg valueArg) {
|
||||||
if (valueArg.isLiteral()) {
|
if (valueArg.isLiteral()) {
|
||||||
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg((LiteralArg) valueArg);
|
IFieldInfoRef 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);
|
||||||
InsnArg arg = InsnArg.wrapArg(fGet);
|
InsnArg arg = InsnArg.wrapArg(fGet);
|
||||||
f.addUseIn(mth);
|
ModVisitor.addFieldUsage(f, mth);
|
||||||
return arg;
|
return arg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -402,7 +402,8 @@ public class SimplifyVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!stringArgFound) {
|
if (!stringArgFound) {
|
||||||
mth.addDebugComment("TODO: convert one arg to string using `String.valueOf()`, args: " + args);
|
String argStr = Utils.listToString(args, InsnArg::toShortString);
|
||||||
|
mth.addDebugComment("TODO: convert one arg to string using `String.valueOf()`, args: " + argStr);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,7 +626,9 @@ public class SimplifyVisitor extends AbstractVisitor {
|
|||||||
for (int i = 1; i < argsCount; i++) {
|
for (int i = 1; i < argsCount; i++) {
|
||||||
concat.addArg(wrap.getArg(i));
|
concat.addArg(wrap.getArg(i));
|
||||||
}
|
}
|
||||||
return ArithNode.oneArgOp(ArithOp.ADD, fArg, InsnArg.wrapArg(concat));
|
InsnArg concatArg = InsnArg.wrapArg(concat);
|
||||||
|
concatArg.setType(ArgType.STRING);
|
||||||
|
return ArithNode.oneArgOp(ArithOp.ADD, fArg, concatArg);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.debug("Can't convert field arith insn: {}, mth: {}", insn, mth, e);
|
LOG.debug("Can't convert field arith insn: {}, mth: {}", insn, mth, e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public class BlockExceptionHandler {
|
|||||||
BlockProcessor.removeMarkedBlocks(mth);
|
BlockProcessor.removeMarkedBlocks(mth);
|
||||||
|
|
||||||
BlockSet sorted = new BlockSet(mth);
|
BlockSet sorted = new BlockSet(mth);
|
||||||
BlockUtils.dfsVisit(mth, sorted::set);
|
BlockUtils.visitDFS(mth, sorted::set);
|
||||||
removeUnusedExcHandlers(mth, tryBlocks, sorted);
|
removeUnusedExcHandlers(mth, tryBlocks, sorted);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -331,6 +331,17 @@ public class BlockExceptionHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BlockNode bottom = searchBottomBlock(mth, blocks);
|
BlockNode bottom = searchBottomBlock(mth, blocks);
|
||||||
|
BlockNode splitReturn;
|
||||||
|
if (bottom != null && bottom.isReturnBlock()) {
|
||||||
|
if (Consts.DEBUG_EXC_HANDLERS) {
|
||||||
|
LOG.debug("TryCatch #{} bottom block ({}) is return, split", tryCatchBlock.id(), bottom);
|
||||||
|
}
|
||||||
|
splitReturn = bottom;
|
||||||
|
bottom = BlockSplitter.blockSplitTop(mth, bottom);
|
||||||
|
bottom.add(AFlag.SYNTHETIC);
|
||||||
|
} else {
|
||||||
|
splitReturn = null;
|
||||||
|
}
|
||||||
if (Consts.DEBUG_EXC_HANDLERS) {
|
if (Consts.DEBUG_EXC_HANDLERS) {
|
||||||
LOG.debug("TryCatch #{} split: top {}, bottom: {}", tryCatchBlock.id(), top, bottom);
|
LOG.debug("TryCatch #{} split: top {}, bottom: {}", tryCatchBlock.id(), top, bottom);
|
||||||
}
|
}
|
||||||
@@ -349,6 +360,18 @@ public class BlockExceptionHandler {
|
|||||||
bottomSplitterBlock.add(AFlag.EXC_BOTTOM_SPLITTER);
|
bottomSplitterBlock.add(AFlag.EXC_BOTTOM_SPLITTER);
|
||||||
bottomSplitterBlock.add(AFlag.SYNTHETIC);
|
bottomSplitterBlock.add(AFlag.SYNTHETIC);
|
||||||
BlockSplitter.connect(bottom, bottomSplitterBlock);
|
BlockSplitter.connect(bottom, bottomSplitterBlock);
|
||||||
|
if (splitReturn != null) {
|
||||||
|
// redirect handler to return block instead synthetic split block to avoid self-loop
|
||||||
|
BlockSet bottomPreds = BlockSet.from(mth, bottom.getPredecessors());
|
||||||
|
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
|
||||||
|
if (bottomPreds.intersects(handler.getBlocks())) {
|
||||||
|
BlockNode lastBlock = bottomPreds.intersect(handler.getBlocks()).getOne();
|
||||||
|
if (lastBlock != null) {
|
||||||
|
BlockSplitter.replaceConnection(lastBlock, bottom, splitReturn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Consts.DEBUG_EXC_HANDLERS) {
|
if (Consts.DEBUG_EXC_HANDLERS) {
|
||||||
@@ -600,7 +623,7 @@ public class BlockExceptionHandler {
|
|||||||
for (ExceptionHandler eh : mth.getExceptionHandlers()) {
|
for (ExceptionHandler eh : mth.getExceptionHandlers()) {
|
||||||
boolean notProcessed = true;
|
boolean notProcessed = true;
|
||||||
BlockNode handlerBlock = eh.getHandlerBlock();
|
BlockNode handlerBlock = eh.getHandlerBlock();
|
||||||
if (blocks.get(handlerBlock)) {
|
if (handlerBlock == null || blocks.get(handlerBlock)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (TryCatchBlockAttr tcb : tryBlocks) {
|
for (TryCatchBlockAttr tcb : tryBlocks) {
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
registerLoops(mth);
|
registerLoops(mth);
|
||||||
processNestedLoops(mth);
|
processNestedLoops(mth);
|
||||||
|
|
||||||
|
PostDominatorTree.compute(mth);
|
||||||
|
|
||||||
updateCleanSuccessors(mth);
|
updateCleanSuccessors(mth);
|
||||||
if (!mth.contains(AFlag.DISABLE_BLOCKS_LOCK)) {
|
if (!mth.contains(AFlag.DISABLE_BLOCKS_LOCK)) {
|
||||||
mth.finishBasicBlocks();
|
mth.finishBasicBlocks();
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ package jadx.core.dex.visitors.blocks;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
@@ -23,14 +22,14 @@ public class DominatorTree {
|
|||||||
|
|
||||||
public static void compute(MethodNode mth) {
|
public static void compute(MethodNode mth) {
|
||||||
List<BlockNode> sorted = sortBlocks(mth);
|
List<BlockNode> sorted = sortBlocks(mth);
|
||||||
BlockNode[] doms = build(sorted);
|
BlockNode[] doms = build(sorted, BlockNode::getPredecessors);
|
||||||
apply(sorted, doms);
|
apply(sorted, doms);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<BlockNode> sortBlocks(MethodNode mth) {
|
private static List<BlockNode> sortBlocks(MethodNode mth) {
|
||||||
int blocksCount = mth.getBasicBlocks().size();
|
int blocksCount = mth.getBasicBlocks().size();
|
||||||
List<BlockNode> sorted = new ArrayList<>(blocksCount);
|
List<BlockNode> sorted = new ArrayList<>(blocksCount);
|
||||||
BlockUtils.dfsVisit(mth, sorted::add);
|
BlockUtils.visitDFS(mth, sorted::add);
|
||||||
if (sorted.size() != blocksCount) {
|
if (sorted.size() != blocksCount) {
|
||||||
throw new JadxRuntimeException("Found unreachable blocks");
|
throw new JadxRuntimeException("Found unreachable blocks");
|
||||||
}
|
}
|
||||||
@@ -38,8 +37,7 @@ public class DominatorTree {
|
|||||||
return sorted;
|
return sorted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
static BlockNode[] build(List<BlockNode> sorted, Function<BlockNode, List<BlockNode>> predFunc) {
|
||||||
private static BlockNode[] build(List<BlockNode> sorted) {
|
|
||||||
int blocksCount = sorted.size();
|
int blocksCount = sorted.size();
|
||||||
BlockNode[] doms = new BlockNode[blocksCount];
|
BlockNode[] doms = new BlockNode[blocksCount];
|
||||||
doms[0] = sorted.get(0);
|
doms[0] = sorted.get(0);
|
||||||
@@ -48,7 +46,7 @@ public class DominatorTree {
|
|||||||
changed = false;
|
changed = false;
|
||||||
for (int blockId = 1; blockId < blocksCount; blockId++) {
|
for (int blockId = 1; blockId < blocksCount; blockId++) {
|
||||||
BlockNode b = sorted.get(blockId);
|
BlockNode b = sorted.get(blockId);
|
||||||
List<BlockNode> preds = b.getPredecessors();
|
List<BlockNode> preds = predFunc.apply(b);
|
||||||
int pickedPred = -1;
|
int pickedPred = -1;
|
||||||
BlockNode newIDom = null;
|
BlockNode newIDom = null;
|
||||||
for (BlockNode pred : preds) {
|
for (BlockNode pred : preds) {
|
||||||
@@ -60,7 +58,7 @@ public class DominatorTree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newIDom == null) {
|
if (newIDom == null) {
|
||||||
throw new JadxRuntimeException("No predecessors for block: " + b);
|
throw new JadxRuntimeException("No immediate dominator for block: " + b);
|
||||||
}
|
}
|
||||||
for (BlockNode predBlock : preds) {
|
for (BlockNode predBlock : preds) {
|
||||||
int predId = predBlock.getId();
|
int predId = predBlock.getId();
|
||||||
@@ -110,7 +108,7 @@ public class DominatorTree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BitSet collectDoms(BlockNode[] doms, BlockNode idom) {
|
static BitSet collectDoms(BlockNode[] doms, BlockNode idom) {
|
||||||
BitSet domBS = new BitSet(doms.length);
|
BitSet domBS = new BitSet(doms.length);
|
||||||
BlockNode nextIDom = idom;
|
BlockNode nextIDom = idom;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ public class FixMultiEntryLoops {
|
|||||||
}
|
}
|
||||||
List<SpecialEdgeAttr> specialEdges = mth.getAll(AType.SPECIAL_EDGE);
|
List<SpecialEdgeAttr> specialEdges = mth.getAll(AType.SPECIAL_EDGE);
|
||||||
List<SpecialEdgeAttr> multiEntryLoops = specialEdges.stream()
|
List<SpecialEdgeAttr> multiEntryLoops = specialEdges.stream()
|
||||||
.filter(e -> e.getType() == SpecialEdgeType.BACK_EDGE)
|
.filter(e -> e.getType() == SpecialEdgeType.BACK_EDGE && !isSingleEntryLoop(e))
|
||||||
.filter(e -> !isSingleEntryLoop(e))
|
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
if (multiEntryLoops.isEmpty()) {
|
if (multiEntryLoops.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -42,12 +41,21 @@ public class FixMultiEntryLoops {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean fixLoop(MethodNode mth, SpecialEdgeAttr backEdge, List<SpecialEdgeAttr> crossEdges) {
|
private static boolean fixLoop(MethodNode mth, SpecialEdgeAttr backEdge, List<SpecialEdgeAttr> crossEdges) {
|
||||||
|
if (isHeaderSuccessorEntry(mth, backEdge, crossEdges)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isEndBlockEntry(mth, backEdge, crossEdges)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
mth.addWarnComment("Unsupported multi-entry loop pattern (" + backEdge + "). Please report as a decompilation issue!!!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isHeaderSuccessorEntry(MethodNode mth, SpecialEdgeAttr backEdge, List<SpecialEdgeAttr> crossEdges) {
|
||||||
BlockNode header = backEdge.getEnd();
|
BlockNode header = backEdge.getEnd();
|
||||||
BlockNode headerIDom = header.getIDom();
|
BlockNode headerIDom = header.getIDom();
|
||||||
SpecialEdgeAttr subEntry = ListUtils.filterOnlyOne(crossEdges, e -> e.getStart() == headerIDom);
|
SpecialEdgeAttr subEntry = ListUtils.filterOnlyOne(crossEdges, e -> e.getStart() == headerIDom);
|
||||||
if (subEntry == null || !isSupportedPattern(header, subEntry)) {
|
if (subEntry == null || !ListUtils.isSingleElement(header.getSuccessors(), subEntry.getEnd())) {
|
||||||
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
BlockNode loopEnd = backEdge.getStart();
|
BlockNode loopEnd = backEdge.getStart();
|
||||||
@@ -55,12 +63,28 @@ public class FixMultiEntryLoops {
|
|||||||
BlockNode copyHeader = BlockSplitter.insertBlockBetween(mth, loopEnd, header);
|
BlockNode copyHeader = BlockSplitter.insertBlockBetween(mth, loopEnd, header);
|
||||||
BlockSplitter.copyBlockData(header, copyHeader);
|
BlockSplitter.copyBlockData(header, copyHeader);
|
||||||
BlockSplitter.replaceConnection(copyHeader, header, subEntryBlock);
|
BlockSplitter.replaceConnection(copyHeader, header, subEntryBlock);
|
||||||
mth.addDebugComment("Duplicate block to fix multi-entry loop: " + backEdge);
|
mth.addDebugComment("Duplicate block (" + header + ") to fix multi-entry loop: " + backEdge);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isSupportedPattern(BlockNode header, SpecialEdgeAttr subEntry) {
|
private static boolean isEndBlockEntry(MethodNode mth, SpecialEdgeAttr backEdge, List<SpecialEdgeAttr> crossEdges) {
|
||||||
return ListUtils.isSingleElement(header.getSuccessors(), subEntry.getEnd());
|
BlockNode loopEnd = backEdge.getStart();
|
||||||
|
SpecialEdgeAttr subEntry = ListUtils.filterOnlyOne(crossEdges, e -> e.getEnd() == loopEnd);
|
||||||
|
if (subEntry == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dupPath(mth, subEntry.getStart(), loopEnd, backEdge.getEnd());
|
||||||
|
mth.addDebugComment("Duplicate block (" + loopEnd + ") to fix multi-entry loop: " + backEdge);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicate 'center' block on path from 'start' to 'end'
|
||||||
|
*/
|
||||||
|
private static void dupPath(MethodNode mth, BlockNode start, BlockNode center, BlockNode end) {
|
||||||
|
BlockNode copyCenter = BlockSplitter.insertBlockBetween(mth, start, end);
|
||||||
|
BlockSplitter.copyBlockData(center, copyCenter);
|
||||||
|
BlockSplitter.removeConnection(start, center);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isSingleEntryLoop(SpecialEdgeAttr e) {
|
private static boolean isSingleEntryLoop(SpecialEdgeAttr e) {
|
||||||
@@ -75,21 +99,18 @@ public class FixMultiEntryLoops {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void detectSpecialEdges(MethodNode mth) {
|
private static void detectSpecialEdges(MethodNode mth) {
|
||||||
List<BlockNode> blocks = mth.getBasicBlocks();
|
BlockColor[] colors = new BlockColor[mth.getBasicBlocks().size()];
|
||||||
BlockColor[] colors = new BlockColor[blocks.size()];
|
|
||||||
Arrays.fill(colors, BlockColor.WHITE);
|
Arrays.fill(colors, BlockColor.WHITE);
|
||||||
colorDFS(mth, blocks, colors, mth.getEnterBlock().getId());
|
colorDFS(mth, colors, mth.getEnterBlock());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: transform to non-recursive form
|
// TODO: transform to non-recursive form
|
||||||
private static void colorDFS(MethodNode mth, List<BlockNode> blocks, BlockColor[] colors, int cur) {
|
private static void colorDFS(MethodNode mth, BlockColor[] colors, BlockNode block) {
|
||||||
colors[cur] = BlockColor.GRAY;
|
colors[block.getId()] = BlockColor.GRAY;
|
||||||
BlockNode block = blocks.get(cur);
|
|
||||||
for (BlockNode v : block.getSuccessors()) {
|
for (BlockNode v : block.getSuccessors()) {
|
||||||
int vId = v.getId();
|
switch (colors[v.getId()]) {
|
||||||
switch (colors[vId]) {
|
|
||||||
case WHITE:
|
case WHITE:
|
||||||
colorDFS(mth, blocks, colors, vId);
|
colorDFS(mth, colors, v);
|
||||||
break;
|
break;
|
||||||
case GRAY:
|
case GRAY:
|
||||||
mth.addAttr(AType.SPECIAL_EDGE, new SpecialEdgeAttr(SpecialEdgeType.BACK_EDGE, block, v));
|
mth.addAttr(AType.SPECIAL_EDGE, new SpecialEdgeAttr(SpecialEdgeType.BACK_EDGE, block, v));
|
||||||
@@ -99,6 +120,6 @@ public class FixMultiEntryLoops {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
colors[cur] = BlockColor.BLACK;
|
colors[block.getId()] = BlockColor.BLACK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package jadx.core.dex.visitors.blocks;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.BlockUtils;
|
||||||
|
import jadx.core.utils.EmptyBitSet;
|
||||||
|
|
||||||
|
public class PostDominatorTree {
|
||||||
|
|
||||||
|
public static void compute(MethodNode mth) {
|
||||||
|
if (!mth.contains(AFlag.COMPUTE_POST_DOM)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int mthBlocksCount = mth.getBasicBlocks().size();
|
||||||
|
List<BlockNode> sorted = new ArrayList<>(mthBlocksCount);
|
||||||
|
BlockUtils.visitReverseDFS(mth, sorted::add);
|
||||||
|
// temporary set block ids to match reverse sorted order
|
||||||
|
// save old ids for later remapping
|
||||||
|
int blocksCount = sorted.size();
|
||||||
|
int[] ids = new int[mthBlocksCount];
|
||||||
|
for (int i = 0; i < blocksCount; i++) {
|
||||||
|
ids[i] = sorted.get(i).getId();
|
||||||
|
}
|
||||||
|
mth.updateBlockIds(sorted);
|
||||||
|
|
||||||
|
BlockNode[] postDoms = DominatorTree.build(sorted, BlockNode::getSuccessors);
|
||||||
|
BlockNode firstBlock = sorted.get(0);
|
||||||
|
firstBlock.setPostDoms(EmptyBitSet.EMPTY);
|
||||||
|
firstBlock.setIPostDom(null);
|
||||||
|
for (int i = 1; i < blocksCount; i++) {
|
||||||
|
BlockNode block = sorted.get(i);
|
||||||
|
BlockNode iPostDom = postDoms[i];
|
||||||
|
block.setIPostDom(iPostDom);
|
||||||
|
BitSet postDomBS = DominatorTree.collectDoms(postDoms, iPostDom);
|
||||||
|
block.setPostDoms(postDomBS);
|
||||||
|
}
|
||||||
|
for (int i = 1; i < blocksCount; i++) {
|
||||||
|
BlockNode block = sorted.get(i);
|
||||||
|
BitSet bs = new BitSet(blocksCount);
|
||||||
|
block.getPostDoms().stream().forEach(n -> bs.set(ids[n]));
|
||||||
|
bs.clear(ids[i]);
|
||||||
|
block.setPostDoms(bs);
|
||||||
|
}
|
||||||
|
// check for missing blocks in 'sorted' list
|
||||||
|
// can be caused by infinite loops
|
||||||
|
int blocksDelta = mthBlocksCount - blocksCount;
|
||||||
|
if (blocksDelta != 0) {
|
||||||
|
int insnsCount = 0;
|
||||||
|
for (BlockNode block : mth.getBasicBlocks()) {
|
||||||
|
if (block.getPostDoms() == null) {
|
||||||
|
block.setPostDoms(EmptyBitSet.EMPTY);
|
||||||
|
block.setIPostDom(null);
|
||||||
|
insnsCount += block.getInstructions().size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mth.addInfoComment("Infinite loop detected, blocks: " + blocksDelta + ", insns: " + insnsCount);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// show error as a warning because this info not always used
|
||||||
|
mth.addWarnComment("Failed to build post-dominance tree", e);
|
||||||
|
} finally {
|
||||||
|
// revert block ids change
|
||||||
|
mth.updateBlockIds(mth.getBasicBlocks());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,7 +89,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
OptionalInt max = ssaVar.getUseList().stream().mapToInt(DebugInfoApplyVisitor::getInsnOffsetByArg).max();
|
OptionalInt max = ssaVar.getUseList().stream().mapToInt(DebugInfoApplyVisitor::getInsnOffsetByArg).max();
|
||||||
if (!max.isPresent()) {
|
if (max.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int startOffset = getInsnOffsetByArg(ssaVar.getAssign());
|
int startOffset = getInsnOffsetByArg(ssaVar.getAssign());
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package jadx.core.dex.visitors.prepare;
|
||||||
|
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
|
import jadx.core.dex.info.ConstStorage;
|
||||||
|
import jadx.core.dex.info.FieldInfo;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.dex.visitors.AbstractVisitor;
|
||||||
|
import jadx.core.dex.visitors.JadxVisitor;
|
||||||
|
import jadx.core.utils.android.AndroidResourcesMap;
|
||||||
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
|
// TODO: move this pass to separate "Android plugin"
|
||||||
|
@JadxVisitor(
|
||||||
|
name = "AddAndroidConstants",
|
||||||
|
desc = "Insert Android constants from resource mapping file",
|
||||||
|
runBefore = {
|
||||||
|
CollectConstValues.class
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public class AddAndroidConstants extends AbstractVisitor {
|
||||||
|
|
||||||
|
private static final String R_CLS = "android.R";
|
||||||
|
private static final String R_INNER_CLS = R_CLS + '$';
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(RootNode root) throws JadxException {
|
||||||
|
if (!root.getArgs().isReplaceConsts()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (root.resolveClass(R_CLS) != null) {
|
||||||
|
// Android R class already loaded
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ConstStorage constStorage = root.getConstValues();
|
||||||
|
AndroidResourcesMap.getMap().forEach((resId, path) -> {
|
||||||
|
int sep = path.indexOf('/');
|
||||||
|
String clsName = R_INNER_CLS + path.substring(0, sep);
|
||||||
|
String resName = path.substring(sep + 1);
|
||||||
|
ClassInfo cls = ClassInfo.fromName(root, clsName);
|
||||||
|
FieldInfo field = FieldInfo.from(root, cls, resName, ArgType.INT);
|
||||||
|
constStorage.addGlobalConstField(field, resId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package jadx.core.dex.visitors.prepare;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
|
import jadx.core.dex.info.AccessInfo;
|
||||||
|
import jadx.core.dex.info.ConstStorage;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.dex.visitors.AbstractVisitor;
|
||||||
|
import jadx.core.dex.visitors.JadxVisitor;
|
||||||
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
|
@JadxVisitor(
|
||||||
|
name = "CollectConstValues",
|
||||||
|
desc = "Collect and store values from static final fields"
|
||||||
|
)
|
||||||
|
public class CollectConstValues extends AbstractVisitor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visit(ClassNode cls) throws JadxException {
|
||||||
|
RootNode root = cls.root();
|
||||||
|
if (!root.getArgs().isReplaceConsts()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (cls.getFields().isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ConstStorage constStorage = root.getConstValues();
|
||||||
|
for (FieldNode fld : cls.getFields()) {
|
||||||
|
try {
|
||||||
|
Object value = getFieldConstValue(fld);
|
||||||
|
if (value != null) {
|
||||||
|
constStorage.addConstField(fld, value, fld.getAccessFlags().isPublic());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
cls.addWarnComment("Failed to process value of field: " + fld, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable Object getFieldConstValue(FieldNode fld) {
|
||||||
|
AccessInfo accFlags = fld.getAccessFlags();
|
||||||
|
if (accFlags.isStatic() && accFlags.isFinal()) {
|
||||||
|
EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE);
|
||||||
|
if (constVal != null && constVal != EncodedValue.NULL) {
|
||||||
|
return constVal.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package jadx.core.dex.visitors.regions;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
@@ -14,8 +13,6 @@ import java.util.Optional;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
@@ -60,8 +57,6 @@ import static jadx.core.utils.BlockUtils.getNextBlock;
|
|||||||
import static jadx.core.utils.BlockUtils.isPathExists;
|
import static jadx.core.utils.BlockUtils.isPathExists;
|
||||||
|
|
||||||
public class RegionMaker {
|
public class RegionMaker {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(RegionMaker.class);
|
|
||||||
|
|
||||||
private final MethodNode mth;
|
private final MethodNode mth;
|
||||||
private final int regionsLimit;
|
private final int regionsLimit;
|
||||||
private final BitSet processedBlocks;
|
private final BitSet processedBlocks;
|
||||||
@@ -794,53 +789,32 @@ public class RegionMaker {
|
|||||||
keys.add(SwitchRegion.DEFAULT_CASE_KEY);
|
keys.add(SwitchRegion.DEFAULT_CASE_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// search 'out' block - 'next' block after whole switch statement
|
|
||||||
BlockNode out;
|
|
||||||
LoopInfo loop = mth.getLoopForBlock(block);
|
|
||||||
if (loop == null) {
|
|
||||||
out = calcPostDomOut(mth, block, mth.getPreExitBlocks());
|
|
||||||
} else {
|
|
||||||
BlockNode loopEnd = loop.getEnd();
|
|
||||||
stack.addExit(loop.getStart());
|
|
||||||
if (stack.containsExit(block)
|
|
||||||
|| block == loopEnd
|
|
||||||
|| loopEnd.getPredecessors().contains(block)) {
|
|
||||||
// in exits or last insn in loop => no 'out' block
|
|
||||||
out = null;
|
|
||||||
} else {
|
|
||||||
// treat 'continue' as exit
|
|
||||||
out = calcPostDomOut(mth, block, loopEnd.getPredecessors());
|
|
||||||
if (out != null) {
|
|
||||||
insertContinueInSwitch(block, out, loopEnd);
|
|
||||||
} else {
|
|
||||||
// no 'continue'
|
|
||||||
out = calcPostDomOut(mth, block, Collections.singletonList(loopEnd));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (out == loop.getStart()) {
|
|
||||||
// no other outs instead back edge to loop start
|
|
||||||
out = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (out != null && processedBlocks.get(out.getId())) {
|
|
||||||
// out block already processed, prevent endless loop
|
|
||||||
throw new JadxRuntimeException("Failed to find switch 'out' block");
|
|
||||||
}
|
|
||||||
|
|
||||||
SwitchRegion sw = new SwitchRegion(currentRegion, block);
|
SwitchRegion sw = new SwitchRegion(currentRegion, block);
|
||||||
insn.addAttr(new RegionRefAttr(sw));
|
insn.addAttr(new RegionRefAttr(sw));
|
||||||
currentRegion.getSubBlocks().add(sw);
|
currentRegion.getSubBlocks().add(sw);
|
||||||
stack.push(sw);
|
stack.push(sw);
|
||||||
|
|
||||||
|
BlockNode out = calcSwitchOut(block, stack);
|
||||||
stack.addExit(out);
|
stack.addExit(out);
|
||||||
|
|
||||||
// detect fallthrough cases
|
processFallThroughCases(sw, out, stack, blocksMap);
|
||||||
|
removeEmptyCases(insn, sw, defCase);
|
||||||
|
|
||||||
|
stack.pop();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processFallThroughCases(SwitchRegion sw, @Nullable BlockNode out,
|
||||||
|
RegionStack stack, Map<BlockNode, List<Object>> blocksMap) {
|
||||||
Map<BlockNode, BlockNode> fallThroughCases = new LinkedHashMap<>();
|
Map<BlockNode, BlockNode> fallThroughCases = new LinkedHashMap<>();
|
||||||
if (out != null) {
|
if (out != null) {
|
||||||
|
// detect fallthrough cases
|
||||||
BitSet caseBlocks = BlockUtils.blocksToBitSet(mth, blocksMap.keySet());
|
BitSet caseBlocks = BlockUtils.blocksToBitSet(mth, blocksMap.keySet());
|
||||||
caseBlocks.clear(out.getId());
|
caseBlocks.clear(out.getId());
|
||||||
for (BlockNode successor : block.getCleanSuccessors()) {
|
for (BlockNode successor : sw.getHeader().getCleanSuccessors()) {
|
||||||
BlockNode fallThroughBlock = searchFallThroughCase(successor, out, caseBlocks);
|
BitSet df = successor.getDomFrontier();
|
||||||
if (fallThroughBlock != null) {
|
if (df.intersects(caseBlocks)) {
|
||||||
|
BlockNode fallThroughBlock = getOneIntersectionBlock(out, caseBlocks, df);
|
||||||
fallThroughCases.put(successor, fallThroughBlock);
|
fallThroughCases.put(successor, fallThroughBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -874,26 +848,6 @@ public class RegionMaker {
|
|||||||
// 'break' instruction will be inserted in RegionMakerVisitor.PostRegionVisitor
|
// 'break' instruction will be inserted in RegionMakerVisitor.PostRegionVisitor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeEmptyCases(insn, sw, defCase);
|
|
||||||
|
|
||||||
stack.pop();
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private BlockNode searchFallThroughCase(BlockNode successor, BlockNode out, BitSet caseBlocks) {
|
|
||||||
BitSet df = successor.getDomFrontier();
|
|
||||||
if (df.intersects(caseBlocks)) {
|
|
||||||
return getOneIntersectionBlock(out, caseBlocks, df);
|
|
||||||
}
|
|
||||||
Set<BlockNode> allPathsBlocks = BlockUtils.getAllPathsBlocks(successor, out);
|
|
||||||
Map<BlockNode, BitSet> bitSetMap = BlockUtils.calcPartialPostDominance(mth, allPathsBlocks, out);
|
|
||||||
BitSet pdoms = bitSetMap.get(successor);
|
|
||||||
if (pdoms != null && pdoms.intersects(caseBlocks)) {
|
|
||||||
return getOneIntersectionBlock(out, caseBlocks, pdoms);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -904,35 +858,71 @@ public class RegionMaker {
|
|||||||
return BlockUtils.bitSetToOneBlock(mth, caseExits);
|
return BlockUtils.bitSetToOneBlock(mth, caseExits);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
private @Nullable BlockNode calcSwitchOut(BlockNode block, RegionStack stack) {
|
||||||
private static BlockNode calcPostDomOut(MethodNode mth, BlockNode block, List<BlockNode> exits) {
|
// union of case blocks dominance frontier
|
||||||
if (exits.size() == 1 && mth.getExitBlock().equals(exits.get(0))) {
|
// works if no fallthrough cases and no returns inside switch
|
||||||
// simple case: for only one exit which is equal to method exit block
|
BitSet outs = BlockUtils.newBlocksBitSet(mth);
|
||||||
return BlockUtils.calcImmediatePostDominator(mth, block);
|
|
||||||
}
|
|
||||||
// fast search: union of blocks dominance frontier
|
|
||||||
// work if no fallthrough cases and no returns inside switch
|
|
||||||
BitSet outs = BlockUtils.copyBlocksBitSet(mth, block.getDomFrontier());
|
|
||||||
for (BlockNode s : block.getCleanSuccessors()) {
|
for (BlockNode s : block.getCleanSuccessors()) {
|
||||||
outs.or(s.getDomFrontier());
|
outs.or(s.getDomFrontier());
|
||||||
}
|
}
|
||||||
outs.clear(block.getId());
|
outs.clear(block.getId());
|
||||||
|
if (outs.isEmpty()) {
|
||||||
|
// switch already contains method exit
|
||||||
|
// add everything, out block not needed
|
||||||
|
return mth.getExitBlock();
|
||||||
|
}
|
||||||
|
|
||||||
if (outs.cardinality() != 1) {
|
BlockNode out;
|
||||||
// slow search: calculate partial post-dominance for every exit node
|
if (outs.cardinality() == 1) {
|
||||||
BitSet ipdoms = BlockUtils.newBlocksBitSet(mth);
|
// single exit
|
||||||
for (BlockNode exitBlock : exits) {
|
out = BlockUtils.bitSetToOneBlock(mth, outs);
|
||||||
if (BlockUtils.isAnyPathExists(block, exitBlock)) {
|
} else {
|
||||||
Set<BlockNode> pathBlocks = BlockUtils.getAllPathsBlocks(block, exitBlock);
|
// several switch exits
|
||||||
BlockNode ipdom = BlockUtils.calcPartialImmediatePostDominator(mth, block, pathBlocks, exitBlock);
|
// possible 'return', 'continue' or fallthrough in one of the cases
|
||||||
if (ipdom != null) {
|
LoopInfo loop = mth.getLoopForBlock(block);
|
||||||
ipdoms.set(ipdom.getId());
|
if (loop != null) {
|
||||||
|
outs.andNot(block.getPostDoms());
|
||||||
|
out = BlockUtils.bitSetToOneBlock(mth, outs);
|
||||||
|
if (out != null) {
|
||||||
|
insertContinueInSwitch(block, out, loop.getEnd());
|
||||||
|
if (out == loop.getStart()) {
|
||||||
|
// no other outs instead back edge to loop start
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
outs.clear(mth.getExitBlock().getId());
|
||||||
|
BlockNode imPostDom = block.getIPostDom();
|
||||||
|
if (outs.get(imPostDom.getId())) {
|
||||||
|
return imPostDom;
|
||||||
|
}
|
||||||
|
outs.andNot(block.getPostDoms());
|
||||||
|
out = BlockUtils.bitSetToOneBlock(mth, outs);
|
||||||
}
|
}
|
||||||
outs.and(ipdoms);
|
|
||||||
}
|
}
|
||||||
return BlockUtils.bitSetToOneBlock(mth, outs);
|
if (out != null && mth.isPreExitBlock(out)) {
|
||||||
|
// include 'return' or 'throw' in case blocks
|
||||||
|
out = mth.getExitBlock();
|
||||||
|
}
|
||||||
|
BlockNode imPostDom = block.getIPostDom();
|
||||||
|
if (out != imPostDom && !mth.isPreExitBlock(imPostDom)) {
|
||||||
|
// stop other paths at common exit
|
||||||
|
stack.addExit(imPostDom);
|
||||||
|
}
|
||||||
|
if (block.getCleanSuccessors().contains(imPostDom)) {
|
||||||
|
// add exit to stop on empty 'default' block
|
||||||
|
stack.addExit(imPostDom);
|
||||||
|
}
|
||||||
|
if (out == null) {
|
||||||
|
mth.addWarnComment("Failed to find 'out' block for switch in " + block + ". Please report as an issue.");
|
||||||
|
// fallback option; should work in most cases
|
||||||
|
out = block.getIPostDom();
|
||||||
|
}
|
||||||
|
if (out != null && processedBlocks.get(out.getId())) {
|
||||||
|
// 'out' block already processed, prevent endless loop
|
||||||
|
throw new JadxRuntimeException("Failed to find switch 'out' block (already processed)");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -999,18 +989,21 @@ public class RegionMaker {
|
|||||||
return newBlocksMap;
|
return newBlocksMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertContinueInSwitch(BlockNode block, BlockNode out, BlockNode end) {
|
private void insertContinueInSwitch(BlockNode switchBlock, BlockNode switchOut, BlockNode loopEnd) {
|
||||||
int endId = end.getId();
|
for (BlockNode caseBlock : switchBlock.getCleanSuccessors()) {
|
||||||
for (BlockNode s : block.getCleanSuccessors()) {
|
if (caseBlock.getDomFrontier().get(loopEnd.getId()) && caseBlock != switchOut) {
|
||||||
if (s.getDomFrontier().get(endId) && s != out) {
|
|
||||||
// search predecessor of loop end on path from this successor
|
// search predecessor of loop end on path from this successor
|
||||||
List<BlockNode> list = BlockUtils.collectBlocksDominatedBy(mth, s, s);
|
Set<BlockNode> list = new HashSet<>(BlockUtils.collectBlocksDominatedBy(mth, caseBlock, caseBlock));
|
||||||
for (BlockNode p : end.getPredecessors()) {
|
if (list.contains(switchOut) || switchOut.getPredecessors().stream().anyMatch(list::contains)) {
|
||||||
if (list.contains(p)) {
|
// 'continue' not needed
|
||||||
if (p.isSynthetic()) {
|
} else {
|
||||||
p.getInstructions().add(new InsnNode(InsnType.CONTINUE, 0));
|
for (BlockNode p : loopEnd.getPredecessors()) {
|
||||||
|
if (list.contains(p)) {
|
||||||
|
if (p.isSynthetic()) {
|
||||||
|
p.getInstructions().add(new InsnNode(InsnType.CONTINUE, 0));
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-1
@@ -84,7 +84,7 @@ public class ProcessVariables extends AbstractVisitor {
|
|||||||
if (insn.canRemoveResult()) {
|
if (insn.canRemoveResult()) {
|
||||||
// remove unused result
|
// remove unused result
|
||||||
remove = true;
|
remove = true;
|
||||||
} else if (insn.isConstInsn()) {
|
} else if (canRemoveInsn(insn)) {
|
||||||
// remove whole insn
|
// remove whole insn
|
||||||
insn.add(AFlag.REMOVE);
|
insn.add(AFlag.REMOVE);
|
||||||
insn.add(AFlag.DONT_GENERATE);
|
insn.add(AFlag.DONT_GENERATE);
|
||||||
@@ -101,6 +101,22 @@ public class ProcessVariables extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove insn if a result is not used
|
||||||
|
*/
|
||||||
|
private boolean canRemoveInsn(InsnNode insn) {
|
||||||
|
if (insn.isConstInsn()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch (insn.getType()) {
|
||||||
|
case CAST:
|
||||||
|
case CHECK_CAST:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isVarUnused(MethodNode mth, @Nullable SSAVar ssaVar) {
|
private boolean isVarUnused(MethodNode mth, @Nullable SSAVar ssaVar) {
|
||||||
if (ssaVar == null) {
|
if (ssaVar == null) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import jadx.core.dex.nodes.FieldNode;
|
|||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.PackageNode;
|
import jadx.core.dex.nodes.PackageNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.StringUtils;
|
||||||
|
|
||||||
public class UserRenames {
|
public class UserRenames {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(UserRenames.class);
|
private static final Logger LOG = LoggerFactory.getLogger(UserRenames.class);
|
||||||
@@ -57,7 +58,12 @@ public class UserRenames {
|
|||||||
case FIELD:
|
case FIELD:
|
||||||
FieldNode fieldNode = cls.searchFieldByShortId(nodeRef.getShortId());
|
FieldNode fieldNode = cls.searchFieldByShortId(nodeRef.getShortId());
|
||||||
if (fieldNode == null) {
|
if (fieldNode == null) {
|
||||||
LOG.warn("Field reference not found: {}", nodeRef);
|
String fieldName = StringUtils.getPrefix(nodeRef.getShortId(), ":");
|
||||||
|
String fieldSign = cls.getFields().stream()
|
||||||
|
.filter(f -> f.getFieldInfo().getName().equals(fieldName))
|
||||||
|
.map(f -> f.getFieldInfo().getShortId())
|
||||||
|
.collect(Collectors.joining());
|
||||||
|
LOG.warn("Field reference not found: {}. Fields with same name: {}", nodeRef, fieldSign);
|
||||||
} else {
|
} else {
|
||||||
fieldNode.rename(rename.getNewName());
|
fieldNode.rename(rename.getNewName());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package jadx.core.dex.visitors.shrink;
|
|||||||
import java.util.ArrayList;
|
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.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -64,12 +63,9 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
|||||||
List<WrapInfo> wrapList = new ArrayList<>();
|
List<WrapInfo> wrapList = new ArrayList<>();
|
||||||
for (ArgsInfo argsInfo : argsList) {
|
for (ArgsInfo argsInfo : argsList) {
|
||||||
List<RegisterArg> args = argsInfo.getArgs();
|
List<RegisterArg> args = argsInfo.getArgs();
|
||||||
if (!args.isEmpty()) {
|
for (int i = args.size() - 1; i >= 0; i--) {
|
||||||
ListIterator<RegisterArg> it = args.listIterator(args.size());
|
RegisterArg arg = args.get(i);
|
||||||
while (it.hasPrevious()) {
|
checkInline(mth, block, insnList, wrapList, argsInfo, arg);
|
||||||
RegisterArg arg = it.previous();
|
|
||||||
checkInline(mth, block, insnList, wrapList, argsInfo, arg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!wrapList.isEmpty()) {
|
if (!wrapList.isEmpty()) {
|
||||||
|
|||||||
@@ -30,4 +30,9 @@ public final class FinishTypeInference extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "FinishTypeInference";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,844 @@
|
|||||||
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.core.Consts;
|
||||||
|
import jadx.core.clsp.ClspGraph;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||||
|
import jadx.core.dex.instructions.ArithNode;
|
||||||
|
import jadx.core.dex.instructions.ArithOp;
|
||||||
|
import jadx.core.dex.instructions.IndexInsnNode;
|
||||||
|
import jadx.core.dex.instructions.InsnType;
|
||||||
|
import jadx.core.dex.instructions.PhiInsn;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
|
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.dex.visitors.AbstractVisitor;
|
||||||
|
import jadx.core.dex.visitors.InitCodeVariables;
|
||||||
|
import jadx.core.dex.visitors.JadxVisitor;
|
||||||
|
import jadx.core.dex.visitors.ModVisitor;
|
||||||
|
import jadx.core.dex.visitors.blocks.BlockSplitter;
|
||||||
|
import jadx.core.utils.BlockUtils;
|
||||||
|
import jadx.core.utils.InsnList;
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||||
|
|
||||||
|
@JadxVisitor(
|
||||||
|
name = "Fix Types Visitor",
|
||||||
|
desc = "Try various methods to fix unresolved types",
|
||||||
|
runAfter = {
|
||||||
|
TypeInferenceVisitor.class
|
||||||
|
},
|
||||||
|
runBefore = {
|
||||||
|
FinishTypeInference.class
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public final class FixTypesVisitor extends AbstractVisitor {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(FixTypesVisitor.class);
|
||||||
|
|
||||||
|
private final TypeInferenceVisitor typeInference = new TypeInferenceVisitor();
|
||||||
|
|
||||||
|
private TypeUpdate typeUpdate;
|
||||||
|
private List<Function<MethodNode, Boolean>> resolvers;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(RootNode root) {
|
||||||
|
this.typeUpdate = root.getTypeUpdate();
|
||||||
|
this.typeInference.init(root);
|
||||||
|
this.resolvers = Arrays.asList(
|
||||||
|
this::tryRestoreTypeVarCasts,
|
||||||
|
this::tryInsertCasts,
|
||||||
|
this::tryDeduceTypes,
|
||||||
|
this::trySplitConstInsns,
|
||||||
|
this::tryToFixIncompatiblePrimitives,
|
||||||
|
this::tryToForceImmutableTypes,
|
||||||
|
this::tryInsertAdditionalMove,
|
||||||
|
this::runMultiVariableSearch,
|
||||||
|
this::tryRemoveGenerics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(MethodNode mth) {
|
||||||
|
if (mth.isNoCode() || checkTypes(mth)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
for (Function<MethodNode, Boolean> resolver : resolvers) {
|
||||||
|
if (resolver.apply(mth) && checkTypes(mth)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
mth.addError("Types fix failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if all types resolved
|
||||||
|
*/
|
||||||
|
private static boolean checkTypes(MethodNode mth) {
|
||||||
|
for (SSAVar var : mth.getSVars()) {
|
||||||
|
ArgType type = var.getTypeInfo().getType();
|
||||||
|
if (!type.isTypeKnown()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean runMultiVariableSearch(MethodNode mth) {
|
||||||
|
try {
|
||||||
|
TypeSearch typeSearch = new TypeSearch(mth);
|
||||||
|
if (!typeSearch.run()) {
|
||||||
|
mth.addWarnComment("Multi-variable type inference failed");
|
||||||
|
}
|
||||||
|
for (SSAVar var : mth.getSVars()) {
|
||||||
|
if (!var.getTypeInfo().getType().isTypeKnown()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
mth.addWarnComment("Multi-variable type inference failed. Error: " + Utils.getStackTrace(e));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setBestType(MethodNode mth, SSAVar ssaVar) {
|
||||||
|
try {
|
||||||
|
return calculateFromBounds(mth, ssaVar);
|
||||||
|
} catch (JadxOverflowException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
mth.addWarnComment("Failed to calculate best type for var: " + ssaVar, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean calculateFromBounds(MethodNode mth, SSAVar ssaVar) {
|
||||||
|
TypeInfo typeInfo = ssaVar.getTypeInfo();
|
||||||
|
Set<ITypeBound> bounds = typeInfo.getBounds();
|
||||||
|
Optional<ArgType> bestTypeOpt = selectBestTypeFromBounds(bounds);
|
||||||
|
if (bestTypeOpt.isEmpty()) {
|
||||||
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
|
LOG.warn("Failed to select best type from bounds, count={} : ", bounds.size());
|
||||||
|
for (ITypeBound bound : bounds) {
|
||||||
|
LOG.warn(" {}", bound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ArgType candidateType = bestTypeOpt.get();
|
||||||
|
TypeUpdateResult result = typeUpdate.apply(mth, ssaVar, candidateType);
|
||||||
|
if (result == TypeUpdateResult.REJECT) {
|
||||||
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
|
if (ssaVar.getTypeInfo().getType().equals(candidateType)) {
|
||||||
|
LOG.info("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
|
||||||
|
} else if (candidateType.isTypeKnown()) {
|
||||||
|
LOG.debug("Type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return result == TypeUpdateResult.CHANGED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ArgType> selectBestTypeFromBounds(Set<ITypeBound> bounds) {
|
||||||
|
return bounds.stream()
|
||||||
|
.map(ITypeBound::getType)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.max(typeUpdate.getTypeCompare().getComparator());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryPossibleTypes(MethodNode mth, SSAVar var, ArgType type) {
|
||||||
|
List<ArgType> types = makePossibleTypesList(type, var);
|
||||||
|
if (types.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (ArgType candidateType : types) {
|
||||||
|
TypeUpdateResult result = typeUpdate.apply(mth, var, candidateType);
|
||||||
|
if (result == TypeUpdateResult.CHANGED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ArgType> makePossibleTypesList(ArgType type, @Nullable SSAVar var) {
|
||||||
|
if (type.isArray()) {
|
||||||
|
List<ArgType> list = new ArrayList<>();
|
||||||
|
for (ArgType arrElemType : makePossibleTypesList(type.getArrayElement(), null)) {
|
||||||
|
list.add(ArgType.array(arrElemType));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
if (var != null) {
|
||||||
|
for (ITypeBound b : var.getTypeInfo().getBounds()) {
|
||||||
|
ArgType boundType = b.getType();
|
||||||
|
if (boundType.isObject() || boundType.isArray()) {
|
||||||
|
// don't add primitive types
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<ArgType> list = new ArrayList<>();
|
||||||
|
for (PrimitiveType possibleType : type.getPossibleTypes()) {
|
||||||
|
if (possibleType == PrimitiveType.VOID) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
list.add(ArgType.convertFromPrimitiveType(possibleType));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryDeduceTypes(MethodNode mth) {
|
||||||
|
boolean fixed = false;
|
||||||
|
for (SSAVar ssaVar : mth.getSVars()) {
|
||||||
|
if (deduceType(mth, ssaVar)) {
|
||||||
|
fixed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
|
private boolean deduceType(MethodNode mth, SSAVar var) {
|
||||||
|
if (var.isTypeImmutable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ArgType type = var.getTypeInfo().getType();
|
||||||
|
if (type.isTypeKnown()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// try best type from bounds again
|
||||||
|
if (setBestType(mth, var)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// try all possible types (useful for primitives)
|
||||||
|
if (tryPossibleTypes(mth, var, type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// for objects try super types
|
||||||
|
if (tryWiderObjects(mth, var)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryRemoveGenerics(MethodNode mth) {
|
||||||
|
boolean resolved = true;
|
||||||
|
for (SSAVar var : mth.getSVars()) {
|
||||||
|
ArgType type = var.getTypeInfo().getType();
|
||||||
|
if (!type.isTypeKnown()
|
||||||
|
&& !var.isTypeImmutable()
|
||||||
|
&& !tryRawType(mth, var)) {
|
||||||
|
resolved = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryRawType(MethodNode mth, SSAVar var) {
|
||||||
|
Set<ArgType> objTypes = new LinkedHashSet<>();
|
||||||
|
for (ITypeBound bound : var.getTypeInfo().getBounds()) {
|
||||||
|
ArgType boundType = bound.getType();
|
||||||
|
if (boundType.isTypeKnown() && boundType.isObject()) {
|
||||||
|
objTypes.add(boundType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (objTypes.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (ArgType objType : objTypes) {
|
||||||
|
if (checkRawType(mth, var, objType)) {
|
||||||
|
mth.addDebugComment("Type inference failed for " + var.toShortString() + "."
|
||||||
|
+ " Raw type applied. Possible types: " + Utils.listToString(objTypes));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkRawType(MethodNode mth, SSAVar var, ArgType objType) {
|
||||||
|
if (objType.isObject() && objType.containsGeneric()) {
|
||||||
|
ArgType rawType = objType.isGenericType() ? ArgType.OBJECT : ArgType.object(objType.getObject());
|
||||||
|
TypeUpdateResult result = typeUpdate.applyWithWiderAllow(mth, var, rawType);
|
||||||
|
return result == TypeUpdateResult.CHANGED;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix check casts to type var extend type:
|
||||||
|
* <br>
|
||||||
|
* {@code <T extends Comparable> T var = (Comparable) obj; => T var = (T) obj; }
|
||||||
|
*/
|
||||||
|
private boolean tryRestoreTypeVarCasts(MethodNode mth) {
|
||||||
|
int changed = 0;
|
||||||
|
List<SSAVar> mthSVars = mth.getSVars();
|
||||||
|
for (SSAVar var : mthSVars) {
|
||||||
|
changed += restoreTypeVarCasts(var);
|
||||||
|
}
|
||||||
|
if (changed == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
|
mth.addDebugComment("Restore " + changed + " type vars casts");
|
||||||
|
}
|
||||||
|
typeInference.initTypeBounds(mth);
|
||||||
|
return typeInference.runTypePropagation(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int restoreTypeVarCasts(SSAVar var) {
|
||||||
|
TypeInfo typeInfo = var.getTypeInfo();
|
||||||
|
Set<ITypeBound> bounds = typeInfo.getBounds();
|
||||||
|
if (!ListUtils.anyMatch(bounds, t -> t.getType().isGenericType())) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
List<ITypeBound> casts = ListUtils.filter(bounds, TypeBoundCheckCastAssign.class::isInstance);
|
||||||
|
if (casts.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ArgType bestType = selectBestTypeFromBounds(bounds).orElse(ArgType.UNKNOWN);
|
||||||
|
if (!bestType.isGenericType()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
List<ArgType> extendTypes = bestType.getExtendTypes();
|
||||||
|
if (extendTypes.size() != 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int fixed = 0;
|
||||||
|
ArgType extendType = extendTypes.get(0);
|
||||||
|
for (ITypeBound bound : casts) {
|
||||||
|
TypeBoundCheckCastAssign cast = (TypeBoundCheckCastAssign) bound;
|
||||||
|
ArgType castType = cast.getType();
|
||||||
|
TypeCompareEnum result = typeUpdate.getTypeCompare().compareTypes(extendType, castType);
|
||||||
|
if (result.isEqual() || result == TypeCompareEnum.NARROW_BY_GENERIC) {
|
||||||
|
cast.getInsn().updateIndex(bestType);
|
||||||
|
fixed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "ForLoopReplaceableByWhile", "ForLoopReplaceableByForEach" })
|
||||||
|
private boolean tryInsertCasts(MethodNode mth) {
|
||||||
|
int added = 0;
|
||||||
|
List<SSAVar> mthSVars = mth.getSVars();
|
||||||
|
int varsCount = mthSVars.size();
|
||||||
|
for (int i = 0; i < varsCount; i++) {
|
||||||
|
SSAVar var = mthSVars.get(i);
|
||||||
|
ArgType type = var.getTypeInfo().getType();
|
||||||
|
if (!type.isTypeKnown() && !var.isTypeImmutable()) {
|
||||||
|
added += tryInsertVarCast(mth, var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (added != 0) {
|
||||||
|
InitCodeVariables.rerun(mth);
|
||||||
|
typeInference.initTypeBounds(mth);
|
||||||
|
return typeInference.runTypePropagation(mth);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int tryInsertVarCast(MethodNode mth, SSAVar var) {
|
||||||
|
for (ITypeBound bound : var.getTypeInfo().getBounds()) {
|
||||||
|
ArgType boundType = bound.getType();
|
||||||
|
if (boundType.isTypeKnown()
|
||||||
|
&& !boundType.equals(var.getTypeInfo().getType())
|
||||||
|
&& boundType.containsTypeVariable()
|
||||||
|
&& !mth.root().getTypeUtils().containsUnknownTypeVar(mth, boundType)) {
|
||||||
|
if (insertAssignCast(mth, var, boundType)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return insertUseCasts(mth, var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int insertUseCasts(MethodNode mth, SSAVar var) {
|
||||||
|
List<RegisterArg> useList = var.getUseList();
|
||||||
|
if (useList.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int useCasts = 0;
|
||||||
|
for (RegisterArg useReg : new ArrayList<>(useList)) {
|
||||||
|
if (insertSoftUseCast(mth, useReg)) {
|
||||||
|
useCasts++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return useCasts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean insertAssignCast(MethodNode mth, SSAVar var, ArgType castType) {
|
||||||
|
RegisterArg assignArg = var.getAssign();
|
||||||
|
InsnNode assignInsn = assignArg.getParentInsn();
|
||||||
|
if (assignInsn == null || assignInsn.getType() == InsnType.PHI) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn);
|
||||||
|
if (assignBlock == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
assignInsn.setResult(assignArg.duplicateWithNewSSAVar(mth));
|
||||||
|
IndexInsnNode castInsn = makeSoftCastInsn(assignArg.duplicate(), assignInsn.getResult().duplicate(), castType);
|
||||||
|
return BlockUtils.insertAfterInsn(assignBlock, assignInsn, castInsn);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean insertSoftUseCast(MethodNode mth, RegisterArg useArg) {
|
||||||
|
InsnNode useInsn = useArg.getParentInsn();
|
||||||
|
if (useInsn == null || useInsn.getType() == InsnType.PHI) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (useInsn.getType() == InsnType.IF && useInsn.getArg(1).isZeroConst()) {
|
||||||
|
// cast isn't needed if compare with null
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BlockNode useBlock = BlockUtils.getBlockByInsn(mth, useInsn);
|
||||||
|
if (useBlock == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
IndexInsnNode castInsn = makeSoftCastInsn(
|
||||||
|
useArg.duplicateWithNewSSAVar(mth),
|
||||||
|
useArg.duplicate(),
|
||||||
|
useArg.getInitType());
|
||||||
|
useInsn.replaceArg(useArg, castInsn.getResult().duplicate());
|
||||||
|
boolean success = BlockUtils.insertBeforeInsn(useBlock, useInsn, castInsn);
|
||||||
|
if (Consts.DEBUG_TYPE_INFERENCE && success) {
|
||||||
|
LOG.info("Insert soft cast for {} before {} in {}", useArg, useInsn, useBlock);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IndexInsnNode makeSoftCastInsn(RegisterArg result, RegisterArg arg, ArgType castType) {
|
||||||
|
IndexInsnNode castInsn = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1);
|
||||||
|
castInsn.setResult(result);
|
||||||
|
castInsn.addArg(arg);
|
||||||
|
castInsn.add(AFlag.SOFT_CAST);
|
||||||
|
castInsn.add(AFlag.SYNTHETIC);
|
||||||
|
return castInsn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean trySplitConstInsns(MethodNode mth) {
|
||||||
|
boolean constSplit = false;
|
||||||
|
for (SSAVar var : new ArrayList<>(mth.getSVars())) {
|
||||||
|
if (checkAndSplitConstInsn(mth, var)) {
|
||||||
|
constSplit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!constSplit) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InitCodeVariables.rerun(mth);
|
||||||
|
typeInference.initTypeBounds(mth);
|
||||||
|
return typeInference.runTypePropagation(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkAndSplitConstInsn(MethodNode mth, SSAVar var) {
|
||||||
|
ArgType type = var.getTypeInfo().getType();
|
||||||
|
if (type.isTypeKnown() || var.isTypeImmutable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return splitByPhi(mth, var) || dupConst(mth, var);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean dupConst(MethodNode mth, SSAVar var) {
|
||||||
|
InsnNode assignInsn = var.getAssign().getAssignInsn();
|
||||||
|
if (!InsnUtils.isInsnType(assignInsn, InsnType.CONST)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (var.getUseList().size() < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn);
|
||||||
|
if (assignBlock == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
assignInsn.remove(AFlag.DONT_INLINE);
|
||||||
|
int insertIndex = 1 + BlockUtils.getInsnIndexInBlock(assignBlock, assignInsn);
|
||||||
|
List<RegisterArg> useList = new ArrayList<>(var.getUseList());
|
||||||
|
for (int i = 0, useCount = useList.size(); i < useCount; i++) {
|
||||||
|
RegisterArg useArg = useList.get(i);
|
||||||
|
useArg.remove(AFlag.DONT_INLINE_CONST);
|
||||||
|
if (i == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
InsnNode useInsn = useArg.getParentInsn();
|
||||||
|
if (useInsn == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
InsnNode newInsn = assignInsn.copyWithNewSsaVar(mth);
|
||||||
|
assignBlock.getInstructions().add(insertIndex, newInsn);
|
||||||
|
useInsn.replaceArg(useArg, newInsn.getResult().duplicate());
|
||||||
|
}
|
||||||
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
|
LOG.debug("Duplicate const insn {} times: {} in {}", useList.size(), assignInsn, assignBlock);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For every PHI make separate CONST insn
|
||||||
|
*/
|
||||||
|
private static boolean splitByPhi(MethodNode mth, SSAVar var) {
|
||||||
|
if (var.getUsedInPhi().size() < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InsnNode assignInsn = var.getAssign().getAssignInsn();
|
||||||
|
InsnNode constInsn = InsnUtils.checkInsnType(assignInsn, InsnType.CONST);
|
||||||
|
if (constInsn == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BlockNode blockNode = BlockUtils.getBlockByInsn(mth, constInsn);
|
||||||
|
if (blockNode == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean first = true;
|
||||||
|
for (PhiInsn phiInsn : var.getUsedInPhi()) {
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
InsnNode copyInsn = constInsn.copyWithNewSsaVar(mth);
|
||||||
|
copyInsn.add(AFlag.SYNTHETIC);
|
||||||
|
BlockUtils.insertAfterInsn(blockNode, constInsn, copyInsn);
|
||||||
|
|
||||||
|
RegisterArg phiArg = phiInsn.getArgBySsaVar(var);
|
||||||
|
phiInsn.replaceArg(phiArg, copyInsn.getResult().duplicate());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryInsertAdditionalMove(MethodNode mth) {
|
||||||
|
int insnsAdded = 0;
|
||||||
|
for (BlockNode block : mth.getBasicBlocks()) {
|
||||||
|
PhiListAttr phiListAttr = block.get(AType.PHI_LIST);
|
||||||
|
if (phiListAttr != null) {
|
||||||
|
for (PhiInsn phiInsn : phiListAttr.getList()) {
|
||||||
|
insnsAdded += tryInsertAdditionalInsn(mth, phiInsn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (insnsAdded == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
|
mth.addDebugComment("Additional " + insnsAdded + " move instructions added to help type inference");
|
||||||
|
}
|
||||||
|
InitCodeVariables.rerun(mth);
|
||||||
|
typeInference.initTypeBounds(mth);
|
||||||
|
if (typeInference.runTypePropagation(mth) && checkTypes(mth)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return tryDeduceTypes(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add MOVE instruction before PHI in bound blocks to make 'soft' type link.
|
||||||
|
* This allows using different types in blocks merged by PHI.
|
||||||
|
*/
|
||||||
|
private int tryInsertAdditionalInsn(MethodNode mth, PhiInsn phiInsn) {
|
||||||
|
ArgType phiType = getCommonTypeForPhiArgs(phiInsn);
|
||||||
|
if (phiType != null && phiType.isTypeKnown()) {
|
||||||
|
// all args have the same known type => nothing to do here
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// check if instructions can be inserted
|
||||||
|
if (insertMovesForPhi(mth, phiInsn, false) == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// check passed => apply
|
||||||
|
return insertMovesForPhi(mth, phiInsn, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ArgType getCommonTypeForPhiArgs(PhiInsn phiInsn) {
|
||||||
|
ArgType phiArgType = null;
|
||||||
|
for (InsnArg arg : phiInsn.getArguments()) {
|
||||||
|
ArgType type = arg.getType();
|
||||||
|
if (phiArgType == null) {
|
||||||
|
phiArgType = type;
|
||||||
|
} else if (!phiArgType.equals(type)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return phiArgType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int insertMovesForPhi(MethodNode mth, PhiInsn phiInsn, boolean apply) {
|
||||||
|
int argsCount = phiInsn.getArgsCount();
|
||||||
|
int count = 0;
|
||||||
|
for (int argIndex = 0; argIndex < argsCount; argIndex++) {
|
||||||
|
RegisterArg reg = phiInsn.getArg(argIndex);
|
||||||
|
BlockNode startBlock = phiInsn.getBlockByArgIndex(argIndex);
|
||||||
|
BlockNode blockNode = checkBlockForInsnInsert(startBlock);
|
||||||
|
if (blockNode == null) {
|
||||||
|
mth.addDebugComment("Failed to insert an additional move for type inference into block " + startBlock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
boolean add = true;
|
||||||
|
SSAVar var = reg.getSVar();
|
||||||
|
InsnNode assignInsn = var.getAssign().getAssignInsn();
|
||||||
|
if (assignInsn != null) {
|
||||||
|
InsnType assignType = assignInsn.getType();
|
||||||
|
if (assignType == InsnType.CONST
|
||||||
|
|| (assignType == InsnType.MOVE && var.getUseCount() == 1)) {
|
||||||
|
add = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (add) {
|
||||||
|
count++;
|
||||||
|
if (apply) {
|
||||||
|
insertMove(mth, blockNode, phiInsn, reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertMove(MethodNode mth, BlockNode blockNode, PhiInsn phiInsn, RegisterArg reg) {
|
||||||
|
SSAVar var = reg.getSVar();
|
||||||
|
int regNum = reg.getRegNum();
|
||||||
|
RegisterArg resultArg = reg.duplicate(regNum, null);
|
||||||
|
SSAVar newSsaVar = mth.makeNewSVar(resultArg);
|
||||||
|
RegisterArg arg = reg.duplicate(regNum, var);
|
||||||
|
|
||||||
|
InsnNode moveInsn = new InsnNode(InsnType.MOVE, 1);
|
||||||
|
moveInsn.setResult(resultArg);
|
||||||
|
moveInsn.addArg(arg);
|
||||||
|
moveInsn.add(AFlag.SYNTHETIC);
|
||||||
|
blockNode.getInstructions().add(moveInsn);
|
||||||
|
|
||||||
|
phiInsn.replaceArg(reg, reg.duplicate(regNum, newSsaVar));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private BlockNode checkBlockForInsnInsert(BlockNode blockNode) {
|
||||||
|
if (blockNode.isSynthetic()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
|
||||||
|
if (lastInsn != null && BlockSplitter.isSeparate(lastInsn.getType())) {
|
||||||
|
// can't insert move in a block with 'separate' instruction => try previous block by simple path
|
||||||
|
List<BlockNode> preds = blockNode.getPredecessors();
|
||||||
|
if (preds.size() == 1) {
|
||||||
|
return checkBlockForInsnInsert(preds.get(0));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return blockNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryWiderObjects(MethodNode mth, SSAVar var) {
|
||||||
|
Set<ArgType> objTypes = new LinkedHashSet<>();
|
||||||
|
for (ITypeBound bound : var.getTypeInfo().getBounds()) {
|
||||||
|
ArgType boundType = bound.getType();
|
||||||
|
if (boundType.isTypeKnown() && boundType.isObject()) {
|
||||||
|
objTypes.add(boundType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (objTypes.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ClspGraph clsp = mth.root().getClsp();
|
||||||
|
for (ArgType objType : objTypes) {
|
||||||
|
for (String ancestor : clsp.getSuperTypes(objType.getObject())) {
|
||||||
|
ArgType ancestorType = ArgType.object(ancestor);
|
||||||
|
TypeUpdateResult result = typeUpdate.applyWithWiderAllow(mth, var, ancestorType);
|
||||||
|
if (result == TypeUpdateResult.CHANGED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||||
|
private boolean tryToFixIncompatiblePrimitives(MethodNode mth) {
|
||||||
|
boolean fixed = false;
|
||||||
|
List<SSAVar> ssaVars = mth.getSVars();
|
||||||
|
int ssaVarsCount = ssaVars.size();
|
||||||
|
// new vars will be added at a list end if fix is applied (can't use for-each loop here)
|
||||||
|
for (int i = 0; i < ssaVarsCount; i++) {
|
||||||
|
if (processIncompatiblePrimitives(mth, ssaVars.get(i))) {
|
||||||
|
fixed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!fixed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InitCodeVariables.rerun(mth);
|
||||||
|
typeInference.initTypeBounds(mth);
|
||||||
|
return typeInference.runTypePropagation(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processIncompatiblePrimitives(MethodNode mth, SSAVar var) {
|
||||||
|
TypeInfo typeInfo = var.getTypeInfo();
|
||||||
|
if (typeInfo.getType().isTypeKnown()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean assigned = false;
|
||||||
|
for (ITypeBound bound : typeInfo.getBounds()) {
|
||||||
|
ArgType boundType = bound.getType();
|
||||||
|
switch (bound.getBound()) {
|
||||||
|
case ASSIGN:
|
||||||
|
if (!boundType.contains(PrimitiveType.BOOLEAN)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
assigned = true;
|
||||||
|
break;
|
||||||
|
case USE:
|
||||||
|
if (!boundType.canBeAnyNumber()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!assigned) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean fixed = false;
|
||||||
|
for (RegisterArg arg : new ArrayList<>(var.getUseList())) {
|
||||||
|
if (fixBooleanUsage(mth, arg)) {
|
||||||
|
fixed = true;
|
||||||
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
|
LOG.info("Fixed boolean usage for arg {} from {}", arg, arg.getParentInsn());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean fixBooleanUsage(MethodNode mth, RegisterArg boundArg) {
|
||||||
|
ArgType boundType = boundArg.getInitType();
|
||||||
|
if (boundType == ArgType.BOOLEAN
|
||||||
|
|| (boundType.isTypeKnown() && !boundType.isPrimitive())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InsnNode insn = boundArg.getParentInsn();
|
||||||
|
if (insn == null || insn.getType() == InsnType.IF) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BlockNode blockNode = BlockUtils.getBlockByInsn(mth, insn);
|
||||||
|
if (blockNode == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<InsnNode> insnList = blockNode.getInstructions();
|
||||||
|
int insnIndex = InsnList.getIndex(insnList, insn);
|
||||||
|
if (insnIndex == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InsnType insnType = insn.getType();
|
||||||
|
if (insnType == InsnType.CAST) {
|
||||||
|
// replace cast
|
||||||
|
ArgType type = (ArgType) ((IndexInsnNode) insn).getIndex();
|
||||||
|
TernaryInsn convertInsn = prepareBooleanConvertInsn(insn.getResult(), boundArg, type);
|
||||||
|
BlockUtils.replaceInsn(mth, blockNode, insnIndex, convertInsn);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (insnType == InsnType.ARITH) {
|
||||||
|
ArithNode arithInsn = (ArithNode) insn;
|
||||||
|
if (arithInsn.getOp() == ArithOp.XOR && arithInsn.getArgsCount() == 2) {
|
||||||
|
// replace (boolean ^ 1) with (!boolean)
|
||||||
|
InsnArg secondArg = arithInsn.getArg(1);
|
||||||
|
if (secondArg.isLiteral() && ((LiteralArg) secondArg).getLiteral() == 1) {
|
||||||
|
InsnNode convertInsn = notBooleanToInt(arithInsn, boundArg);
|
||||||
|
BlockUtils.replaceInsn(mth, blockNode, insnIndex, convertInsn);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert before insn
|
||||||
|
RegisterArg resultArg = boundArg.duplicateWithNewSSAVar(mth);
|
||||||
|
TernaryInsn convertInsn = prepareBooleanConvertInsn(resultArg, boundArg, boundType);
|
||||||
|
insnList.add(insnIndex, convertInsn);
|
||||||
|
insn.replaceArg(boundArg, convertInsn.getResult().duplicate());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InsnNode notBooleanToInt(ArithNode insn, RegisterArg boundArg) {
|
||||||
|
InsnNode notInsn = new InsnNode(InsnType.NOT, 1);
|
||||||
|
notInsn.addArg(boundArg.duplicate());
|
||||||
|
notInsn.add(AFlag.SYNTHETIC);
|
||||||
|
|
||||||
|
ArgType resType = insn.getResult().getType();
|
||||||
|
if (resType.canBePrimitive(PrimitiveType.BOOLEAN)) {
|
||||||
|
notInsn.setResult(insn.getResult());
|
||||||
|
return notInsn;
|
||||||
|
}
|
||||||
|
InsnArg notArg = InsnArg.wrapArg(notInsn);
|
||||||
|
notArg.setType(ArgType.BOOLEAN);
|
||||||
|
TernaryInsn convertInsn = ModVisitor.makeBooleanConvertInsn(insn.getResult(), notArg, ArgType.INT);
|
||||||
|
convertInsn.add(AFlag.SYNTHETIC);
|
||||||
|
return convertInsn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TernaryInsn prepareBooleanConvertInsn(RegisterArg resultArg, RegisterArg boundArg, ArgType useType) {
|
||||||
|
RegisterArg useArg = boundArg.getSVar().getAssign().duplicate();
|
||||||
|
TernaryInsn convertInsn = ModVisitor.makeBooleanConvertInsn(resultArg, useArg, useType);
|
||||||
|
convertInsn.add(AFlag.SYNTHETIC);
|
||||||
|
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 typeInference.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "FixTypesVisitor";
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -8,7 +8,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
|
|||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow to ignore down casts (return arg type instead cast type)
|
* Allow ignoring down casts (return arg type instead cast type)
|
||||||
* Such casts will be removed later.
|
* Such casts will be removed later.
|
||||||
*/
|
*/
|
||||||
public final class TypeBoundCheckCastAssign implements ITypeBoundDynamic {
|
public final class TypeBoundCheckCastAssign implements ITypeBoundDynamic {
|
||||||
@@ -36,7 +36,7 @@ public final class TypeBoundCheckCastAssign implements ITypeBoundDynamic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ArgType getReturnType(ArgType argType) {
|
private ArgType getReturnType(ArgType argType) {
|
||||||
ArgType castType = (ArgType) insn.getIndex();
|
ArgType castType = insn.getIndexAsType();
|
||||||
TypeCompareEnum result = root.getTypeCompare().compareTypes(argType, castType);
|
TypeCompareEnum result = root.getTypeCompare().compareTypes(argType, castType);
|
||||||
return result.isNarrow() ? argType : castType;
|
return result.isNarrow() ? argType : castType;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import jadx.core.dex.nodes.RootNode;
|
|||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.CONFLICT;
|
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.CONFLICT;
|
||||||
|
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.CONFLICT_BY_GENERIC;
|
||||||
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.EQUAL;
|
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.EQUAL;
|
||||||
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.NARROW;
|
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.NARROW;
|
||||||
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.NARROW_BY_GENERIC;
|
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.NARROW_BY_GENERIC;
|
||||||
@@ -265,6 +266,9 @@ public class TypeCompare {
|
|||||||
if (objType.isGenericType()) {
|
if (objType.isGenericType()) {
|
||||||
return compareTypeVariables(genericType, objType);
|
return compareTypeVariables(genericType, objType);
|
||||||
}
|
}
|
||||||
|
if (objType.isWildcard()) {
|
||||||
|
return CONFLICT_BY_GENERIC;
|
||||||
|
}
|
||||||
boolean rootObject = objType.equals(ArgType.OBJECT);
|
boolean rootObject = objType.equals(ArgType.OBJECT);
|
||||||
List<ArgType> extendTypes = genericType.getExtendTypes();
|
List<ArgType> extendTypes = genericType.getExtendTypes();
|
||||||
if (extendTypes.isEmpty()) {
|
if (extendTypes.isEmpty()) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public enum TypeCompareEnum {
|
|||||||
WIDER,
|
WIDER,
|
||||||
WIDER_BY_GENERIC, // same basic type without generic
|
WIDER_BY_GENERIC, // same basic type without generic
|
||||||
CONFLICT,
|
CONFLICT,
|
||||||
|
CONFLICT_BY_GENERIC, // same basic type, conflict in generics
|
||||||
UNKNOWN;
|
UNKNOWN;
|
||||||
|
|
||||||
public TypeCompareEnum invert() {
|
public TypeCompareEnum invert() {
|
||||||
@@ -24,6 +25,7 @@ public enum TypeCompareEnum {
|
|||||||
return NARROW_BY_GENERIC;
|
return NARROW_BY_GENERIC;
|
||||||
|
|
||||||
case CONFLICT:
|
case CONFLICT:
|
||||||
|
case CONFLICT_BY_GENERIC:
|
||||||
case EQUAL:
|
case EQUAL:
|
||||||
case UNKNOWN:
|
case UNKNOWN:
|
||||||
default:
|
default:
|
||||||
@@ -51,7 +53,7 @@ public enum TypeCompareEnum {
|
|||||||
return isEqual() || isNarrow();
|
return isEqual() || isNarrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isGeneric() {
|
public boolean isConflict() {
|
||||||
return this == WIDER_BY_GENERIC || this == NARROW_BY_GENERIC;
|
return this == CONFLICT || this == CONFLICT_BY_GENERIC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-707
@@ -1,29 +1,19 @@
|
|||||||
package jadx.core.dex.visitors.typeinference;
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
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.AnonymousClassAttr;
|
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||||
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.ArithOp;
|
|
||||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||||
import jadx.core.dex.instructions.IndexInsnNode;
|
import jadx.core.dex.instructions.IndexInsnNode;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
@@ -33,12 +23,9 @@ 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.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.LiteralArg;
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
|
||||||
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.instructions.mods.ConstructorInsn;
|
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.IMethodDetails;
|
import jadx.core.dex.nodes.IMethodDetails;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
@@ -49,16 +36,8 @@ import jadx.core.dex.trycatch.ExcHandlerAttr;
|
|||||||
import jadx.core.dex.visitors.AbstractVisitor;
|
import jadx.core.dex.visitors.AbstractVisitor;
|
||||||
import jadx.core.dex.visitors.AttachMethodDetails;
|
import jadx.core.dex.visitors.AttachMethodDetails;
|
||||||
import jadx.core.dex.visitors.ConstInlineVisitor;
|
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||||
import jadx.core.dex.visitors.InitCodeVariables;
|
|
||||||
import jadx.core.dex.visitors.JadxVisitor;
|
import jadx.core.dex.visitors.JadxVisitor;
|
||||||
import jadx.core.dex.visitors.ModVisitor;
|
|
||||||
import jadx.core.dex.visitors.blocks.BlockSplitter;
|
|
||||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||||
import jadx.core.utils.BlockUtils;
|
|
||||||
import jadx.core.utils.InsnList;
|
|
||||||
import jadx.core.utils.InsnUtils;
|
|
||||||
import jadx.core.utils.ListUtils;
|
|
||||||
import jadx.core.utils.Utils;
|
|
||||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||||
|
|
||||||
@JadxVisitor(
|
@JadxVisitor(
|
||||||
@@ -75,24 +54,11 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
private RootNode root;
|
private RootNode root;
|
||||||
private TypeUpdate typeUpdate;
|
private TypeUpdate typeUpdate;
|
||||||
private List<Function<MethodNode, Boolean>> resolvers;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(RootNode root) {
|
public void init(RootNode root) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.typeUpdate = root.getTypeUpdate();
|
this.typeUpdate = root.getTypeUpdate();
|
||||||
this.resolvers = Arrays.asList(
|
|
||||||
this::initTypeBounds,
|
|
||||||
this::runTypePropagation,
|
|
||||||
this::tryRestoreTypeVarCasts,
|
|
||||||
this::tryInsertCasts,
|
|
||||||
this::tryDeduceTypes,
|
|
||||||
this::trySplitConstInsns,
|
|
||||||
this::tryToFixIncompatiblePrimitives,
|
|
||||||
this::tryToForceImmutableTypes,
|
|
||||||
this::tryInsertAdditionalMove,
|
|
||||||
this::runMultiVariableSearch,
|
|
||||||
this::tryRemoveGenerics);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -103,73 +69,39 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
LOG.info("Start type inference in method: {}", mth);
|
LOG.info("Start type inference in method: {}", mth);
|
||||||
}
|
}
|
||||||
assignImmutableTypes(mth);
|
|
||||||
try {
|
try {
|
||||||
for (Function<MethodNode, Boolean> resolver : resolvers) {
|
assignImmutableTypes(mth);
|
||||||
if (resolver.apply(mth) && checkTypes(mth)) {
|
initTypeBounds(mth);
|
||||||
return;
|
runTypePropagation(mth);
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
mth.addError("Type inference failed with exception", e);
|
mth.addError("Type inference failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if all types resolved
|
|
||||||
*/
|
|
||||||
private static boolean checkTypes(MethodNode mth) {
|
|
||||||
for (SSAVar var : mth.getSVars()) {
|
|
||||||
ArgType type = var.getTypeInfo().getType();
|
|
||||||
if (!type.isTypeKnown()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect initial type bounds from assign and usages
|
* Collect initial type bounds from assign and usages
|
||||||
*/
|
*/
|
||||||
private boolean initTypeBounds(MethodNode mth) {
|
void initTypeBounds(MethodNode mth) {
|
||||||
List<SSAVar> ssaVars = mth.getSVars();
|
List<SSAVar> ssaVars = mth.getSVars();
|
||||||
ssaVars.forEach(this::attachBounds);
|
ssaVars.forEach(this::attachBounds);
|
||||||
ssaVars.forEach(this::mergePhiBounds);
|
ssaVars.forEach(this::mergePhiBounds);
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
ssaVars.forEach(ssaVar -> LOG.debug("Type bounds for {}: {}", ssaVar.toShortString(), ssaVar.getTypeInfo().getBounds()));
|
ssaVars.stream().sorted()
|
||||||
|
.forEach(ssaVar -> LOG.debug("Type bounds for {}: {}", ssaVar.toShortString(), ssaVar.getTypeInfo().getBounds()));
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guess type from usage and try to set it to current variable
|
* Guess type from usage and try to set it to current variable
|
||||||
* and all connected instructions with {@link TypeUpdate#apply(MethodNode, SSAVar, ArgType)}
|
* and all connected instructions with {@link TypeUpdate#apply(MethodNode, SSAVar, ArgType)}
|
||||||
*/
|
*/
|
||||||
private boolean runTypePropagation(MethodNode mth) {
|
boolean runTypePropagation(MethodNode mth) {
|
||||||
List<SSAVar> ssaVars = mth.getSVars();
|
List<SSAVar> ssaVars = mth.getSVars();
|
||||||
ssaVars.forEach(var -> setImmutableType(mth, var));
|
ssaVars.forEach(var -> setImmutableType(mth, var));
|
||||||
ssaVars.forEach(var -> setBestType(mth, var));
|
ssaVars.forEach(var -> setBestType(mth, var));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean runMultiVariableSearch(MethodNode mth) {
|
|
||||||
try {
|
|
||||||
TypeSearch typeSearch = new TypeSearch(mth);
|
|
||||||
if (!typeSearch.run()) {
|
|
||||||
mth.addWarnComment("Multi-variable type inference failed");
|
|
||||||
}
|
|
||||||
for (SSAVar var : mth.getSVars()) {
|
|
||||||
if (!var.getTypeInfo().getType().isTypeKnown()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
mth.addWarnComment("Multi-variable type inference failed. Error: " + Utils.getStackTrace(e));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setImmutableType(MethodNode mth, SSAVar ssaVar) {
|
private void setImmutableType(MethodNode mth, SSAVar ssaVar) {
|
||||||
try {
|
try {
|
||||||
ArgType immutableType = ssaVar.getImmutableType();
|
ArgType immutableType = ssaVar.getImmutableType();
|
||||||
@@ -186,18 +118,17 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setBestType(MethodNode mth, SSAVar ssaVar) {
|
private void setBestType(MethodNode mth, SSAVar ssaVar) {
|
||||||
try {
|
try {
|
||||||
return calculateFromBounds(mth, ssaVar);
|
calculateFromBounds(mth, ssaVar);
|
||||||
} catch (JadxOverflowException e) {
|
} catch (JadxOverflowException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
mth.addWarnComment("Failed to calculate best type for var: " + ssaVar, e);
|
mth.addWarnComment("Failed to calculate best type for var: " + ssaVar, e);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean calculateFromBounds(MethodNode mth, SSAVar ssaVar) {
|
private void calculateFromBounds(MethodNode mth, SSAVar ssaVar) {
|
||||||
TypeInfo typeInfo = ssaVar.getTypeInfo();
|
TypeInfo typeInfo = ssaVar.getTypeInfo();
|
||||||
Set<ITypeBound> bounds = typeInfo.getBounds();
|
Set<ITypeBound> bounds = typeInfo.getBounds();
|
||||||
Optional<ArgType> bestTypeOpt = selectBestTypeFromBounds(bounds);
|
Optional<ArgType> bestTypeOpt = selectBestTypeFromBounds(bounds);
|
||||||
@@ -208,21 +139,17 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
LOG.warn(" {}", bound);
|
LOG.warn(" {}", bound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
ArgType candidateType = bestTypeOpt.get();
|
ArgType candidateType = bestTypeOpt.get();
|
||||||
TypeUpdateResult result = typeUpdate.apply(mth, ssaVar, candidateType);
|
TypeUpdateResult result = typeUpdate.apply(mth, ssaVar, candidateType);
|
||||||
if (result == TypeUpdateResult.REJECT) {
|
if (Consts.DEBUG_TYPE_INFERENCE && result == TypeUpdateResult.REJECT) {
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
if (ssaVar.getTypeInfo().getType().equals(candidateType)) {
|
||||||
if (ssaVar.getTypeInfo().getType().equals(candidateType)) {
|
LOG.info("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
|
||||||
LOG.info("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
|
} else if (candidateType.isTypeKnown()) {
|
||||||
} else if (candidateType.isTypeKnown()) {
|
LOG.debug("Type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
|
||||||
LOG.debug("Type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return result == TypeUpdateResult.CHANGED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<ArgType> selectBestTypeFromBounds(Set<ITypeBound> bounds) {
|
private Optional<ArgType> selectBestTypeFromBounds(Set<ITypeBound> bounds) {
|
||||||
@@ -310,7 +237,11 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case CHECK_CAST:
|
case CHECK_CAST:
|
||||||
addBound(typeInfo, new TypeBoundCheckCastAssign(root, (IndexInsnNode) insn));
|
if (insn.contains(AFlag.SOFT_CAST)) {
|
||||||
|
// ignore bound, will run checks on update
|
||||||
|
} else {
|
||||||
|
addBound(typeInfo, new TypeBoundCheckCastAssign(root, (IndexInsnNode) insn));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -396,7 +327,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
return new TypeBoundInvokeUse(root, invoke, regArg, argType);
|
return new TypeBoundInvokeUse(root, invoke, regArg, argType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for override methods use origin declared class as type
|
// for override methods use origin declared class as a type
|
||||||
if (methodDetails instanceof MethodNode) {
|
if (methodDetails instanceof MethodNode) {
|
||||||
MethodNode callMth = (MethodNode) methodDetails;
|
MethodNode callMth = (MethodNode) methodDetails;
|
||||||
ClassInfo declCls = methodUtils.getMethodOriginDeclClass(callMth);
|
ClassInfo declCls = methodUtils.getMethodOriginDeclClass(callMth);
|
||||||
@@ -405,621 +336,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean tryPossibleTypes(MethodNode mth, SSAVar var, ArgType type) {
|
private void assignImmutableTypes(MethodNode mth) {
|
||||||
List<ArgType> types = makePossibleTypesList(type, var);
|
|
||||||
if (types.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (ArgType candidateType : types) {
|
|
||||||
TypeUpdateResult result = typeUpdate.apply(mth, var, candidateType);
|
|
||||||
if (result == TypeUpdateResult.CHANGED) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ArgType> makePossibleTypesList(ArgType type, @Nullable SSAVar var) {
|
|
||||||
if (type.isArray()) {
|
|
||||||
List<ArgType> list = new ArrayList<>();
|
|
||||||
for (ArgType arrElemType : makePossibleTypesList(type.getArrayElement(), null)) {
|
|
||||||
list.add(ArgType.array(arrElemType));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
if (var != null) {
|
|
||||||
for (ITypeBound b : var.getTypeInfo().getBounds()) {
|
|
||||||
ArgType boundType = b.getType();
|
|
||||||
if (boundType.isObject() || boundType.isArray()) {
|
|
||||||
// don't add primitive types
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<ArgType> list = new ArrayList<>();
|
|
||||||
for (PrimitiveType possibleType : type.getPossibleTypes()) {
|
|
||||||
if (possibleType == PrimitiveType.VOID) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
list.add(ArgType.convertFromPrimitiveType(possibleType));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean tryDeduceTypes(MethodNode mth) {
|
|
||||||
boolean fixed = false;
|
|
||||||
for (SSAVar ssaVar : mth.getSVars()) {
|
|
||||||
if (deduceType(mth, ssaVar)) {
|
|
||||||
fixed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("RedundantIfStatement")
|
|
||||||
private boolean deduceType(MethodNode mth, SSAVar var) {
|
|
||||||
if (var.isTypeImmutable()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ArgType type = var.getTypeInfo().getType();
|
|
||||||
if (type.isTypeKnown()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// try best type from bounds again
|
|
||||||
if (setBestType(mth, var)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// try all possible types (useful for primitives)
|
|
||||||
if (tryPossibleTypes(mth, var, type)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// for objects try super types
|
|
||||||
if (tryWiderObjects(mth, var)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean tryRemoveGenerics(MethodNode mth) {
|
|
||||||
boolean resolved = true;
|
|
||||||
for (SSAVar var : mth.getSVars()) {
|
|
||||||
ArgType type = var.getTypeInfo().getType();
|
|
||||||
if (!type.isTypeKnown()
|
|
||||||
&& !var.isTypeImmutable()
|
|
||||||
&& !tryRawType(mth, var)) {
|
|
||||||
resolved = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resolved;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean tryRawType(MethodNode mth, SSAVar var) {
|
|
||||||
Set<ArgType> objTypes = new LinkedHashSet<>();
|
|
||||||
for (ITypeBound bound : var.getTypeInfo().getBounds()) {
|
|
||||||
ArgType boundType = bound.getType();
|
|
||||||
if (boundType.isTypeKnown() && boundType.isObject()) {
|
|
||||||
objTypes.add(boundType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (objTypes.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (ArgType objType : objTypes) {
|
|
||||||
if (checkRawType(mth, var, objType)) {
|
|
||||||
mth.addDebugComment("Type inference failed for " + var.toShortString() + "."
|
|
||||||
+ " Raw type applied. Possible types: " + Utils.listToString(objTypes));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkRawType(MethodNode mth, SSAVar var, ArgType objType) {
|
|
||||||
if (objType.isObject() && objType.containsGeneric()) {
|
|
||||||
ArgType rawType = objType.isGenericType() ? ArgType.OBJECT : ArgType.object(objType.getObject());
|
|
||||||
TypeUpdateResult result = typeUpdate.applyWithWiderAllow(mth, var, rawType);
|
|
||||||
return result == TypeUpdateResult.CHANGED;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fix check casts to type var extend type:
|
|
||||||
* <br>
|
|
||||||
* {@code <T extends Comparable> T var = (Comparable) obj; => T var = (T) obj; }
|
|
||||||
*/
|
|
||||||
private boolean tryRestoreTypeVarCasts(MethodNode mth) {
|
|
||||||
int changed = 0;
|
|
||||||
List<SSAVar> mthSVars = mth.getSVars();
|
|
||||||
for (SSAVar var : mthSVars) {
|
|
||||||
changed += restoreTypeVarCasts(var);
|
|
||||||
}
|
|
||||||
if (changed == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
|
||||||
mth.addDebugComment("Restore " + changed + " type vars casts");
|
|
||||||
}
|
|
||||||
initTypeBounds(mth);
|
|
||||||
return runTypePropagation(mth);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int restoreTypeVarCasts(SSAVar var) {
|
|
||||||
TypeInfo typeInfo = var.getTypeInfo();
|
|
||||||
Set<ITypeBound> bounds = typeInfo.getBounds();
|
|
||||||
if (!ListUtils.anyMatch(bounds, t -> t.getType().isGenericType())) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
List<ITypeBound> casts = ListUtils.filter(bounds, TypeBoundCheckCastAssign.class::isInstance);
|
|
||||||
if (casts.isEmpty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
ArgType bestType = selectBestTypeFromBounds(bounds).orElse(ArgType.UNKNOWN);
|
|
||||||
if (!bestType.isGenericType()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
List<ArgType> extendTypes = bestType.getExtendTypes();
|
|
||||||
if (extendTypes.size() != 1) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int fixed = 0;
|
|
||||||
ArgType extendType = extendTypes.get(0);
|
|
||||||
for (ITypeBound bound : casts) {
|
|
||||||
TypeBoundCheckCastAssign cast = (TypeBoundCheckCastAssign) bound;
|
|
||||||
ArgType castType = cast.getType();
|
|
||||||
TypeCompareEnum result = typeUpdate.getTypeCompare().compareTypes(extendType, castType);
|
|
||||||
if (result.isEqual() || result == TypeCompareEnum.NARROW_BY_GENERIC) {
|
|
||||||
cast.getInsn().updateIndex(bestType);
|
|
||||||
fixed++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({ "ForLoopReplaceableByWhile", "ForLoopReplaceableByForEach" })
|
|
||||||
private boolean tryInsertCasts(MethodNode mth) {
|
|
||||||
int added = 0;
|
|
||||||
List<SSAVar> mthSVars = mth.getSVars();
|
|
||||||
int varsCount = mthSVars.size();
|
|
||||||
for (int i = 0; i < varsCount; i++) {
|
|
||||||
SSAVar var = mthSVars.get(i);
|
|
||||||
ArgType type = var.getTypeInfo().getType();
|
|
||||||
if (!type.isTypeKnown() && !var.isTypeImmutable()) {
|
|
||||||
added += tryInsertVarCast(mth, var);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (added != 0) {
|
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
|
||||||
mth.addDebugComment("Additional " + added + " cast instructions added to help type inference");
|
|
||||||
}
|
|
||||||
InitCodeVariables.rerun(mth);
|
|
||||||
initTypeBounds(mth);
|
|
||||||
return runTypePropagation(mth);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int tryInsertVarCast(MethodNode mth, SSAVar var) {
|
|
||||||
for (ITypeBound bound : var.getTypeInfo().getBounds()) {
|
|
||||||
ArgType boundType = bound.getType();
|
|
||||||
if (boundType.isTypeKnown()
|
|
||||||
&& !boundType.equals(var.getTypeInfo().getType())
|
|
||||||
&& boundType.containsTypeVariable()
|
|
||||||
&& !root.getTypeUtils().containsUnknownTypeVar(mth, boundType)) {
|
|
||||||
if (insertAssignCast(mth, var, boundType)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return insertUseCasts(mth, var);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int insertUseCasts(MethodNode mth, SSAVar var) {
|
|
||||||
List<RegisterArg> useList = var.getUseList();
|
|
||||||
if (useList.isEmpty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int useCasts = 0;
|
|
||||||
for (RegisterArg useReg : new ArrayList<>(useList)) {
|
|
||||||
if (insertSoftUseCast(mth, useReg)) {
|
|
||||||
useCasts++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return useCasts;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean insertAssignCast(MethodNode mth, SSAVar var, ArgType castType) {
|
|
||||||
RegisterArg assignArg = var.getAssign();
|
|
||||||
InsnNode assignInsn = assignArg.getParentInsn();
|
|
||||||
if (assignInsn == null || assignInsn.getType() == InsnType.PHI) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn);
|
|
||||||
if (assignBlock == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
assignInsn.setResult(assignArg.duplicateWithNewSSAVar(mth));
|
|
||||||
IndexInsnNode castInsn = makeSoftCastInsn(assignArg.duplicate(), assignInsn.getResult().duplicate(), castType);
|
|
||||||
return BlockUtils.insertAfterInsn(assignBlock, assignInsn, castInsn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean insertSoftUseCast(MethodNode mth, RegisterArg useArg) {
|
|
||||||
InsnNode useInsn = useArg.getParentInsn();
|
|
||||||
if (useInsn == null || useInsn.getType() == InsnType.PHI) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (useInsn.getType() == InsnType.IF && useInsn.getArg(1).isZeroLiteral()) {
|
|
||||||
// cast not needed if compare with null
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
BlockNode useBlock = BlockUtils.getBlockByInsn(mth, useInsn);
|
|
||||||
if (useBlock == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
IndexInsnNode castInsn = makeSoftCastInsn(
|
|
||||||
useArg.duplicateWithNewSSAVar(mth),
|
|
||||||
useArg.duplicate(),
|
|
||||||
useArg.getInitType());
|
|
||||||
useInsn.replaceArg(useArg, castInsn.getResult().duplicate());
|
|
||||||
return BlockUtils.insertBeforeInsn(useBlock, useInsn, castInsn);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private IndexInsnNode makeSoftCastInsn(RegisterArg result, RegisterArg arg, ArgType castType) {
|
|
||||||
IndexInsnNode castInsn = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1);
|
|
||||||
castInsn.setResult(result);
|
|
||||||
castInsn.addArg(arg);
|
|
||||||
castInsn.add(AFlag.SOFT_CAST);
|
|
||||||
castInsn.add(AFlag.SYNTHETIC);
|
|
||||||
return castInsn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean trySplitConstInsns(MethodNode mth) {
|
|
||||||
boolean constSplit = false;
|
|
||||||
for (SSAVar var : new ArrayList<>(mth.getSVars())) {
|
|
||||||
if (checkAndSplitConstInsn(mth, var)) {
|
|
||||||
constSplit = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!constSplit) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
InitCodeVariables.rerun(mth);
|
|
||||||
initTypeBounds(mth);
|
|
||||||
return runTypePropagation(mth);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkAndSplitConstInsn(MethodNode mth, SSAVar var) {
|
|
||||||
ArgType type = var.getTypeInfo().getType();
|
|
||||||
if (type.isTypeKnown() || var.isTypeImmutable()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (var.getUsedInPhi().size() < 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
InsnNode assignInsn = var.getAssign().getAssignInsn();
|
|
||||||
InsnNode constInsn = InsnUtils.checkInsnType(assignInsn, InsnType.CONST);
|
|
||||||
if (constInsn == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
BlockNode blockNode = BlockUtils.getBlockByInsn(mth, constInsn);
|
|
||||||
if (blockNode == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// for every PHI make separate CONST insn
|
|
||||||
boolean first = true;
|
|
||||||
for (PhiInsn phiInsn : var.getUsedInPhi()) {
|
|
||||||
if (first) {
|
|
||||||
first = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
InsnNode copyInsn = constInsn.copyWithNewSsaVar(mth);
|
|
||||||
copyInsn.add(AFlag.SYNTHETIC);
|
|
||||||
BlockUtils.insertAfterInsn(blockNode, constInsn, copyInsn);
|
|
||||||
|
|
||||||
RegisterArg phiArg = phiInsn.getArgBySsaVar(var);
|
|
||||||
phiInsn.replaceArg(phiArg, copyInsn.getResult().duplicate());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean tryInsertAdditionalMove(MethodNode mth) {
|
|
||||||
int insnsAdded = 0;
|
|
||||||
for (BlockNode block : mth.getBasicBlocks()) {
|
|
||||||
PhiListAttr phiListAttr = block.get(AType.PHI_LIST);
|
|
||||||
if (phiListAttr != null) {
|
|
||||||
for (PhiInsn phiInsn : phiListAttr.getList()) {
|
|
||||||
insnsAdded += tryInsertAdditionalInsn(mth, phiInsn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (insnsAdded == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
|
||||||
mth.addDebugComment("Additional " + insnsAdded + " move instructions added to help type inference");
|
|
||||||
}
|
|
||||||
InitCodeVariables.rerun(mth);
|
|
||||||
initTypeBounds(mth);
|
|
||||||
if (runTypePropagation(mth) && checkTypes(mth)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return tryDeduceTypes(mth);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add MOVE instruction before PHI in bound blocks to make 'soft' type link.
|
|
||||||
* This allows to use different types in blocks merged by PHI.
|
|
||||||
*/
|
|
||||||
private int tryInsertAdditionalInsn(MethodNode mth, PhiInsn phiInsn) {
|
|
||||||
ArgType phiType = getCommonTypeForPhiArgs(phiInsn);
|
|
||||||
if (phiType != null && phiType.isTypeKnown()) {
|
|
||||||
// all args have same known type => nothing to do here
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// check if instructions can be inserted
|
|
||||||
if (insertMovesForPhi(mth, phiInsn, false) == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// check passed => apply
|
|
||||||
return insertMovesForPhi(mth, phiInsn, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private ArgType getCommonTypeForPhiArgs(PhiInsn phiInsn) {
|
|
||||||
ArgType phiArgType = null;
|
|
||||||
for (InsnArg arg : phiInsn.getArguments()) {
|
|
||||||
ArgType type = arg.getType();
|
|
||||||
if (phiArgType == null) {
|
|
||||||
phiArgType = type;
|
|
||||||
} else if (!phiArgType.equals(type)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return phiArgType;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int insertMovesForPhi(MethodNode mth, PhiInsn phiInsn, boolean apply) {
|
|
||||||
int argsCount = phiInsn.getArgsCount();
|
|
||||||
int count = 0;
|
|
||||||
for (int argIndex = 0; argIndex < argsCount; argIndex++) {
|
|
||||||
RegisterArg reg = phiInsn.getArg(argIndex);
|
|
||||||
BlockNode startBlock = phiInsn.getBlockByArgIndex(argIndex);
|
|
||||||
BlockNode blockNode = checkBlockForInsnInsert(startBlock);
|
|
||||||
if (blockNode == null) {
|
|
||||||
mth.addDebugComment("Failed to insert an additional move for type inference into block " + startBlock);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
boolean add = true;
|
|
||||||
SSAVar var = reg.getSVar();
|
|
||||||
InsnNode assignInsn = var.getAssign().getAssignInsn();
|
|
||||||
if (assignInsn != null) {
|
|
||||||
InsnType assignType = assignInsn.getType();
|
|
||||||
if (assignType == InsnType.CONST
|
|
||||||
|| (assignType == InsnType.MOVE && var.getUseCount() == 1)) {
|
|
||||||
add = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (add) {
|
|
||||||
count++;
|
|
||||||
if (apply) {
|
|
||||||
insertMove(mth, blockNode, phiInsn, reg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insertMove(MethodNode mth, BlockNode blockNode, PhiInsn phiInsn, RegisterArg reg) {
|
|
||||||
SSAVar var = reg.getSVar();
|
|
||||||
int regNum = reg.getRegNum();
|
|
||||||
RegisterArg resultArg = reg.duplicate(regNum, null);
|
|
||||||
SSAVar newSsaVar = mth.makeNewSVar(resultArg);
|
|
||||||
RegisterArg arg = reg.duplicate(regNum, var);
|
|
||||||
|
|
||||||
InsnNode moveInsn = new InsnNode(InsnType.MOVE, 1);
|
|
||||||
moveInsn.setResult(resultArg);
|
|
||||||
moveInsn.addArg(arg);
|
|
||||||
moveInsn.add(AFlag.SYNTHETIC);
|
|
||||||
blockNode.getInstructions().add(moveInsn);
|
|
||||||
|
|
||||||
phiInsn.replaceArg(reg, reg.duplicate(regNum, newSsaVar));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private BlockNode checkBlockForInsnInsert(BlockNode blockNode) {
|
|
||||||
if (blockNode.isSynthetic()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
|
|
||||||
if (lastInsn != null && BlockSplitter.isSeparate(lastInsn.getType())) {
|
|
||||||
// can't insert move in a block with 'separate' instruction => try previous block by simple path
|
|
||||||
List<BlockNode> preds = blockNode.getPredecessors();
|
|
||||||
if (preds.size() == 1) {
|
|
||||||
return checkBlockForInsnInsert(preds.get(0));
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return blockNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean tryWiderObjects(MethodNode mth, SSAVar var) {
|
|
||||||
Set<ArgType> objTypes = new LinkedHashSet<>();
|
|
||||||
for (ITypeBound bound : var.getTypeInfo().getBounds()) {
|
|
||||||
ArgType boundType = bound.getType();
|
|
||||||
if (boundType.isTypeKnown() && boundType.isObject()) {
|
|
||||||
objTypes.add(boundType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (objTypes.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ClspGraph clsp = mth.root().getClsp();
|
|
||||||
for (ArgType objType : objTypes) {
|
|
||||||
for (String ancestor : clsp.getSuperTypes(objType.getObject())) {
|
|
||||||
ArgType ancestorType = ArgType.object(ancestor);
|
|
||||||
TypeUpdateResult result = typeUpdate.applyWithWiderAllow(mth, var, ancestorType);
|
|
||||||
if (result == TypeUpdateResult.CHANGED) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
|
||||||
private boolean tryToFixIncompatiblePrimitives(MethodNode mth) {
|
|
||||||
boolean fixed = false;
|
|
||||||
List<SSAVar> ssaVars = mth.getSVars();
|
|
||||||
int ssaVarsCount = ssaVars.size();
|
|
||||||
// new vars will be added at list end if fix is applied (can't use for-each loop)
|
|
||||||
for (int i = 0; i < ssaVarsCount; i++) {
|
|
||||||
if (processIncompatiblePrimitives(mth, ssaVars.get(i))) {
|
|
||||||
fixed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!fixed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
InitCodeVariables.rerun(mth);
|
|
||||||
initTypeBounds(mth);
|
|
||||||
return runTypePropagation(mth);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean processIncompatiblePrimitives(MethodNode mth, SSAVar var) {
|
|
||||||
TypeInfo typeInfo = var.getTypeInfo();
|
|
||||||
if (typeInfo.getType().isTypeKnown()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
boolean assigned = false;
|
|
||||||
for (ITypeBound bound : typeInfo.getBounds()) {
|
|
||||||
ArgType boundType = bound.getType();
|
|
||||||
switch (bound.getBound()) {
|
|
||||||
case ASSIGN:
|
|
||||||
if (!boundType.contains(PrimitiveType.BOOLEAN)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
assigned = true;
|
|
||||||
break;
|
|
||||||
case USE:
|
|
||||||
if (!boundType.canBeAnyNumber()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!assigned) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean fixed = false;
|
|
||||||
for (RegisterArg arg : new ArrayList<>(var.getUseList())) {
|
|
||||||
if (fixBooleanUsage(mth, arg)) {
|
|
||||||
fixed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean fixBooleanUsage(MethodNode mth, RegisterArg boundArg) {
|
|
||||||
ArgType boundType = boundArg.getInitType();
|
|
||||||
if (boundType == ArgType.BOOLEAN
|
|
||||||
|| (boundType.isTypeKnown() && !boundType.isPrimitive())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
InsnNode insn = boundArg.getParentInsn();
|
|
||||||
if (insn == null || insn.getType() == InsnType.IF) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
BlockNode blockNode = BlockUtils.getBlockByInsn(mth, insn);
|
|
||||||
if (blockNode == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
List<InsnNode> insnList = blockNode.getInstructions();
|
|
||||||
int insnIndex = InsnList.getIndex(insnList, insn);
|
|
||||||
if (insnIndex == -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
InsnType insnType = insn.getType();
|
|
||||||
if (insnType == InsnType.CAST) {
|
|
||||||
// replace cast
|
|
||||||
ArgType type = (ArgType) ((IndexInsnNode) insn).getIndex();
|
|
||||||
TernaryInsn convertInsn = prepareBooleanConvertInsn(insn.getResult(), boundArg, type);
|
|
||||||
BlockUtils.replaceInsn(mth, blockNode, insnIndex, convertInsn);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (insnType == InsnType.ARITH) {
|
|
||||||
ArithNode arithInsn = (ArithNode) insn;
|
|
||||||
if (arithInsn.getOp() == ArithOp.XOR && arithInsn.getArgsCount() == 2) {
|
|
||||||
// replace (boolean ^ 1) with (!boolean)
|
|
||||||
InsnArg secondArg = arithInsn.getArg(1);
|
|
||||||
if (secondArg.isLiteral() && ((LiteralArg) secondArg).getLiteral() == 1) {
|
|
||||||
InsnNode convertInsn = notBooleanToInt(arithInsn, boundArg);
|
|
||||||
BlockUtils.replaceInsn(mth, blockNode, insnIndex, convertInsn);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert before insn
|
|
||||||
RegisterArg resultArg = boundArg.duplicateWithNewSSAVar(mth);
|
|
||||||
TernaryInsn convertInsn = prepareBooleanConvertInsn(resultArg, boundArg, boundType);
|
|
||||||
insnList.add(insnIndex, convertInsn);
|
|
||||||
insn.replaceArg(boundArg, convertInsn.getResult().duplicate());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private InsnNode notBooleanToInt(ArithNode insn, RegisterArg boundArg) {
|
|
||||||
InsnNode notInsn = new InsnNode(InsnType.NOT, 1);
|
|
||||||
notInsn.addArg(boundArg.duplicate());
|
|
||||||
notInsn.add(AFlag.SYNTHETIC);
|
|
||||||
|
|
||||||
InsnArg notArg = InsnArg.wrapArg(notInsn);
|
|
||||||
notArg.setType(ArgType.BOOLEAN);
|
|
||||||
TernaryInsn convertInsn = ModVisitor.makeBooleanConvertInsn(insn.getResult(), notArg, ArgType.INT);
|
|
||||||
convertInsn.add(AFlag.SYNTHETIC);
|
|
||||||
return convertInsn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TernaryInsn prepareBooleanConvertInsn(RegisterArg resultArg, RegisterArg boundArg, ArgType useType) {
|
|
||||||
RegisterArg useArg = boundArg.getSVar().getAssign().duplicate();
|
|
||||||
TernaryInsn convertInsn = ModVisitor.makeBooleanConvertInsn(resultArg, useArg, useType);
|
|
||||||
convertInsn.add(AFlag.SYNTHETIC);
|
|
||||||
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) {
|
|
||||||
for (SSAVar ssaVar : mth.getSVars()) {
|
for (SSAVar ssaVar : mth.getSVars()) {
|
||||||
ArgType immutableType = getSsaImmutableType(ssaVar);
|
ArgType immutableType = getSsaImmutableType(ssaVar);
|
||||||
if (immutableType != null) {
|
if (immutableType != null) {
|
||||||
@@ -1040,4 +357,9 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TypeInferenceVisitor";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,10 +67,9 @@ public class TypeSearch {
|
|||||||
if (vars.isEmpty()) {
|
if (vars.isEmpty()) {
|
||||||
searchSuccess = true;
|
searchSuccess = true;
|
||||||
} else {
|
} else {
|
||||||
search(vars);
|
searchSuccess = search(vars) && fullCheck(vars);
|
||||||
searchSuccess = fullCheck(vars);
|
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE && !searchSuccess) {
|
if (Consts.DEBUG_TYPE_INFERENCE && !searchSuccess) {
|
||||||
LOG.debug("Multi-variable search failed in {}", mth);
|
LOG.debug("Multi-variable search failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (searchSuccess) {
|
if (searchSuccess) {
|
||||||
@@ -110,7 +109,7 @@ public class TypeSearch {
|
|||||||
private boolean search(List<TypeSearchVarInfo> vars) {
|
private boolean search(List<TypeSearchVarInfo> vars) {
|
||||||
int len = vars.size();
|
int len = vars.size();
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
LOG.debug("Run search for {} vars: ", len);
|
LOG.debug("Run multi-variable search for {} vars: ", len);
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
long count = 1;
|
long count = 1;
|
||||||
for (TypeSearchVarInfo var : vars) {
|
for (TypeSearchVarInfo var : vars) {
|
||||||
@@ -120,7 +119,7 @@ public class TypeSearch {
|
|||||||
count *= size;
|
count *= size;
|
||||||
}
|
}
|
||||||
sb.append(" = ").append(count);
|
sb.append(" = ").append(count);
|
||||||
LOG.debug(" > max iterations count = {}", sb);
|
LOG.debug(" max iterations count = {}", sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare vars
|
// prepare vars
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ import java.util.Set;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
|
import jadx.core.clsp.ClspClass;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.instructions.ArithNode;
|
import jadx.core.dex.instructions.ArithNode;
|
||||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||||
import jadx.core.dex.instructions.IndexInsnNode;
|
import jadx.core.dex.instructions.IndexInsnNode;
|
||||||
@@ -83,14 +84,13 @@ public final class TypeUpdate {
|
|||||||
if (result == REJECT) {
|
if (result == REJECT) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
List<TypeUpdateEntry> updates = updateInfo.getUpdates();
|
if (updateInfo.isEmpty()) {
|
||||||
if (updates.isEmpty()) {
|
|
||||||
return SAME;
|
return SAME;
|
||||||
}
|
}
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
LOG.debug("Applying type {} to {}", ssaVar.toShortString(), candidateType);
|
LOG.debug("Applying type {} to {}:", candidateType, ssaVar.toShortString());
|
||||||
updates.forEach(updateEntry -> LOG.debug(" {} -> {} in {}",
|
updateInfo.getSortedUpdates().forEach(upd -> LOG.debug(" {} -> {} in {}",
|
||||||
updateEntry.getType(), updateEntry.getArg(), updateEntry.getArg().getParentInsn()));
|
upd.getType(), upd.getArg().toShortString(), upd.getArg().getParentInsn()));
|
||||||
}
|
}
|
||||||
updateInfo.applyUpdates();
|
updateInfo.applyUpdates();
|
||||||
return CHANGED;
|
return CHANGED;
|
||||||
@@ -100,6 +100,21 @@ public final class TypeUpdate {
|
|||||||
if (candidateType == null) {
|
if (candidateType == null) {
|
||||||
throw new JadxRuntimeException("Null type update for arg: " + arg);
|
throw new JadxRuntimeException("Null type update for arg: " + arg);
|
||||||
}
|
}
|
||||||
|
if (updateInfo.isProcessed(arg)) {
|
||||||
|
return CHANGED;
|
||||||
|
}
|
||||||
|
TypeUpdateResult res = verifyType(updateInfo, arg, candidateType);
|
||||||
|
if (res != null) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if (arg instanceof RegisterArg) {
|
||||||
|
RegisterArg reg = (RegisterArg) arg;
|
||||||
|
return updateTypeForSsaVar(updateInfo, reg.getSVar(), candidateType);
|
||||||
|
}
|
||||||
|
return requestUpdate(updateInfo, arg, candidateType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable TypeUpdateResult verifyType(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
||||||
ArgType currentType = arg.getType();
|
ArgType currentType = arg.getType();
|
||||||
if (Objects.equals(currentType, candidateType)) {
|
if (Objects.equals(currentType, candidateType)) {
|
||||||
if (!updateInfo.getFlags().isIgnoreSame()) {
|
if (!updateInfo.getFlags().isIgnoreSame()) {
|
||||||
@@ -114,6 +129,12 @@ public final class TypeUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType);
|
TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType);
|
||||||
|
if (compareResult.isConflict()) {
|
||||||
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
|
LOG.debug("Type rejected for {}: candidate={} in conflict with current={}", arg, candidateType, currentType);
|
||||||
|
}
|
||||||
|
return REJECT;
|
||||||
|
}
|
||||||
if (compareResult == TypeCompareEnum.UNKNOWN && updateInfo.getFlags().isIgnoreUnknown()) {
|
if (compareResult == TypeCompareEnum.UNKNOWN && updateInfo.getFlags().isIgnoreUnknown()) {
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
@@ -144,11 +165,7 @@ public final class TypeUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (arg instanceof RegisterArg) {
|
return null;
|
||||||
RegisterArg reg = (RegisterArg) arg;
|
|
||||||
return updateTypeForSsaVar(updateInfo, reg.getSVar(), candidateType);
|
|
||||||
}
|
|
||||||
return requestUpdate(updateInfo, arg, candidateType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeUpdateResult updateTypeForSsaVar(TypeUpdateInfo updateInfo, SSAVar ssaVar, ArgType candidateType) {
|
private TypeUpdateResult updateTypeForSsaVar(TypeUpdateInfo updateInfo, SSAVar ssaVar, ArgType candidateType) {
|
||||||
@@ -163,25 +180,25 @@ public final class TypeUpdate {
|
|||||||
if (!inBounds(updateInfo, ssaVar, typeInfo.getBounds(), candidateType)) {
|
if (!inBounds(updateInfo, ssaVar, typeInfo.getBounds(), candidateType)) {
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
return requestUpdateForSsaVar(updateInfo, ssaVar, candidateType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private TypeUpdateResult requestUpdateForSsaVar(TypeUpdateInfo updateInfo, SSAVar ssaVar, ArgType candidateType) {
|
|
||||||
boolean allSame = true;
|
|
||||||
TypeUpdateResult result = requestUpdate(updateInfo, ssaVar.getAssign(), candidateType);
|
TypeUpdateResult result = requestUpdate(updateInfo, ssaVar.getAssign(), candidateType);
|
||||||
if (result == REJECT) {
|
boolean allSame = result == SAME;
|
||||||
return result;
|
if (result != REJECT) {
|
||||||
|
List<RegisterArg> useList = ssaVar.getUseList();
|
||||||
|
for (RegisterArg arg : useList) {
|
||||||
|
result = requestUpdate(updateInfo, arg, candidateType);
|
||||||
|
if (result == REJECT) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (result != SAME) {
|
||||||
|
allSame = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
List<RegisterArg> useList = ssaVar.getUseList();
|
if (result == REJECT) {
|
||||||
for (RegisterArg arg : useList) {
|
// rollback update for all registers in current SSA var
|
||||||
TypeUpdateResult useResult = requestUpdate(updateInfo, arg, candidateType);
|
updateInfo.rollbackUpdate(ssaVar.getAssign());
|
||||||
if (useResult == REJECT) {
|
ssaVar.getUseList().forEach(updateInfo::rollbackUpdate);
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
|
||||||
if (useResult != SAME) {
|
|
||||||
allSame = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return allSame ? SAME : CHANGED;
|
return allSame ? SAME : CHANGED;
|
||||||
}
|
}
|
||||||
@@ -191,7 +208,6 @@ public final class TypeUpdate {
|
|||||||
return CHANGED;
|
return CHANGED;
|
||||||
}
|
}
|
||||||
updateInfo.requestUpdate(arg, candidateType);
|
updateInfo.requestUpdate(arg, candidateType);
|
||||||
updateInfo.checkUpdatesCount();
|
|
||||||
try {
|
try {
|
||||||
TypeUpdateResult result = runListeners(updateInfo, arg, candidateType);
|
TypeUpdateResult result = runListeners(updateInfo, arg, candidateType);
|
||||||
if (result == REJECT) {
|
if (result == REJECT) {
|
||||||
@@ -199,7 +215,8 @@ public final class TypeUpdate {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} catch (StackOverflowError | BootstrapMethodError error) {
|
} catch (StackOverflowError | BootstrapMethodError error) {
|
||||||
throw new JadxOverflowException("Type update terminated with stack overflow, arg: " + arg);
|
throw new JadxOverflowException("Type update terminated with stack overflow, arg: " + arg
|
||||||
|
+ ", method size: " + updateInfo.getMth().getInsnsCount());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +252,7 @@ public final class TypeUpdate {
|
|||||||
}
|
}
|
||||||
if (boundType != null && !checkBound(candidateType, bound, boundType)) {
|
if (boundType != null && !checkBound(candidateType, bound, boundType)) {
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
LOG.debug("Reject type '{}' for {} by bound: {}", candidateType, ssaVar, bound);
|
LOG.debug("Reject type '{}' for {} by bound: {} from {}", candidateType, ssaVar, boundType, bound);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -265,6 +282,7 @@ public final class TypeUpdate {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case CONFLICT:
|
case CONFLICT:
|
||||||
|
case CONFLICT_BY_GENERIC:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case UNKNOWN:
|
case UNKNOWN:
|
||||||
@@ -407,6 +425,9 @@ public final class TypeUpdate {
|
|||||||
|
|
||||||
private TypeUpdateResult sameFirstArgListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
private TypeUpdateResult sameFirstArgListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
||||||
InsnArg changeArg = isAssign(insn, arg) ? insn.getArg(0) : insn.getResult();
|
InsnArg changeArg = isAssign(insn, arg) ? insn.getArg(0) : insn.getResult();
|
||||||
|
if (updateInfo.hasUpdateWithType(changeArg, candidateType)) {
|
||||||
|
return CHANGED;
|
||||||
|
}
|
||||||
return updateTypeChecked(updateInfo, changeArg, candidateType);
|
return updateTypeChecked(updateInfo, changeArg, candidateType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,15 +521,43 @@ public final class TypeUpdate {
|
|||||||
TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType);
|
TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType);
|
||||||
return result == REJECT ? SAME : result;
|
return result == REJECT ? SAME : result;
|
||||||
}
|
}
|
||||||
if (candidateType.containsGeneric()) {
|
ArgType castType = (ArgType) checkCast.getIndex();
|
||||||
ArgType castType = (ArgType) checkCast.getIndex();
|
TypeCompareEnum res = comparator.compareTypes(candidateType, castType);
|
||||||
TypeCompareEnum compResult = comparator.compareTypes(candidateType, castType);
|
if (res == TypeCompareEnum.CONFLICT) {
|
||||||
if (compResult == TypeCompareEnum.NARROW_BY_GENERIC) {
|
// allow casting one interface to another
|
||||||
// propagate generic type to result
|
if (!isInterfaces(candidateType, castType)) {
|
||||||
return updateTypeChecked(updateInfo, checkCast.getResult(), candidateType);
|
return REJECT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return SAME;
|
if (res == TypeCompareEnum.CONFLICT_BY_GENERIC) {
|
||||||
|
if (!insn.contains(AFlag.SOFT_CAST)) {
|
||||||
|
return REJECT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (res == TypeCompareEnum.NARROW_BY_GENERIC && candidateType.containsGeneric()) {
|
||||||
|
// propagate generic type to result
|
||||||
|
return updateTypeChecked(updateInfo, checkCast.getResult(), candidateType);
|
||||||
|
}
|
||||||
|
ArgType currentType = checkCast.getArg(0).getType();
|
||||||
|
return candidateType.equals(currentType) ? SAME : CHANGED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInterfaces(ArgType firstType, ArgType secondType) {
|
||||||
|
if (!firstType.isObject() || !secondType.isObject()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ClspClass firstCls = root.getClsp().getClsDetails(firstType);
|
||||||
|
ClspClass secondCls = root.getClsp().getClsDetails(secondType);
|
||||||
|
if (firstCls != null && !firstCls.isInterface()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (secondCls != null && !secondCls.isInterface()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (firstCls == null || secondCls == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return secondCls.isInterface() && firstCls.isInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeUpdateResult arrayGetListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
private TypeUpdateResult arrayGetListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
package jadx.core.dex.visitors.typeinference;
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
|
||||||
public final class TypeUpdateEntry {
|
public final class TypeUpdateEntry implements Comparable<TypeUpdateEntry> {
|
||||||
|
private final int seq;
|
||||||
private final InsnArg arg;
|
private final InsnArg arg;
|
||||||
private final ArgType type;
|
private final ArgType type;
|
||||||
|
|
||||||
public TypeUpdateEntry(InsnArg arg, ArgType type) {
|
public TypeUpdateEntry(int seq, InsnArg arg, ArgType type) {
|
||||||
|
this.seq = seq;
|
||||||
this.arg = arg;
|
this.arg = arg;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getSeq() {
|
||||||
|
return seq;
|
||||||
|
}
|
||||||
|
|
||||||
public InsnArg getArg() {
|
public InsnArg getArg() {
|
||||||
return arg;
|
return arg;
|
||||||
}
|
}
|
||||||
@@ -20,8 +28,13 @@ public final class TypeUpdateEntry {
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(@NotNull TypeUpdateEntry other) {
|
||||||
|
return Integer.compare(this.seq, other.seq);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "TypeUpdateEntry{" + arg + " -> " + type + '}';
|
return type + " -> " + arg.toShortString() + " in " + arg.getParentInsn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
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 ALLOW_WIDER = 1;
|
||||||
private static final int IGNORE_SAME = 2;
|
private static final int IGNORE_SAME = 2;
|
||||||
@@ -14,7 +12,6 @@ public class TypeUpdateFlags {
|
|||||||
|
|
||||||
private final int flags;
|
private final int flags;
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private static TypeUpdateFlags build(int flags) {
|
private static TypeUpdateFlags build(int flags) {
|
||||||
return new TypeUpdateFlags(flags);
|
return new TypeUpdateFlags(flags);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +1,84 @@
|
|||||||
package jadx.core.dex.visitors.typeinference;
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public class TypeUpdateInfo {
|
public class TypeUpdateInfo {
|
||||||
private final MethodNode mth;
|
private final MethodNode mth;
|
||||||
private final TypeUpdateFlags flags;
|
private final TypeUpdateFlags flags;
|
||||||
private final List<TypeUpdateEntry> updates = new ArrayList<>();
|
private final Map<InsnArg, TypeUpdateEntry> updateMap = new IdentityHashMap<>();
|
||||||
private final int updatesLimitCount;
|
private final int updatesLimitCount;
|
||||||
|
private int updateSeq = 0;
|
||||||
|
|
||||||
public TypeUpdateInfo(MethodNode mth, TypeUpdateFlags flags) {
|
public TypeUpdateInfo(MethodNode mth, TypeUpdateFlags flags) {
|
||||||
this.mth = mth;
|
this.mth = mth;
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
this.updatesLimitCount = mth.getInsnsCount() * 5; // maximum registers count to update at once
|
this.updatesLimitCount = mth.getInsnsCount() * 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void requestUpdate(InsnArg arg, ArgType changeType) {
|
public void requestUpdate(InsnArg arg, ArgType changeType) {
|
||||||
updates.add(new TypeUpdateEntry(arg, changeType));
|
TypeUpdateEntry prev = updateMap.put(arg, new TypeUpdateEntry(updateSeq++, arg, changeType));
|
||||||
|
if (prev != null) {
|
||||||
|
throw new JadxRuntimeException("Unexpected type update override for arg: " + arg
|
||||||
|
+ " types: prev=" + prev.getType() + ", new=" + changeType
|
||||||
|
+ ", insn: " + arg.getParentInsn());
|
||||||
|
}
|
||||||
|
if (updateSeq > updatesLimitCount) {
|
||||||
|
throw new JadxOverflowException("Type inference error: updates count limit reached");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rollbackUpdate(InsnArg arg) {
|
||||||
|
TypeUpdateEntry removed = updateMap.remove(arg);
|
||||||
|
if (removed != null) {
|
||||||
|
int seq = removed.getSeq();
|
||||||
|
updateMap.values().removeIf(upd -> upd.getSeq() > seq);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void applyUpdates() {
|
public void applyUpdates() {
|
||||||
for (TypeUpdateEntry updateEntry : updates) {
|
updateMap.values().stream().sorted()
|
||||||
InsnArg arg = updateEntry.getArg();
|
.forEach(upd -> upd.getArg().setType(upd.getType()));
|
||||||
arg.setType(updateEntry.getType());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isProcessed(InsnArg arg) {
|
public boolean isProcessed(InsnArg arg) {
|
||||||
if (updates.isEmpty()) {
|
return updateMap.containsKey(arg);
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
for (TypeUpdateEntry entry : updates) {
|
public boolean hasUpdateWithType(InsnArg arg, ArgType type) {
|
||||||
if (entry.getArg() == arg) {
|
TypeUpdateEntry updateEntry = updateMap.get(arg);
|
||||||
return true;
|
if (updateEntry != null) {
|
||||||
}
|
return updateEntry.getType().equals(type);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArgType getType(InsnArg arg) {
|
public ArgType getType(InsnArg arg) {
|
||||||
for (TypeUpdateEntry update : updates) {
|
TypeUpdateEntry updateEntry = updateMap.get(arg);
|
||||||
if (update.getArg() == arg) {
|
if (updateEntry != null) {
|
||||||
return update.getType();
|
return updateEntry.getType();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return arg.getType();
|
return arg.getType();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void rollbackUpdate(InsnArg arg) {
|
|
||||||
updates.removeIf(updateEntry -> updateEntry.getArg() == arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkUpdatesCount() {
|
|
||||||
if (updates.size() > updatesLimitCount) {
|
|
||||||
throw new JadxOverflowException("Type inference error: update tree size limit reached");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MethodNode getMth() {
|
public MethodNode getMth() {
|
||||||
return mth;
|
return mth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<TypeUpdateEntry> getUpdates() {
|
public boolean isEmpty() {
|
||||||
return updates;
|
return updateMap.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TypeUpdateEntry> getSortedUpdates() {
|
||||||
|
return updateMap.values().stream().sorted().collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypeUpdateFlags getFlags() {
|
public TypeUpdateFlags getFlags() {
|
||||||
@@ -76,6 +87,6 @@ public class TypeUpdateInfo {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "TypeUpdateInfo{" + flags + ", updates=" + updates + '}';
|
return "TypeUpdateInfo{" + flags + ' ' + getSortedUpdates() + '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ import java.util.BitSet;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
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;
|
||||||
@@ -37,6 +36,7 @@ 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.CatchAttr;
|
import jadx.core.dex.trycatch.CatchAttr;
|
||||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
|
import jadx.core.utils.blocks.BlockSet;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public class BlockUtils {
|
public class BlockUtils {
|
||||||
@@ -483,33 +483,37 @@ public class BlockUtils {
|
|||||||
|
|
||||||
public static List<BlockNode> collectAllSuccessors(MethodNode mth, BlockNode startBlock, boolean clean) {
|
public static List<BlockNode> collectAllSuccessors(MethodNode mth, BlockNode startBlock, boolean clean) {
|
||||||
List<BlockNode> list = new ArrayList<>(mth.getBasicBlocks().size());
|
List<BlockNode> list = new ArrayList<>(mth.getBasicBlocks().size());
|
||||||
dfsVisit(mth, startBlock, clean, list::add);
|
Function<BlockNode, List<BlockNode>> nextFunc = clean ? BlockNode::getCleanSuccessors : BlockNode::getSuccessors;
|
||||||
|
visitDFS(mth, startBlock, nextFunc, list::add);
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void dfsVisit(MethodNode mth, Consumer<BlockNode> visitor) {
|
public static void visitDFS(MethodNode mth, Consumer<BlockNode> visitor) {
|
||||||
dfsVisit(mth, mth.getEnterBlock(), false, visitor);
|
visitDFS(mth, mth.getEnterBlock(), BlockNode::getSuccessors, visitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void dfsVisit(MethodNode mth, BlockNode startBlock, boolean clean, Consumer<BlockNode> visitor) {
|
public static void visitReverseDFS(MethodNode mth, Consumer<BlockNode> visitor) {
|
||||||
BitSet visited = newBlocksBitSet(mth);
|
visitDFS(mth, mth.getExitBlock(), BlockNode::getPredecessors, visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void visitDFS(MethodNode mth, BlockNode startBlock,
|
||||||
|
Function<BlockNode, List<BlockNode>> nextFunc, Consumer<BlockNode> visitor) {
|
||||||
|
BlockSet visited = new BlockSet(mth);
|
||||||
Deque<BlockNode> queue = new ArrayDeque<>();
|
Deque<BlockNode> queue = new ArrayDeque<>();
|
||||||
queue.addLast(startBlock);
|
queue.addLast(startBlock);
|
||||||
visited.set(startBlock.getId());
|
visited.set(startBlock);
|
||||||
while (true) {
|
while (true) {
|
||||||
BlockNode current = queue.pollLast();
|
BlockNode current = queue.pollLast();
|
||||||
if (current == null) {
|
if (current == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
visitor.accept(current);
|
visitor.accept(current);
|
||||||
List<BlockNode> successors = clean ? current.getCleanSuccessors() : current.getSuccessors();
|
List<BlockNode> nextBlocks = nextFunc.apply(current);
|
||||||
int count = successors.size();
|
int count = nextBlocks.size();
|
||||||
for (int i = count - 1; i >= 0; i--) { // to preserve order in queue
|
for (int i = count - 1; i >= 0; i--) { // to preserve order in queue
|
||||||
BlockNode next = successors.get(i);
|
BlockNode next = nextBlocks.get(i);
|
||||||
int nextId = next.getId();
|
if (!visited.checkAndSet(next)) {
|
||||||
if (!visited.get(nextId)) {
|
|
||||||
queue.addLast(next);
|
queue.addLast(next);
|
||||||
visited.set(nextId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1156,104 +1160,6 @@ public class BlockUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<BlockNode, BitSet> calcPostDominance(MethodNode mth) {
|
|
||||||
return calcPartialPostDominance(mth, mth.getBasicBlocks(), mth.getPreExitBlocks().get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<BlockNode, BitSet> calcPartialPostDominance(MethodNode mth, Collection<BlockNode> blockNodes, BlockNode exitBlock) {
|
|
||||||
int blocksCount = mth.getBasicBlocks().size();
|
|
||||||
Map<BlockNode, BitSet> map = new HashMap<>(blocksCount);
|
|
||||||
|
|
||||||
BitSet initSet = new BitSet(blocksCount);
|
|
||||||
for (BlockNode block : blockNodes) {
|
|
||||||
initSet.set(block.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (BlockNode block : blockNodes) {
|
|
||||||
BitSet postDoms = new BitSet(blocksCount);
|
|
||||||
postDoms.or(initSet);
|
|
||||||
map.put(block, postDoms);
|
|
||||||
}
|
|
||||||
BitSet exitBitSet = map.get(exitBlock);
|
|
||||||
exitBitSet.clear();
|
|
||||||
exitBitSet.set(exitBlock.getId());
|
|
||||||
|
|
||||||
BitSet domSet = new BitSet(blocksCount);
|
|
||||||
boolean changed;
|
|
||||||
do {
|
|
||||||
changed = false;
|
|
||||||
for (BlockNode block : blockNodes) {
|
|
||||||
if (block == exitBlock) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
BitSet d = map.get(block);
|
|
||||||
if (!changed) {
|
|
||||||
domSet.clear();
|
|
||||||
domSet.or(d);
|
|
||||||
}
|
|
||||||
for (BlockNode scc : block.getSuccessors()) {
|
|
||||||
BitSet scPDoms = map.get(scc);
|
|
||||||
if (scPDoms != null) {
|
|
||||||
d.and(scPDoms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.set(block.getId());
|
|
||||||
if (!changed && !d.equals(domSet)) {
|
|
||||||
changed = true;
|
|
||||||
map.put(block, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (changed);
|
|
||||||
|
|
||||||
blockNodes.forEach(block -> {
|
|
||||||
BitSet postDoms = map.get(block);
|
|
||||||
postDoms.clear(block.getId());
|
|
||||||
if (postDoms.isEmpty()) {
|
|
||||||
map.put(block, EmptyBitSet.EMPTY);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static BlockNode calcImmediatePostDominator(MethodNode mth, BlockNode block) {
|
|
||||||
BlockNode oneSuccessor = Utils.getOne(block.getSuccessors());
|
|
||||||
if (oneSuccessor != null) {
|
|
||||||
return oneSuccessor;
|
|
||||||
}
|
|
||||||
return calcImmediatePostDominator(mth, block, calcPostDominance(mth));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static BlockNode calcPartialImmediatePostDominator(MethodNode mth, BlockNode block,
|
|
||||||
Collection<BlockNode> blockNodes, BlockNode exitBlock) {
|
|
||||||
BlockNode oneSuccessor = Utils.getOne(block.getSuccessors());
|
|
||||||
if (oneSuccessor != null) {
|
|
||||||
return oneSuccessor;
|
|
||||||
}
|
|
||||||
Map<BlockNode, BitSet> pDomsMap = calcPartialPostDominance(mth, blockNodes, exitBlock);
|
|
||||||
return calcImmediatePostDominator(mth, block, pDomsMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static BlockNode calcImmediatePostDominator(MethodNode mth, BlockNode block, Map<BlockNode, BitSet> postDomsMap) {
|
|
||||||
BlockNode oneSuccessor = Utils.getOne(block.getSuccessors());
|
|
||||||
if (oneSuccessor != null) {
|
|
||||||
return oneSuccessor;
|
|
||||||
}
|
|
||||||
List<BlockNode> basicBlocks = mth.getBasicBlocks();
|
|
||||||
BitSet postDoms = postDomsMap.get(block);
|
|
||||||
BitSet bs = copyBlocksBitSet(mth, postDoms);
|
|
||||||
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
|
|
||||||
BlockNode pdomBlock = basicBlocks.get(i);
|
|
||||||
BitSet pdoms = postDomsMap.get(pdomBlock);
|
|
||||||
if (pdoms != null) {
|
|
||||||
bs.andNot(pdoms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bitSetToOneBlock(mth, bs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BlockNode getTopSplitterForHandler(BlockNode handlerBlock) {
|
public static BlockNode getTopSplitterForHandler(BlockNode handlerBlock) {
|
||||||
BlockNode block = getBlockWithFlag(handlerBlock.getPredecessors(), AFlag.EXC_TOP_SPLITTER);
|
BlockNode block = getBlockWithFlag(handlerBlock.getPredecessors(), AFlag.EXC_TOP_SPLITTER);
|
||||||
if (block == null) {
|
if (block == null) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user