Compare commits

...

51 Commits

Author SHA1 Message Date
Skylot f2ea6415c9 fix(cli): don't print stacktrace for incorrect options (#2140) 2024-04-20 18:06:30 +01:00
Skylot bc70f8eabb fix: use correct new line string for simple code writer 2024-04-20 17:37:45 +01:00
Skylot be25cbf8c2 fix: use common parser for manifest, verify app package 2024-04-20 17:37:45 +01:00
Skylot f9c0cad146 chore: update dependencies 2024-04-19 20:14:25 +01:00
Skylot b356ff76e1 fix: improve StringBuilder elimination (#2148) 2024-04-19 20:14:25 +01:00
Skylot ec9244a635 fix(gui): use common code for manifest parsing in debugger 2024-04-19 20:14:25 +01:00
omerfarukkykc a5bd64461d fix(gui): remember selected device in debugger (PR #2153)
* ADBDialog->launchApp() if multiple devices presented should let user select the one they desire.

* compare objects directly instead parsing

---------

Co-authored-by: Ömer Faruk KAYIKCI <omer.kayikci@tubitak.gov.tr>
Co-authored-by: Skylot <118523+skylot@users.noreply.github.com>
2024-04-19 19:02:12 +01:00
dependabot[bot] 54bf79ccc5 build(deps): bump gradle/wrapper-validation-action from 2 to 3 (#2149) 2024-04-15 18:17:48 +00:00
Skylot 6182332eef fix: avoid self-loop for exception handlers (#2147) 2024-04-11 23:07:45 +03:00
Skylot 37b57096ec fix: allow use FieldInfo as switch key (#2147) 2024-04-11 23:07:44 +03:00
Skylot 6aab8fabc9 chore: update dependencies 2024-04-11 23:07:41 +03:00
JustFor 665c1e57d2 fix(gui): update Messages_zh_CN.properties (PR #2146)
Sync new Jadx text.
2024-04-09 22:11:24 +01:00
Skylot 6e8affcbdc feat: add options to JadxArgs to change code new line and indent (#1945, #1948) 2024-04-08 21:51:24 +01:00
Skylot 41d6b0018e fix: add missing " * " on new line for block comments, flip addCodeComment args (#2145) 2024-04-08 21:34:34 +01:00
Skylot dbadbb01fc refactor: rename method collectArgsWithoutLoading into collectArgNodes in MethodNode (#2142) 2024-04-07 23:09:02 +01:00
Skylot 0f52077c5c feat: allow to set style for code comments (#2145) 2024-04-07 23:06:32 +01:00
Skylot ea861829c7 fix: support end block entry for mutli-entry loops (#889) 2024-04-06 22:49:32 +01:00
Skylot c1de235289 fix: in anonymous class checks ignore instance fields not used outside 2024-04-06 22:45:30 +01:00
Skylot 8f969d4e89 chore: update gradle and dependencies 2024-04-03 21:03:48 +01:00
Skylot 0c1f830f94 fix: lambda decoding and code generation (#2139) 2024-04-03 21:03:48 +01:00
Skylot 43c082e4da feat: replace Android resource ids with android.R fields (#2119) 2024-03-31 20:37:33 +01:00
Skylot ecdc4e6757 refactor: move constant collection into separate pass (#2119) 2024-03-30 21:51:02 +00:00
Skylot b865c9c687 refactor: allow store unresolved fields in ConstStorage (#2119) 2024-03-30 20:52:31 +00:00
xnumad 6b4976c593 fix(gui): handle paths where file name is null (#2136)(PR #2137)
* fix: Ignore invalid files

Avoid NullPointerException when using "Open files" or drag-n-drop

* refactor: Replace Stream API chain with loop

IntelliJ

* fix: Ignore invalid files

Avoid NullPointerException when using "Add files"

* fix: Fall back to complete path string

Instead of empty project name

* fix: Render tree

Project tree (sidebar) didn’t load
Toggling "View > Show flatten packages" threw a NPE here

* fix code formatting

---------

Co-authored-by: Skylot <skylot@gmail.com>
2024-03-29 22:30:01 +00:00
Skylot 2807dc5090 fix(script): add example script for resources rename (#2126) 2024-03-20 18:46:44 +00:00
Skylot 463d2b90fa fix: don't apply node positions and prevent eager loading for custom decompile modes (#2116) 2024-03-19 20:23:04 +00:00
Skylot bff00d101f fix(script): add option flags, fix missing script options in help 2024-03-19 20:22:50 +00:00
Skylot 1290ef63a2 fix(build): enable publish to maven for rename-mappings plugin 2024-03-16 21:58:03 +03:00
Skylot 49d2b34d84 chore: update dependencies 2024-03-16 21:58:00 +03:00
CKCat eecdfae73f fix(res): resolve some manifest decode errors (PR #2122)
* The elementSize may be larger than the actual size of the element chunk.

* end namespace chunk size can be any value.

* keep at least a warning.
2024-03-16 18:57:10 +00:00
JustFor 8760b4ddde fix(gui): copy strings without quotes (PR #2121)
* Update AbstractCodeArea.java

In general, we need data, not text in code. But now every time you copy the highlighted text, you copy the highlighted quotes as well. This often results in an extra need to delete the quotation marks around the sides, which is confusing.
Now when copying selected highlighted text, quotes are not copied in.

* Update AbstractCodeArea.java

fix code format

* additional checks, move to common method

---------

Co-authored-by: Skylot <skylot@gmail.com>
2024-03-16 18:55:57 +00:00
Andrei Kudryavtsev 3599b248a4 feat(gui): dragging tab appearance settings (#2120)(PR #2118) 2024-03-08 23:11:58 +03:00
bagipro 2fdd496518 fix(res): add indents for namespace declarations (PR #2114)
Co-authored-by: bagipro <bugi@bugi>
2024-03-01 16:32:47 +00:00
bagipro 278e3c2d47 fix(res): avoid duplicated XML attributes (PR #2112)
Co-authored-by: bagipro <bugi@bugi>
2024-02-27 18:49:09 +00:00
bagipro 881a716b8e fix(res): fixed XML proto parsing for removed debug data (PR #2111)
* Fixed XML proto parsing for removed debug data

* Fixed codestyle check

---------

Co-authored-by: bagipro <bugi@bugi>
2024-02-27 17:34:37 +00:00
Skylot a73c9e90fc fix(dex-input): improve error report message for invalid dex checksum 2024-02-26 19:36:28 +00:00
Skylot 56749b2afb chore: update dependencies 2024-02-25 22:38:00 +03:00
Andrei Kudryavtsev d7ec35791b feat(gui): tabs drag and drop reorder support (#1212) (PR #2109) 2024-02-25 19:36:46 +00:00
Skylot d51362ed50 fix: don't remove exception handlers (#2104) 2024-02-19 20:18:44 +00:00
Skylot 5c0c1daa71 fix(gui): use new RSTA line number formatter API to show source lines 2024-02-16 18:38:56 +00:00
Skylot 603ea3989a chore: update dependencies 2024-02-16 17:41:37 +00:00
Emiel Matthys 018ff98df7 feat(gui): remember save preference decision (PR #2103)
* First version

* Use dropdown

* Spotless

* Language strings and tests

* Comment out translated versions

* Remove more translations

---------

Co-authored-by: Emiel Matthys <emiel.matthys@guardsquare.com>
2024-02-15 18:00:37 +00:00
nitram84 5fbabdefca fix: NPE in unused EcxHandler block removal code (#2086) (PR #2104) 2024-02-15 17:57:30 +00:00
nitram84 13607fc8b6 fix(res): add missing namespace declarations (PR #2102)
* fix(res): add missing namespace declarations

* remove `writer.getIndent() == 0`

---------

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
2024-02-15 17:56:39 +00:00
DanielFi 0c33d723c8 fix: optimize switch fallthrough (PR #2054)
* cache post dom map between switch cases
* cache post dom map of whole methods
* calculate full post dom tree, fix switch out block detection

---------

Co-authored-by: Skylot <skylot@gmail.com>
2024-02-14 18:31:38 +00:00
Skylot 0143423dc9 fix: use empty line before field in correct place (#2101) 2024-02-12 15:39:41 +00:00
Skylot 21b1452485 chore: update gradle and dependencies 2024-02-12 15:39:40 +00:00
Skylot ecb8abb98e fix: correct rollback if type update failed (#2090) 2024-02-10 16:08:51 +00:00
Skylot a3a4fabd5a fix: store classes access flags in class set 2024-02-07 22:10:31 +03:00
Skylot edf6ce273c fix: clear node flags for custom decompilation mode 2024-02-06 21:54:41 +03:00
dependabot[bot] 1bb956a8b0 build(deps): bump gradle/wrapper-validation-action from 1 to 2 (PR #2099)
Bumps [gradle/wrapper-validation-action](https://github.com/gradle/wrapper-validation-action) from 1 to 2.
- [Release notes](https://github.com/gradle/wrapper-validation-action/releases)
- [Commits](https://github.com/gradle/wrapper-validation-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: gradle/wrapper-validation-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 18:38:02 +00:00
234 changed files with 4946 additions and 2529 deletions
@@ -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
+10 -6
View File
@@ -8,16 +8,19 @@
![GitHub release (latest by SemVer)](https://img.shields.io/github/downloads/skylot/jadx/latest/total) ![GitHub release (latest by SemVer)](https://img.shields.io/github/downloads/skylot/jadx/latest/total)
![Latest release](https://img.shields.io/github/release/skylot/jadx.svg) ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)
[![Maven Central](https://img.shields.io/maven-central/v/io.github.skylot/jadx-core)](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx) [![Maven Central](https://img.shields.io/maven-central/v/io.github.skylot/jadx-core)](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
![Java 11+](https://img.shields.io/badge/Java-11%2B-blue)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) [![License](http://img.shields.io/:license-apache-blue.svg)](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)
+1 -1
View File
@@ -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")
Binary file not shown.
+2 -2
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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");
+6 -2
View File
@@ -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
* *&#47;
* </pre>
*/
// @formatter:on
BLOCK("/*\n * ", " * ", "\n */"),
/**
* <pre>
* /* comment *&#47;
* </pre>
*/
BLOCK_CONDENSED("/* ", " * ", " */"),
// @formatter:off
/**
* <pre>
* /**
* * comment
* *&#47;
* </pre>
*/
// @formatter:on
JAVADOC("/**\n * ", " * ", "\n */"),
/**
* <pre>
* /** comment *&#47;
* </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 + "'}";
}
}
@@ -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;
} }
} }
} }
@@ -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";
}
}
@@ -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;
} }
} }
@@ -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