Compare commits

...

76 Commits

Author SHA1 Message Date
Skylot 09335395f5 doc: update option description 2022-02-20 16:51:36 +00:00
Skylot 57e3dd8f15 feat(cli): improve single file mode (#1344)(#1384) 2022-02-20 15:04:59 +00:00
Skylot a9bbadd602 feat: add option for deobfuscation map file handle mode (#1351) 2022-02-19 21:20:11 +03:00
skylot 2c570681f7 doc: add link to jadx-gui key bindings in readme 2022-02-18 20:26:39 +00:00
Skylot 25166970cc feat(gui): ctrl+c copy node string in search window (#293) 2022-02-18 19:10:56 +00:00
Skylot d3a0a56b8b feat(gui): ctrl+c copy highlighted word in code view (#1292) 2022-02-18 19:10:34 +00:00
YenKoc 3c2c198a0e feat(gui): add Xposed snippet copy action (PR #1383)
* add xposedscript
* fix code style and minor issues
* some code style changes for Xposed snippets
* some code style changes for Frida snippets + a fix for multidimensional arrays in overload params
* hide frida and xposed when right-clicking on a null node
* small style fix
* fixed formatting violations
* fix minor issues

Co-authored-by: Skylot <skylot@gmail.com>
Co-authored-by: Orip <oriori1703@gmail.com>
2022-02-18 12:54:41 +00:00
Skylot 4d4d67f0b4 fix: remove shadowed catch handlers (#1377) 2022-02-16 19:31:19 +00:00
Skylot 97e8a34906 fix: prevent some NPE in try/catch/finally processing (#1379) 2022-02-15 12:29:30 +00:00
Skylot 82f3b57e83 perf: improve ternary mod on big methods (#1379) 2022-02-15 12:03:06 +00:00
Skylot af2f14f807 fix: prevent endless loop in anonymous class analysis (#1382) 2022-02-14 23:23:02 +00:00
Skylot fe248d7098 fix: check values in inner class annotation (#1382) 2022-02-14 18:25:54 +00:00
Skylot 1a2e702b25 fix: inline nested anonymous classes (#1379) 2022-02-14 17:30:22 +00:00
Skylot 1da20b8e7d doc: update readme 2022-02-14 16:41:31 +00:00
Skylot 01f74ff706 chore: update gradle and dependencies 2022-02-13 19:08:49 +00:00
Skylot 89e95eb9ee fix: correct code reload after rename (#1378) 2022-02-12 19:15:18 +00:00
Skylot a61ebaaa00 fix: sum only sub dependencies in batches build (#1376) 2022-02-11 19:53:12 +00:00
xxjy 7a5a2fcd84 fix: nested try catches with overlap try blocks (#1374)(PR #1375)
* fix: nested try catch decompilation failed (#1374)
* add tests and sort handlers

Co-authored-by: Skylot <skylot@gmail.com>
2022-02-09 20:55:15 +00:00
Jan S 8d5554f1b5 fix(gui): frida context menu entry does nothing (#1365)(PR #1372) 2022-02-08 12:47:49 +00:00
Ori Perry 873aabb471 fix: use raw class names in Frida action (#1365)(PR #1366)
* Use raw_name instead of full_name for the names of class in generated frida snippet.
Also cleaned the code a bit

* Fixed getting method parameters from inlined methods

* fixed generating code for constructor overloads, more cleaning

* Fixed getting method parameters from inlined methods for real this time

* made the option for a frida snippet only appear if clicked on a relevant node

* added support for generating a frida snippet for fields

* apply spotless

* Update jadx-gui/src/main/java/jadx/gui/ui/codearea/FridaAction.java

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>

* moved the overload check from NodeMethod to FridaAction

* added semicolons in the end of lines of the generated frida snippet

* fix code formatting
2022-02-07 21:50:01 +00:00
cyqw 4bed9dc358 fix(gui): results in usage search should be sorted by name (PR #1363) 2022-02-07 15:39:57 +00:00
nitram84 e229874195 fix: check if targetSdkVersion is missing in gradle export (#1367)(PR #1370) 2022-02-07 10:39:09 +00:00
Skylot 473b6e31e9 fix: support multi-entry loops (simple case) (#1320) 2022-02-06 18:36:33 +00:00
Jan S b5ce460618 feat(deobf): do not deobfuscate known top level domains with 2 or 3 characters (PR #1369) 2022-02-06 12:56:59 +00:00
Skylot 3c05b05196 fix: check names from Kotlin metadata before use (#1364) 2022-02-05 21:49:36 +00:00
Skylot bdb2efdb6b fix(res): remove static caching map for xml renames (#1364) 2022-02-05 20:23:44 +00:00
Skylot a27ba3ff4b fix(res): skip '.9.png' decode if patch data not found (#1112) 2022-02-05 17:45:08 +00:00
Skylot 4684207b54 fix: remove duplicate classes from decompilation batches (#1361) 2022-02-05 17:45:07 +00:00
Skylot dd1be3039b fix(gui): split decompile and index tasks for correct time counting (#1361) 2022-02-05 17:45:07 +00:00
Skylot 8b30b770cd fix(gui): missing icons and html decorations in usage dialog 2022-02-05 13:36:26 +00:00
Yotam 47caa91e85 fix(cli): fix and add debug log messages in initialization phase (PR #1362)
* Fix log level settings in the CLI
* Add log messages in initialization phase
2022-02-02 19:04:19 +00:00
Skylot d71f3e09df fix: prevent endless loop in path cross search (#1360) 2022-02-01 14:32:44 +00:00
Jan S 06c7415827 fix(res): improved decoding of flag attributes in binary XML files (#1156)(PR #1359) 2022-01-31 18:00:50 +00:00
Skylot bd3e62617e fix: correct inline for enums in j$.time.temporal 2022-01-31 11:49:59 +00:00
Skylot 00b48473a0 test: add internal option to disable file save 2022-01-31 10:27:20 +00:00
Skylot 84facb13d0 fix: don't inline named variables (#1338) 2022-01-28 18:33:38 +00:00
Skylot 96f90e18e8 fix: improve exception handlers attach 2022-01-26 15:43:40 +00:00
Skylot 8ff18e63ee chore: update dependencies 2022-01-25 18:51:43 +00:00
Skylot 381405ea99 fix: always use deep resolve for fields and methods (#1357) 2022-01-25 11:37:36 +00:00
Ahmet Bilal Can ae5c00397a feat(gui): add frida action to copy methods/classes as frida snippets (#1355)(PR #1356)
* add frida action to copy methods/classes as frida snippets
* bug: call toString before comparing
2022-01-24 21:37:12 +00:00
Skylot bd4509f1a7 fix: update field usage on const replace (#1348) 2022-01-24 18:22:43 +00:00
Skylot b8c84886a8 fix: correct use of class names for inner types (#1340) 2022-01-24 14:11:40 +00:00
Skylot 45021389bc fix: correct method arg name if unused 2022-01-24 13:38:49 +00:00
Yotam f674a29a64 fix(deobf): rename classes as anonymous only if they are a number (PR #1354) 2022-01-23 21:16:05 +00:00
Yotam 0c9e3227d0 fix(deobf): collect missing renames for .jobf file (#1350)(PR #1353) 2022-01-23 16:08:54 +00:00
cyqw be7e1479a1 fix(gui): find usage for overridden methods (#1349)(PR #1352) 2022-01-23 16:06:13 +00:00
Skylot 19827fca20 fix: support full class name in inner generic types (#1340) 2022-01-22 18:49:31 +00:00
Skylot 5eb7cc40ed feat: check dex checksum before parsing (#1343) 2022-01-20 19:24:49 +00:00
Skylot d22db30166 fix: use secure xml parser for process manifest 2022-01-20 11:17:12 +00:00
Skylot 6db61e7a59 chore: update dependencies 2022-01-20 10:23:49 +00:00
Skylot 86582de521 feat: use kotlin intrinsic methods for variables rename (#1207) 2022-01-19 17:30:04 +00:00
Skylot a7c63c2eb3 fix: handle method override with several bases (#1234) 2022-01-18 18:27:09 +00:00
Skylot 081a0e21ee fix: precalculate class deps for inline methods (#1339) 2022-01-17 14:38:38 +00:00
Skylot 9ac9c05265 fix: simplify cascading casts (#1336) 2022-01-15 16:31:18 +00:00
Skylot b7daf79b26 fix: add explicit type for non-int constants (#1336) 2022-01-15 14:11:44 +00:00
Skylot b67a3561a4 build: add CodeQL analysis 2022-01-13 22:37:36 +03:00
Skylot 52ac6dbbaf docs: add security.md 2022-01-13 16:45:32 +00:00
Skylot 72381ad8f3 fix: correct literal negate for double and float (#1334) 2022-01-13 14:00:53 +00:00
Skylot 6a065c46f4 chore: update dependencies 2022-01-13 12:12:15 +00:00
Skylot 092d0d7e67 fix(gui): reduce tree focus switching 2022-01-12 19:57:38 +03:00
Skylot 5ca7285558 fix(gui): correct handling for tree row click (#1324) 2022-01-12 16:57:25 +00:00
Skylot 7576f9cd5e fix: wrap negative literals before cast (#1327) 2022-01-12 17:31:40 +03:00
Skylot 46b5725d98 refactor(test): replace inputs with test profiles 2022-01-12 17:31:37 +03:00
Jan S 72542fa6f9 fix(gui): processing threads spinner initialization (#1331)(PR #1332)
* fix: processing threads spinner initialization (#1331)
* fix: processing threads spinner initialization (#1331)
2022-01-12 14:23:07 +00:00
demonlol a250d0461b fix(dbg): support multiple main <action> and <activity-alias> tags (#1322)(PR #1323)
* fix(dgb): support multiple main <action> and <activity-alias> tags in manifest
* Update jadx-gui/src/main/java/jadx/gui/device/debugger/DbgUtils.java
2022-01-02 20:09:24 +03:00
Skylot c7795bfc48 fix: improve anonymous class inline (#523) 2021-12-26 13:06:49 +00:00
Skylot 5de46b7e40 chore: update gradle and dependencies 2021-12-24 12:53:30 +00:00
Skylot 99c70872c1 fix: use debug line numbers only at fixed offsets (#1315) 2021-12-22 22:55:14 +03:00
Skylot 3566669303 chore: update lgtm config 2021-12-22 12:24:01 +00:00
Skylot 4557d05256 fix: use correct type for anonymous class instance (#597) 2021-12-21 17:47:52 +00:00
Skylot fa421d165e build: disable missing warnings from javadoc 2021-12-21 12:52:52 +00:00
Skylot ecf20020d7 chore: cache current working dir in static field, other minor changes 2021-12-20 19:25:07 +00:00
Skylot ae85af61c7 fix: skip input file name checks by zip name validator (#1310) 2021-12-20 18:55:28 +00:00
Skylot 659bbbf4fb fix: correct usage of Path.getParent() 2021-12-20 16:48:50 +00:00
Jan S 427e2dddc4 fix: use relative file paths in .jadx project file (#1312) (PR #1313)
* chore: use relative file paths in .jadx project file (#1312)
* code beautified
* requested changes
2021-12-20 13:52:51 +00:00
skylot d47483f957 docs: use jadx as a library 2021-12-19 20:36:58 +00:00
220 changed files with 5741 additions and 1213 deletions
+41
View File
@@ -0,0 +1,41 @@
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 9 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ['java']
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
queries: +security-extended
languages: ${{ matrix.language }}
# Don't build tests in jadx-core also skip tests execution and checkstyle tasks
- run: |
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+2
View File
@@ -34,3 +34,5 @@ jadx-output/
*.cfg *.cfg
*.orig *.orig
quark.json quark.json
cliff.toml
+19 -6
View File
@@ -5,13 +5,14 @@
[![Build status](https://github.com/skylot/jadx/workflows/Build/badge.svg)](https://github.com/skylot/jadx/actions?query=workflow%3ABuild) [![Build status](https://github.com/skylot/jadx/workflows/Build/badge.svg)](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
[![Alerts from lgtm.com](https://img.shields.io/lgtm/alerts/g/skylot/jadx.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/skylot/jadx/alerts/) [![Alerts from lgtm.com](https://img.shields.io/lgtm/alerts/g/skylot/jadx.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/skylot/jadx/alerts/)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
[![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)
[![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 :exclamation::exclamation::exclamation: Please note that in most cases **jadx** can't decompile all 100% of the code, so errors will occur. Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds
**Main features:** **Main features:**
- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files - decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
@@ -23,7 +24,9 @@ Command line and GUI tools for producing Java source code from Android Dex and A
- jump to declaration - jump to declaration
- find usage - find usage
- full text search - full text search
- smali debugger (thanks to [@LBJ-the-GOAT](https://github.com/LBJ-the-GOAT)), check [wiki page](https://github.com/skylot/jadx/wiki/Smali-debugger) for setup and usage - smali debugger, check [wiki page](https://github.com/skylot/jadx/wiki/Smali-debugger) for setup and usage
Jadx-gui key bindings can be found [here](https://github.com/skylot/jadx/wiki/JADX-GUI-Key-bindings)
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview) See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
@@ -39,7 +42,7 @@ After download unpack zip file go to `bin` directory and run:
On Windows run `.bat` files with double-click\ On Windows run `.bat` files with double-click\
**Note:** ensure you have installed Java 11 or later 64-bit version. **Note:** ensure you have installed Java 11 or later 64-bit version.
For windows you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer). For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
### Install ### Install
1. Arch linux 1. Arch linux
@@ -51,6 +54,9 @@ For windows you can download it from [oracle.com](https://www.oracle.com/java/te
brew install jadx brew install jadx
``` ```
### Use jadx as a library
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
### Build from source ### Build from source
JDK 8 or higher must be installed: JDK 8 or higher must be installed:
``` ```
@@ -73,7 +79,8 @@ options:
-dr, --output-dir-res - output directory for resources -dr, --output-dir-res - output directory for resources
-r, --no-res - do not decode resources -r, --no-res - do not decode resources
-s, --no-src - do not decompile source code -s, --no-src - do not decompile source code
--single-class - decompile a single class --single-class - decompile a single class, full name, raw or alias
--single-class-output - file or dir for write if decompile a single class
--output-format - can be 'java' or 'json', default: java --output-format - can be 'java' or 'json', default: java
-e, --export-gradle - save as android gradle project -e, --export-gradle - save as android gradle project
-j, --threads-count - processing threads count, default: 4 -j, --threads-count - processing threads count, default: 4
@@ -90,9 +97,15 @@ options:
--deobf-min - min length of name, renamed if shorter, default: 3 --deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64 --deobf-max - max length of name, renamed if longer, default: 64
--deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension --deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension
--deobf-rewrite-cfg - force to ignore and overwrite deobfuscation map file --deobf-cfg-file-mode - set mode for handle deobfuscation map file:
'read' - read if found, don't save (default)
'read-or-save' - read if found, save otherwise (don't overwrite)
'overwrite' - don't read, always save
'ignore' - don't read and don't save
--deobf-rewrite-cfg - set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)
--deobf-use-sourcename - use source file name as class name alias --deobf-use-sourcename - use source file name as class name alias
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names --deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
--rename-flags - fix options (comma-separated list of): --rename-flags - fix options (comma-separated list of):
'case' - fix case sensitivity issues (according to --fs-case-sensitive option), 'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
'valid' - rename java identifiers to make them valid, 'valid' - rename java identifiers to make them valid,
@@ -104,7 +117,7 @@ options:
--raw-cfg - save methods control flow graph (use raw instructions) --raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc) -f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
--use-dx - use dx/d8 to convert java bytecode --use-dx - use dx/d8 to convert java bytecode
--comments-level - set code comments level, values: none, user_only, error, warn, info, debug, default: info --comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress --log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG) -v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET) -q, --quiet - turn off output (set --log-level to QUIET)
+7
View File
@@ -0,0 +1,7 @@
# Security Policy
## Reporting a Vulnerability
To report a security issue, please email `skylot@gmail.com` with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
We will check and respond within 3 working days. If the issue is confirmed as a vulnerability, we will apply required mitigations at the next release.
This project follows a 90 day disclosure timeline.
+9 -8
View File
@@ -1,6 +1,6 @@
plugins { plugins {
id 'com.github.ben-manes.versions' version '0.39.0' id 'com.github.ben-manes.versions' version '0.42.0'
id 'com.diffplug.spotless' version '6.0.2' id 'com.diffplug.spotless' version '6.2.2'
} }
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev" ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -27,13 +27,13 @@ allprojects {
} }
dependencies { dependencies {
implementation 'org.slf4j:slf4j-api:1.7.32' implementation 'org.slf4j:slf4j-api:1.7.36'
compileOnly 'org.jetbrains:annotations:23.0.0' compileOnly 'org.jetbrains:annotations:23.0.0'
testImplementation 'ch.qos.logback:logback-classic:1.2.8' testImplementation 'ch.qos.logback:logback-classic:1.2.10'
testImplementation 'org.hamcrest:hamcrest-library:2.2' testImplementation 'org.hamcrest:hamcrest-library:2.2'
testImplementation 'org.mockito:mockito-core:4.1.0' testImplementation 'org.mockito:mockito-core:4.3.1'
testImplementation 'org.assertj:assertj-core:3.21.0' testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
@@ -67,8 +67,9 @@ spotless {
if (JavaVersion.current() < JavaVersion.VERSION_16) { if (JavaVersion.current() < JavaVersion.VERSION_16) {
removeUnusedImports() removeUnusedImports()
} else { } else {
// google-format broken on java 16 (https://github.com/diffplug/spotless/issues/834) // google-format on Java 16+ issue: https://github.com/diffplug/spotless/issues/834
println('Warning! Unused imports remove is disabled for Java 16') println('Warning! Unused imports remove is disabled for Java 16+'
+ ' (use workaround from https://github.com/diffplug/spotless/tree/main/plugin-gradle#google-java-format)')
} }
lineEndings(com.diffplug.spotless.LineEnding.UNIX) lineEndings(com.diffplug.spotless.LineEnding.UNIX)
@@ -73,4 +73,6 @@ javadoc {
if (JavaVersion.current().isJava9Compatible()) { if (JavaVersion.current().isJava9Compatible()) {
options.addBooleanOption('html5', true) options.addBooleanOption('html5', true)
} }
// disable 'missing' warnings
options.addStringOption('Xdoclint:all,-missing', '-quiet')
} }
Binary file not shown.
+2 -2
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=9afb3ca688fc12c761a0e9e4321e4d24e977a4a8916c8a768b1fe05ddb4d6b66 distributionSha256Sum=8cc27038d5dbd815759851ba53e70cf62e481b87494cc97cfd97982ada5ba634
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
+2 -2
View File
@@ -10,8 +10,8 @@ dependencies {
runtimeOnly(project(':jadx-plugins:jadx-java-convert')) runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
runtimeOnly(project(':jadx-plugins:jadx-smali-input')) runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
implementation 'com.beust:jcommander:1.81' implementation 'com.beust:jcommander:1.82'
implementation 'ch.qos.logback:logback-classic:1.2.8' implementation 'ch.qos.logback:logback-classic:1.2.10'
} }
application { application {
@@ -94,7 +94,7 @@ public class JCommanderWrapper<T> {
opt.append("- ").append(description); opt.append("- ").append(description);
} }
String defaultValue = getDefaultValue(args, f, opt); String defaultValue = getDefaultValue(args, f, opt);
if (defaultValue != null) { if (defaultValue != null && !description.contains("(default)")) {
opt.append(", default: ").append(defaultValue); opt.append(", default: ").append(defaultValue);
} }
out.println(opt); out.println(opt);
+16 -9
View File
@@ -32,23 +32,19 @@ public class JadxCLI {
public static int execute(String[] args) { public static int execute(String[] args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs(); JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) { if (jadxArgs.processArgs(args)) {
return processAndSave(jadxArgs.toJadxArgs()); return processAndSave(jadxArgs);
} }
return 0; return 0;
} }
private static int processAndSave(JadxArgs jadxArgs) { private static int processAndSave(JadxCLIArgs cliArgs) {
JadxArgs jadxArgs = cliArgs.toJadxArgs();
jadxArgs.setCodeCache(new NoOpCodeCache()); jadxArgs.setCodeCache(new NoOpCodeCache());
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new); jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) { try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load(); jadx.load();
if (LogHelper.getLogLevel() == LogHelper.LogLevelEnum.QUIET) { if (!SingleClassMode.process(jadx, cliArgs)) {
jadx.save(); save(jadx);
} else {
jadx.save(500, (done, total) -> {
int progress = (int) (done * 100.0 / total);
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
});
} }
int errorsCount = jadx.getErrorsCount(); int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) { if (errorsCount != 0) {
@@ -60,4 +56,15 @@ public class JadxCLI {
} }
return 0; return 0;
} }
private static void save(JadxDecompiler jadx) {
if (LogHelper.getLogLevel() == LogHelper.LogLevelEnum.QUIET) {
jadx.save();
} else {
jadx.save(500, (done, total) -> {
int progress = (int) (done * 100.0 / total);
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
});
}
}
} }
@@ -14,7 +14,9 @@ import com.beust.jcommander.Parameter;
import jadx.api.CommentsLevel; import jadx.api.CommentsLevel;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RenameEnum; import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
@@ -38,9 +40,12 @@ public class JadxCLIArgs {
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code") @Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
protected boolean skipSources = false; protected boolean skipSources = false;
@Parameter(names = { "--single-class" }, description = "decompile a single class") @Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
protected String singleClass = null; protected String singleClass = null;
@Parameter(names = { "--single-class-output" }, description = "file or dir for write if decompile a single class")
protected String singleClassOutput = null;
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'") @Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
protected String outputFormat = "java"; protected String outputFormat = "java";
@@ -92,7 +97,18 @@ public class JadxCLIArgs {
) )
protected String deobfuscationMapFile; protected String deobfuscationMapFile;
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "force to ignore and overwrite deobfuscation map file") @Parameter(
names = { "--deobf-cfg-file-mode" },
description = "set mode for handle deobfuscation map file:"
+ "\n 'read' - read if found, don't save (default)"
+ "\n 'read-or-save' - read if found, save otherwise (don't overwrite)"
+ "\n 'overwrite' - don't read, always save"
+ "\n 'ignore' - don't read and don't save",
converter = DeobfuscationMapFileModeConverter.class
)
protected DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)")
protected boolean deobfuscationForceSave = false; protected boolean deobfuscationForceSave = false;
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias") @Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
@@ -101,6 +117,13 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names") @Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
protected boolean deobfuscationParseKotlinMetadata = false; protected boolean deobfuscationParseKotlinMetadata = false;
@Parameter(
names = { "--use-kotlin-methods-for-var-names" },
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
converter = UseKotlinMethodsForVarNamesConverter.class
)
protected UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
@Parameter( @Parameter(
names = { "--rename-flags" }, names = { "--rename-flags" },
description = "fix options (comma-separated list of):" description = "fix options (comma-separated list of):"
@@ -130,7 +153,7 @@ public class JadxCLIArgs {
@Parameter( @Parameter(
names = { "--comments-level" }, names = { "--comments-level" },
description = "set code comments level, values: error, warn, info, debug, user_only, none", description = "set code comments level, values: error, warn, info, debug, user-only, none",
converter = CommentsLevelConverter.class converter = CommentsLevelConverter.class
) )
protected CommentsLevel commentsLevel = CommentsLevel.INFO; protected CommentsLevel commentsLevel = CommentsLevel.INFO;
@@ -207,9 +230,6 @@ public class JadxCLIArgs {
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase())); args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
args.setThreadsCount(threadsCount); args.setThreadsCount(threadsCount);
args.setSkipSources(skipSources); args.setSkipSources(skipSources);
if (singleClass != null) {
args.setClassFilter(className -> singleClass.equals(className));
}
args.setSkipResources(skipResources); args.setSkipResources(skipResources);
args.setFallbackMode(fallbackMode); args.setFallbackMode(fallbackMode);
args.setShowInconsistentCode(showInconsistentCode); args.setShowInconsistentCode(showInconsistentCode);
@@ -218,11 +238,16 @@ public class JadxCLIArgs {
args.setReplaceConsts(replaceConsts); args.setReplaceConsts(replaceConsts);
args.setDeobfuscationOn(deobfuscationOn); args.setDeobfuscationOn(deobfuscationOn);
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile)); args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
args.setDeobfuscationForceSave(deobfuscationForceSave); if (deobfuscationForceSave) {
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
} else {
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
}
args.setDeobfuscationMinLength(deobfuscationMinLength); args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength); args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias); args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata); args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
args.setEscapeUnicode(escapeUnicode); args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers); args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject); args.setExportAsGradleProject(exportAsGradleProject);
@@ -254,6 +279,14 @@ public class JadxCLIArgs {
return outDirRes; return outDirRes;
} }
public String getSingleClass() {
return singleClass;
}
public String getSingleClassOutput() {
return singleClassOutput;
}
public boolean isSkipResources() { public boolean isSkipResources() {
return skipResources; return skipResources;
} }
@@ -314,6 +347,10 @@ public class JadxCLIArgs {
return deobfuscationMapFile; return deobfuscationMapFile;
} }
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
return deobfuscationMapFileMode;
}
public boolean isDeobfuscationForceSave() { public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave; return deobfuscationForceSave;
} }
@@ -326,6 +363,10 @@ public class JadxCLIArgs {
return deobfuscationParseKotlinMetadata; return deobfuscationParseKotlinMetadata;
} }
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public boolean isEscapeUnicode() { public boolean isEscapeUnicode() {
return escapeUnicode; return escapeUnicode;
} }
@@ -412,9 +453,35 @@ public class JadxCLIArgs {
} }
} }
public static class UseKotlinMethodsForVarNamesConverter implements IStringConverter<UseKotlinMethodsForVarNames> {
@Override
public UseKotlinMethodsForVarNames convert(String value) {
try {
return UseKotlinMethodsForVarNames.valueOf(value.replace('-', '_').toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: "
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
}
}
}
public static class DeobfuscationMapFileModeConverter implements IStringConverter<DeobfuscationMapFileMode> {
@Override
public DeobfuscationMapFileMode convert(String value) {
try {
return DeobfuscationMapFileMode.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: "
+ JadxCLIArgs.enumValuesString(DeobfuscationMapFileMode.values()));
}
}
}
public static String enumValuesString(Enum<?>[] values) { public static String enumValuesString(Enum<?>[] values) {
return Stream.of(values) return Stream.of(values)
.map(v -> v.name().toLowerCase(Locale.ROOT)) .map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
.collect(Collectors.joining(", ")); .collect(Collectors.joining(", "));
} }
} }
@@ -54,10 +54,11 @@ public class LogHelper {
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(logLevel.getLevel()); rootLogger.setLevel(logLevel.getLevel());
if (logLevel != LogLevelEnum.QUIET) { if (logLevel == LogLevelEnum.PROGRESS) {
// show progress for all levels except quiet // show progress for all levels except quiet
setLevelForClass(JadxCLI.class, Level.INFO); setLevelForClass(JadxCLI.class, Level.INFO);
setLevelForClass(JadxDecompiler.class, Level.INFO); setLevelForClass(JadxDecompiler.class, Level.INFO);
setLevelForClass(SingleClassMode.class, Level.INFO);
} }
} }
@@ -0,0 +1,87 @@
package jadx.cli;
import java.io.File;
import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.api.JadxDecompiler;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
public class SingleClassMode {
private static final Logger LOG = LoggerFactory.getLogger(SingleClassMode.class);
public static boolean process(JadxDecompiler jadx, JadxCLIArgs cliArgs) {
String singleClass = cliArgs.getSingleClass();
String singleClassOutput = cliArgs.getSingleClassOutput();
if (singleClass == null && singleClassOutput == null) {
return false;
}
ClassNode clsForProcess;
if (singleClass != null) {
clsForProcess = jadx.getRoot().resolveClass(singleClass);
if (clsForProcess == null) {
clsForProcess = jadx.getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(singleClass))
.findFirst().orElse(null);
}
if (clsForProcess == null) {
throw new JadxRuntimeException("Input class not found: " + singleClass);
}
if (clsForProcess.contains(AFlag.DONT_GENERATE)) {
throw new JadxRuntimeException("Input class can't be saved by currect jadx settings (marked as DONT_GENERATE)");
}
if (clsForProcess.isInner()) {
clsForProcess = clsForProcess.getTopParentClass();
LOG.warn("Input class is inner, parent class will be saved: {}", clsForProcess.getFullName());
}
} else {
// singleClassOutput is set
// expect only one class to be loaded
List<ClassNode> classes = jadx.getRoot().getClasses().stream()
.filter(c -> !c.isInner() && !c.contains(AFlag.DONT_GENERATE))
.collect(Collectors.toList());
int size = classes.size();
if (size == 1) {
clsForProcess = classes.get(0);
} else {
throw new JadxRuntimeException("Found " + size + " classes, single class output can't be used");
}
}
ICodeInfo codeInfo;
try {
codeInfo = clsForProcess.decompile();
} catch (Exception e) {
throw new JadxRuntimeException("Class decompilation failed", e);
}
String fileExt = SaveCode.getFileExtension(jadx.getRoot());
File out;
if (singleClassOutput == null) {
out = new File(jadx.getArgs().getOutDirSrc(), clsForProcess.getClassInfo().getAliasFullPath() + fileExt);
} else {
if (singleClassOutput.endsWith(fileExt)) {
// treat as file name
out = new File(singleClassOutput);
} else {
// treat as directory
out = new File(singleClassOutput, clsForProcess.getShortName() + fileExt);
}
}
File resultOut = FileUtils.prepareFile(out);
if (clsForProcess.getClassInfo().hasAlias()) {
LOG.info("Saving class '{}' (alias: '{}') to file '{}'",
clsForProcess.getClassInfo().getFullName(), clsForProcess.getFullName(), resultOut.getAbsolutePath());
} else {
LOG.info("Saving class '{}' to file '{}'", clsForProcess.getFullName(), resultOut.getAbsolutePath());
}
SaveCode.save(codeInfo.getCodeStr(), resultOut);
return true;
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ plugins {
dependencies { dependencies {
api(project(':jadx-plugins:jadx-plugins-api')) api(project(':jadx-plugins:jadx-plugins-api'))
implementation 'com.google.code.gson:gson:2.8.9' implementation 'com.google.code.gson:gson:2.9.0'
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631' implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
constraints { constraints {
// Force protobuf version to prevent Java-7 issue // Force protobuf version to prevent Java-7 issue
+47 -5
View File
@@ -9,6 +9,7 @@ import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.data.ICodeData; import jadx.api.data.ICodeData;
import jadx.api.impl.AnnotatedCodeWriter; import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache; import jadx.api.impl.InMemoryCodeCache;
@@ -54,11 +55,12 @@ public class JadxArgs {
private Predicate<String> classFilter = null; private Predicate<String> classFilter = null;
private boolean deobfuscationOn = false; private boolean deobfuscationOn = false;
private boolean deobfuscationForceSave = false;
private boolean useSourceNameAsClassAlias = false; private boolean useSourceNameAsClassAlias = false;
private boolean parseKotlinMetadata = false; private boolean parseKotlinMetadata = false;
private File deobfuscationMapFile = null; private File deobfuscationMapFile = null;
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
private int deobfuscationMinLength = 0; private int deobfuscationMinLength = 0;
private int deobfuscationMaxLength = Integer.MAX_VALUE; private int deobfuscationMaxLength = Integer.MAX_VALUE;
@@ -87,6 +89,17 @@ public class JadxArgs {
private boolean useDxInput = false; private boolean useDxInput = false;
public enum UseKotlinMethodsForVarNames {
DISABLE, APPLY, APPLY_AND_HIDE
}
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
/**
* Don't save files (can be using for performance testing)
*/
private boolean skipFilesSave = false;
public JadxArgs() { public JadxArgs() {
// use default options // use default options
} }
@@ -138,7 +151,7 @@ public class JadxArgs {
} }
public void setThreadsCount(int threadsCount) { public void setThreadsCount(int threadsCount) {
this.threadsCount = threadsCount; this.threadsCount = Math.max(1, threadsCount); // make sure threadsCount >= 1
} }
public boolean isCfgOutput() { public boolean isCfgOutput() {
@@ -253,12 +266,24 @@ public class JadxArgs {
this.deobfuscationOn = deobfuscationOn; this.deobfuscationOn = deobfuscationOn;
} }
@Deprecated
public boolean isDeobfuscationForceSave() { public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave; return deobfuscationMapFileMode == DeobfuscationMapFileMode.OVERWRITE;
} }
@Deprecated
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) { public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
this.deobfuscationForceSave = deobfuscationForceSave; if (deobfuscationForceSave) {
this.deobfuscationMapFileMode = DeobfuscationMapFileMode.OVERWRITE;
}
}
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
return deobfuscationMapFileMode;
}
public void setDeobfuscationMapFileMode(DeobfuscationMapFileMode deobfuscationMapFileMode) {
this.deobfuscationMapFileMode = deobfuscationMapFileMode;
} }
public boolean isUseSourceNameAsClassAlias() { public boolean isUseSourceNameAsClassAlias() {
@@ -433,6 +458,22 @@ public class JadxArgs {
this.useDxInput = useDxInput; this.useDxInput = useDxInput;
} }
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public void setUseKotlinMethodsForVarNames(UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
}
public boolean isSkipFilesSave() {
return skipFilesSave;
}
public void setSkipFilesSave(boolean skipFilesSave) {
this.skipFilesSave = skipFilesSave;
}
@Override @Override
public String toString() { public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -449,9 +490,10 @@ public class JadxArgs {
+ ", skipSources=" + skipSources + ", skipSources=" + skipSources
+ ", deobfuscationOn=" + deobfuscationOn + ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationMapFile=" + deobfuscationMapFile + ", deobfuscationMapFile=" + deobfuscationMapFile
+ ", deobfuscationForceSave=" + deobfuscationForceSave + ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias + ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata + ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
+ ", deobfuscationMinLength=" + deobfuscationMinLength + ", deobfuscationMinLength=" + deobfuscationMinLength
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength + ", deobfuscationMaxLength=" + deobfuscationMaxLength
+ ", escapeUnicode=" + escapeUnicode + ", escapeUnicode=" + escapeUnicode
@@ -128,6 +128,7 @@ public final class JadxDecompiler implements Closeable {
loadedInputs.add(loadResult); loadedInputs.add(loadResult);
} }
} }
LOG.debug("Loaded using {} inputs plugin", loadedInputs.size());
} }
private void reset() { private void reset() {
@@ -282,6 +283,9 @@ public final class JadxDecompiler implements Closeable {
} }
private void appendResourcesSaveTasks(List<Runnable> tasks, File outDir) { private void appendResourcesSaveTasks(List<Runnable> tasks, File outDir) {
if (args.isSkipFilesSave()) {
return;
}
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet()); Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
for (ResourceFile resourceFile : getResources()) { for (ResourceFile resourceFile : getResources()) {
if (resourceFile.getType() != ResourceType.ARSC if (resourceFile.getType() != ResourceType.ARSC
@@ -306,7 +310,13 @@ public final class JadxDecompiler implements Closeable {
} }
processQueue.add(cls); processQueue.add(cls);
} }
for (List<JavaClass> decompileBatch : decompileScheduler.buildBatches(processQueue)) { List<List<JavaClass>> batches;
try {
batches = decompileScheduler.buildBatches(processQueue);
} catch (Exception e) {
throw new JadxRuntimeException("Decompilation batches build failed", e);
}
for (List<JavaClass> decompileBatch : batches) {
tasks.add(() -> { tasks.add(() -> {
for (JavaClass cls : decompileBatch) { for (JavaClass cls : decompileBatch) {
try { try {
+10 -10
View File
@@ -11,6 +11,8 @@ import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
@@ -69,6 +71,10 @@ public final class JavaClass implements JavaNode {
cls.unloadCode(); cls.unloadCode();
} }
public boolean isNoCode() {
return cls.contains(AFlag.DONT_GENERATE);
}
public synchronized String getSmali() { public synchronized String getSmali() {
return cls.getDisassembledCode(); return cls.getDisassembledCode();
} }
@@ -237,7 +243,7 @@ public final class JavaClass implements JavaNode {
@Override @Override
public JavaClass getTopParentClass() { public JavaClass getTopParentClass() {
if (cls.contains(AFlag.ANONYMOUS_CLASS)) { if (cls.contains(AType.ANONYMOUS_CLASS)) {
// moved to usage class // moved to usage class
return getParentForAnonymousClass(); return getParentForAnonymousClass();
} }
@@ -245,15 +251,9 @@ public final class JavaClass implements JavaNode {
} }
private JavaClass getParentForAnonymousClass() { private JavaClass getParentForAnonymousClass() {
List<JavaNode> useIn = getUseIn(); AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
if (useIn.isEmpty()) { ClassNode topParentClass = attr.getOuterCls().getTopParentClass();
return this; return getRootDecompiler().convertClassNode(topParentClass);
}
JavaNode useNode = useIn.get(0);
if (useNode.equals(this)) {
return this;
}
return useNode.getTopParentClass();
} }
public AccessInfo getAccessInfo() { public AccessInfo getAccessInfo() {
@@ -28,6 +28,10 @@ public final class JavaField implements JavaNode {
return parent.getFullName() + '.' + getName(); return parent.getFullName() + '.' + getName();
} }
public String getRawName() {
return field.getName();
}
@Override @Override
public JavaClass getDeclaringClass() { public JavaClass getDeclaringClass() {
return parent; return parent;
@@ -37,6 +37,10 @@ public class ResourceFile {
private ZipRef zipRef; private ZipRef zipRef;
private String deobfName; private String deobfName;
public static ResourceFile createResourceFile(JadxDecompiler decompiler, File file, ResourceType type) {
return new ResourceFile(decompiler, file.getAbsolutePath(), type);
}
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) { public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) { if (!ZipSecurity.isValidZipEntryName(name)) {
return null; return null;
@@ -41,7 +41,7 @@ public enum ResourceType {
} }
public static ResourceType getFileType(String fileName) { public static ResourceType getFileType(String fileName) {
if (fileName.matches("[^/]+/resources.pb")) { if (fileName.endsWith("/resources.pb")) {
return ARSC; return ARSC;
} }
int dot = fileName.lastIndexOf('.'); int dot = fileName.lastIndexOf('.');
@@ -126,8 +126,9 @@ public final class ResourcesLoader {
if (name.endsWith(".9.png")) { if (name.endsWith(".9.png")) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder(); Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
decoder.decode(inputStream, os); if (decoder.decode(inputStream, os)) {
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray()); return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
}
} catch (Exception e) { } catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e); LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
} }
@@ -145,16 +146,8 @@ public final class ResourcesLoader {
return null; return null;
}); });
} else { } else {
addResourceFile(list, file); ResourceType type = ResourceType.getFileType(file.getAbsolutePath());
} list.add(ResourceFile.createResourceFile(jadxRef, file, type));
}
private void addResourceFile(List<ResourceFile> list, File file) {
String name = file.getAbsolutePath();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
list.add(rf);
} }
} }
@@ -0,0 +1,32 @@
package jadx.api.args;
public enum DeobfuscationMapFileMode {
/**
* Load if found, don't save (default)
*/
READ,
/**
* Load if found, save only if new (don't overwrite)
*/
READ_OR_SAVE,
/**
* Don't load, always save
*/
OVERWRITE,
/**
* Don't load and don't save
*/
IGNORE;
public boolean shouldRead() {
return this == READ || this == READ_OR_SAVE;
}
public boolean shouldWrite() {
return this == READ_OR_SAVE || this == OVERWRITE;
}
}
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel; import jadx.api.CommentsLevel;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.core.dex.visitors.AnonymousClassVisitor;
import jadx.core.dex.visitors.AttachCommentsVisitor; import jadx.core.dex.visitors.AttachCommentsVisitor;
import jadx.core.dex.visitors.AttachMethodDetails; import jadx.core.dex.visitors.AttachMethodDetails;
import jadx.core.dex.visitors.AttachTryCatchVisitor; import jadx.core.dex.visitors.AttachTryCatchVisitor;
@@ -37,6 +38,7 @@ import jadx.core.dex.visitors.OverrideMethodVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen; import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ProcessAnonymous; import jadx.core.dex.visitors.ProcessAnonymous;
import jadx.core.dex.visitors.ProcessInstructionsVisitor; import jadx.core.dex.visitors.ProcessInstructionsVisitor;
import jadx.core.dex.visitors.ProcessMethodsForInline;
import jadx.core.dex.visitors.ReSugarCode; import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.ShadowFieldVisitor; import jadx.core.dex.visitors.ShadowFieldVisitor;
import jadx.core.dex.visitors.SignatureProcessor; import jadx.core.dex.visitors.SignatureProcessor;
@@ -46,6 +48,7 @@ import jadx.core.dex.visitors.blocks.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor; 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.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;
@@ -88,6 +91,7 @@ public class Jadx {
passes.add(new RenameVisitor()); passes.add(new RenameVisitor());
passes.add(new UsageInfoVisitor()); passes.add(new UsageInfoVisitor());
passes.add(new ProcessAnonymous()); passes.add(new ProcessAnonymous());
passes.add(new ProcessMethodsForInline());
return passes; return passes;
} }
@@ -128,6 +132,9 @@ public class Jadx {
if (args.isDebugInfo()) { if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor()); passes.add(new DebugInfoApplyVisitor());
} }
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
passes.add(new ProcessKotlinInternals());
}
passes.add(new CodeRenameVisitor()); passes.add(new CodeRenameVisitor());
if (args.isInlineMethods()) { if (args.isInlineMethods()) {
passes.add(new InlineMethods()); passes.add(new InlineMethods());
@@ -135,6 +142,7 @@ public class Jadx {
passes.add(new GenericTypesVisitor()); passes.add(new GenericTypesVisitor());
passes.add(new ShadowFieldVisitor()); passes.add(new ShadowFieldVisitor());
passes.add(new DeboxingVisitor()); passes.add(new DeboxingVisitor());
passes.add(new AnonymousClassVisitor());
passes.add(new ModVisitor()); passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor()); passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode()); passes.add(new ReSugarCode());
@@ -1,10 +1,5 @@
package jadx.core; package jadx.core;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -39,17 +34,17 @@ public final class ProcessClass {
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) { if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
cls.remove(AFlag.CLASS_DEEP_RELOAD); cls.remove(AFlag.CLASS_DEEP_RELOAD);
cls.deepUnload(); cls.deepUnload();
cls.root().runPreDecompileStageForClass(cls); cls.add(AFlag.CLASS_UNLOADED);
} }
if (cls.contains(AFlag.CLASS_UNLOADED)) { if (cls.contains(AFlag.CLASS_UNLOADED)) {
cls.remove(AFlag.CLASS_UNLOADED);
cls.root().runPreDecompileStageForClass(cls); cls.root().runPreDecompileStageForClass(cls);
cls.remove(AFlag.CLASS_UNLOADED);
}
if (cls.getState() == GENERATED_AND_UNLOADED) {
// force loading code again
cls.setState(NOT_LOADED);
} }
if (codegen) { if (codegen) {
if (cls.getState() == GENERATED_AND_UNLOADED) {
// allow to run code generation again
cls.setState(NOT_LOADED);
}
cls.setLoadStage(LoadStage.CODEGEN_STAGE); cls.setLoadStage(LoadStage.CODEGEN_STAGE);
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) { if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE); cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
@@ -94,21 +89,13 @@ public final class ProcessClass {
return generateCode(topParentClass); return generateCode(topParentClass);
} }
try { try {
Set<ClassNode> useIn = new HashSet<>(cls.getUseIn());
List<ClassNode> usedInDeps = new ArrayList<>();
for (ClassNode depCls : cls.getDependencies()) { for (ClassNode depCls : cls.getDependencies()) {
if (useIn.contains(depCls)) { process(depCls, false);
// postpone to resolve cross dependencies
usedInDeps.add(depCls);
} else {
process(depCls, false);
}
} }
if (!usedInDeps.isEmpty()) { if (!cls.getCodegenDeps().isEmpty()) {
// process current class before its usage
process(cls, false); process(cls, false);
for (ClassNode depCls : usedInDeps) { for (ClassNode codegenDep : cls.getCodegenDeps()) {
process(depCls, false); process(codegenDep, false);
} }
} }
ICodeInfo code = process(cls, true); ICodeInfo code = process(cls, true);
@@ -285,7 +285,7 @@ public class ClassGen {
private boolean isInnerClassesPresents() { private boolean isInnerClassesPresents() {
for (ClassNode innerCls : cls.getInnerClasses()) { for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) { if (!innerCls.contains(AType.ANONYMOUS_CLASS)) {
return true; return true;
} }
} }
@@ -459,7 +459,7 @@ public class ClassGen {
} }
if (f.getCls() != null) { if (f.getCls() != null) {
code.add(' '); code.add(' ');
new ClassGen(f.getCls(), this).addClassBody(code); new ClassGen(f.getCls(), this).addClassBody(code, true);
} }
if (it.hasNext()) { if (it.hasNext()) {
code.add(','); code.add(',');
@@ -526,12 +526,42 @@ public class ClassGen {
if (outerType != null) { if (outerType != null) {
useClass(code, outerType); useClass(code, outerType);
code.add('.'); code.add('.');
// import not needed, force use short name addInnerType(code, type);
useClassShortName(code, type.getObject());
return; return;
} }
useClass(code, ClassInfo.fromType(cls.root(), type)); useClass(code, ClassInfo.fromType(cls.root(), type));
addGenerics(code, type);
}
private void addInnerType(ICodeWriter code, ArgType baseType) {
ArgType innerType = baseType.getInnerType();
ArgType outerType = innerType.getOuterType();
if (outerType != null) {
useClassWithShortName(code, baseType, outerType);
code.add('.');
addInnerType(code, innerType);
return;
}
useClassWithShortName(code, baseType, innerType);
}
private void useClassWithShortName(ICodeWriter code, ArgType baseType, ArgType type) {
String fullNameObj;
if (type.getObject().contains(".")) {
fullNameObj = type.getObject();
} else {
fullNameObj = baseType.getObject();
}
ClassInfo classInfo = ClassInfo.fromName(cls.root(), fullNameObj);
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
code.add(classInfo.getAliasShortName());
addGenerics(code, type);
}
private void addGenerics(ICodeWriter code, ArgType type) {
List<ArgType> generics = type.getGenericTypes(); List<ArgType> generics = type.getGenericTypes();
if (generics != null) { if (generics != null) {
code.add('<'); code.add('<');
@@ -556,15 +586,6 @@ public class ClassGen {
} }
} }
private void useClassShortName(ICodeWriter code, String object) {
ClassInfo classInfo = ClassInfo.fromName(cls.root(), object);
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
code.add(classInfo.getAliasShortName());
}
public void useClass(ICodeWriter code, ClassInfo classInfo) { public void useClass(ICodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.root().resolveClass(classInfo); ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) { if (classNode != null) {
@@ -15,7 +15,6 @@ import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef; import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.data.annotations.VarRef; import jadx.api.data.annotations.VarRef;
import jadx.api.plugins.input.data.MethodHandleType; import jadx.api.plugins.input.data.MethodHandleType;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
@@ -112,7 +111,7 @@ public class InsnGen {
} }
code.add(mgen.getNameGen().useArg(reg)); code.add(mgen.getNameGen().useArg(reg));
} else if (arg.isLiteral()) { } else if (arg.isLiteral()) {
code.add(lit((LiteralArg) arg)); addLiteralArg(code, (LiteralArg) arg, flags);
} else if (arg.isInsnWrap()) { } else if (arg.isInsnWrap()) {
addWrappedArg(code, (InsnWrapArg) arg, flags); addWrappedArg(code, (InsnWrapArg) arg, flags);
} else if (arg.isNamed()) { } else if (arg.isNamed()) {
@@ -122,6 +121,15 @@ public class InsnGen {
} }
} }
private void addLiteralArg(ICodeWriter code, LiteralArg litArg, Set<Flags> flags) {
String literalStr = lit(litArg);
if (!flags.contains(Flags.BODY_ONLY_NOWRAP) && literalStr.startsWith("-")) {
code.add('(').add(literalStr).add(')');
} else {
code.add(literalStr);
}
}
private void addWrappedArg(ICodeWriter code, InsnWrapArg arg, Set<Flags> flags) throws CodegenException { private void addWrappedArg(ICodeWriter code, InsnWrapArg arg, Set<Flags> flags) throws CodegenException {
InsnNode wrapInsn = arg.getWrapInsn(); InsnNode wrapInsn = arg.getWrapInsn();
if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) { if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
@@ -164,7 +172,7 @@ public class InsnGen {
private void instanceField(ICodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException { private void instanceField(ICodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
ClassNode pCls = mth.getParentClass(); ClassNode pCls = mth.getParentClass();
FieldNode fieldNode = pCls.root().deepResolveField(field); FieldNode fieldNode = pCls.root().resolveField(field);
if (fieldNode != null) { if (fieldNode != null) {
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE); FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
if (replace != null) { if (replace != null) {
@@ -202,7 +210,7 @@ public class InsnGen {
} }
code.add('.'); code.add('.');
} }
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field); FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
if (fieldNode != null) { if (fieldNode != null) {
code.attachAnnotation(fieldNode); code.attachAnnotation(fieldNode);
} }
@@ -711,20 +719,13 @@ public class InsnGen {
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException { private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
if (this.mth.getParentClass() == cls) { if (this.mth.getParentClass() == cls) {
cls.remove(AFlag.ANONYMOUS_CLASS); cls.remove(AType.ANONYMOUS_CLASS);
cls.remove(AFlag.DONT_GENERATE); cls.remove(AFlag.DONT_GENERATE);
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN); mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
throw new CodegenException("Anonymous inner class unlimited recursion detected." throw new CodegenException("Anonymous inner class unlimited recursion detected."
+ " Convert class to inner: " + cls.getClassInfo().getFullName()); + " Convert class to inner: " + cls.getClassInfo().getFullName());
} }
ArgType parent = cls.get(AType.ANONYMOUS_CLASS).getBaseType();
cls.add(AFlag.DONT_GENERATE);
ArgType parent;
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
} else {
parent = cls.getSuperClass();
}
// hide empty anonymous constructors // hide empty anonymous constructors
for (MethodNode ctor : cls.getMethods()) { for (MethodNode ctor : cls.getMethods()) {
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR) if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
@@ -732,14 +733,22 @@ public class InsnGen {
ctor.add(AFlag.DONT_GENERATE); ctor.add(AFlag.DONT_GENERATE);
} }
} }
code.add("new "); code.add("new ");
if (parent == null) { useClass(code, parent);
code.add("Object");
} else {
useClass(code, parent);
}
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth()); MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
if (callMth != null) {
// copy var names
List<RegisterArg> mthArgs = callMth.getArgRegs();
int argsCount = Math.min(insn.getArgsCount(), mthArgs.size());
for (int i = 0; i < argsCount; i++) {
InsnArg arg = insn.getArg(i);
if (arg.isRegister()) {
RegisterArg mthArg = mthArgs.get(i);
RegisterArg insnArg = (RegisterArg) arg;
mthArg.getSVar().setCodeVar(insnArg.getSVar().getCodeVar());
}
}
}
generateMethodArguments(code, insn, 0, callMth); generateMethodArguments(code, insn, 0, callMth);
code.add(' '); code.add(' ');
@@ -755,7 +764,7 @@ public class InsnGen {
return; return;
} }
MethodInfo callMth = insn.getCallMth(); MethodInfo callMth = insn.getCallMth();
MethodNode callMthNode = mth.root().deepResolveMethod(callMth); MethodNode callMthNode = mth.root().resolveMethod(callMth);
int k = 0; int k = 0;
switch (type) { switch (type) {
@@ -1029,7 +1038,6 @@ public class InsnGen {
} else { } else {
condGen.wrap(code, insn.getCondition()); condGen.wrap(code, insn.getCondition());
code.add(" ? "); code.add(" ? ");
addCastIfNeeded(code, first, second);
addArg(code, first, false); addArg(code, first, false);
code.add(" : "); code.add(" : ");
addArg(code, second, false); addArg(code, second, false);
@@ -1039,33 +1047,6 @@ public class InsnGen {
} }
} }
private void addCastIfNeeded(ICodeWriter code, InsnArg first, InsnArg second) {
if (first.isLiteral() && second.isLiteral()) {
if (first.getType() == ArgType.BYTE) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (lit1 != Byte.MAX_VALUE && lit1 != Byte.MIN_VALUE
&& lit2 != Byte.MAX_VALUE && lit2 != Byte.MIN_VALUE) {
code.add("(byte) ");
}
} else if (first.getType() == ArgType.SHORT) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (lit1 != Short.MAX_VALUE && lit1 != Short.MIN_VALUE
&& lit2 != Short.MAX_VALUE && lit2 != Short.MIN_VALUE) {
code.add("(short) ");
}
} else if (first.getType() == ArgType.CHAR) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (!NameMapper.isPrintableChar((char) (lit1))
&& !NameMapper.isPrintableChar((char) (lit2))) {
code.add("(char) ");
}
}
}
}
private void makeArith(ArithNode insn, ICodeWriter code, Set<Flags> state) throws CodegenException { private void makeArith(ArithNode insn, ICodeWriter code, Set<Flags> state) throws CodegenException {
if (insn.contains(AFlag.ARITH_ONEARG)) { if (insn.contains(AFlag.ARITH_ONEARG)) {
makeArithOneArg(insn, code); makeArithOneArg(insn, code);
@@ -180,7 +180,7 @@ public class MethodGen {
if (overrideAttr == null) { if (overrideAttr == null) {
return; return;
} }
if (!overrideAttr.isAtBaseMth()) { if (!overrideAttr.getBaseMethods().contains(mth)) {
code.startLine("@Override"); code.startLine("@Override");
if (mth.checkCommentsLevel(CommentsLevel.INFO)) { if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
code.add(" // "); code.add(" // ");
@@ -238,10 +238,11 @@ public class MethodGen {
classGen.useType(code, argType); classGen.useType(code, argType);
} }
code.add(' '); code.add(' ');
if (code.isMetadataSupported() && ssaVar != null) { String varName = nameGen.assignArg(var);
if (code.isMetadataSupported() && ssaVar != null /* for fallback mode */) {
code.attachDefinition(VarDeclareRef.get(mth, var)); code.attachDefinition(VarDeclareRef.get(mth, var));
} }
code.add(nameGen.assignArg(var)); code.add(varName);
i++; i++;
if (it.hasNext()) { if (it.hasNext()) {
@@ -162,8 +162,7 @@ public class NameGen {
InsnNode assignInsn = assignArg.getParentInsn(); InsnNode assignInsn = assignArg.getParentInsn();
if (assignInsn != null) { if (assignInsn != null) {
String name = makeNameFromInsn(assignInsn); String name = makeNameFromInsn(assignInsn);
if (name != null && !NameMapper.isReserved(name)) { if (name != null && NameMapper.isValidAndPrintable(name)) {
assignArg.setName(name);
return name; return name;
} }
} }
@@ -202,7 +201,11 @@ public class NameGen {
return vName; return vName;
} }
if (shortName != null) { if (shortName != null) {
return StringUtils.escape(shortName.toLowerCase()); String lower = StringUtils.escape(shortName.toLowerCase());
if (shortName.equals(lower)) {
return lower + "Var";
}
return lower;
} }
} }
return StringUtils.escape(type.toString()); return StringUtils.escape(type.toString());
@@ -0,0 +1,24 @@
package jadx.core.deobf;
public class ClsAliasPair {
private final String pkg;
private final String name;
public ClsAliasPair(String pkg, String name) {
this.pkg = pkg;
this.name = name;
}
public String getPkg() {
return pkg;
}
public String getName() {
return name;
}
@Override
public String toString() {
return pkg + '.' + name;
}
}
@@ -17,6 +17,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
@@ -50,6 +51,9 @@ public class DeobfPresets {
@Nullable @Nullable
private static Path getPathDeobfMapPath(RootNode root) { private static Path getPathDeobfMapPath(RootNode root) {
JadxArgs jadxArgs = root.getArgs(); JadxArgs jadxArgs = root.getArgs();
if (jadxArgs.getDeobfuscationMapFileMode() == DeobfuscationMapFileMode.IGNORE) {
return null;
}
File deobfMapFile = jadxArgs.getDeobfuscationMapFile(); File deobfMapFile = jadxArgs.getDeobfuscationMapFile();
if (deobfMapFile != null) { if (deobfMapFile != null) {
return deobfMapFile.toPath(); return deobfMapFile.toPath();
@@ -58,7 +62,7 @@ public class DeobfPresets {
if (inputFiles.isEmpty()) { if (inputFiles.isEmpty()) {
return null; return null;
} }
Path inputFilePath = inputFiles.get(0).getAbsoluteFile().toPath(); Path inputFilePath = inputFiles.get(0).toPath().toAbsolutePath();
String baseName = FileUtils.getPathBaseName(inputFilePath); String baseName = FileUtils.getPathBaseName(inputFilePath);
return inputFilePath.getParent().resolve(baseName + ".jobf"); return inputFilePath.getParent().resolve(baseName + ".jobf");
} }
@@ -70,9 +74,9 @@ public class DeobfPresets {
/** /**
* Loads deobfuscator presets * Loads deobfuscator presets
*/ */
public void load() { public boolean load() {
if (!Files.exists(deobfMapFile)) { if (!Files.exists(deobfMapFile)) {
return; return false;
} }
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath()); LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
try { try {
@@ -106,8 +110,10 @@ public class DeobfPresets {
break; break;
} }
} }
return true;
} catch (Exception e) { } catch (Exception e) {
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e); LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
return false;
} }
} }
@@ -142,9 +148,7 @@ public class DeobfPresets {
} }
Files.write(deobfMapFile, list, MAP_FILE_CHARSET, Files.write(deobfMapFile, list, MAP_FILE_CHARSET,
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
if (LOG.isDebugEnabled()) { LOG.info("Deobfuscation map file saved as: {}", deobfMapFile);
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
}
} }
public String getForCls(ClassInfo cls) { public String getForCls(ClassInfo cls) {
@@ -16,6 +16,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.plugins.input.data.attributes.JadxAttrType; import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr; import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
@@ -76,22 +77,25 @@ public class Deobfuscator {
} }
public void execute() { public void execute() {
if (!args.isDeobfuscationForceSave()) { if (args.getDeobfuscationMapFileMode().shouldRead()) {
deobfPresets.load(); if (deobfPresets.load()) {
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) { for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue()); addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
}
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
initIndexes();
} }
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
initIndexes();
} }
process(); process();
} }
public void savePresets() { public void savePresets() {
DeobfuscationMapFileMode mode = args.getDeobfuscationMapFileMode();
if (!mode.shouldWrite()) {
return;
}
Path deobfMapFile = deobfPresets.getDeobfMapFile(); Path deobfMapFile = deobfPresets.getDeobfMapFile();
if (Files.exists(deobfMapFile) && !args.isDeobfuscationForceSave()) { if (mode == DeobfuscationMapFileMode.READ_OR_SAVE && Files.exists(deobfMapFile)) {
LOG.info("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
deobfMapFile.toAbsolutePath());
return; return;
} }
try { try {
@@ -112,16 +116,25 @@ public class Deobfuscator {
deobfPresets.getPkgPresetMap().put(p.getName(), p.getAlias()); deobfPresets.getPkgPresetMap().put(p.getName(), p.getAlias());
} }
} }
for (DeobfClsInfo deobfClsInfo : clsMap.values()) { for (ClassNode cls : root.getClasses()) {
if (deobfClsInfo.getAlias() != null) { ClassInfo classInfo = cls.getClassInfo();
deobfPresets.getClsPresetMap().put(deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias()); if (classInfo.hasAlias()) {
deobfPresets.getClsPresetMap().put(classInfo.makeRawFullName(), classInfo.getAliasShortName());
}
for (FieldNode fld : cls.getFields()) {
FieldInfo fieldInfo = fld.getFieldInfo();
if (fieldInfo.hasAlias()) {
deobfPresets.getFldPresetMap().put(fieldInfo.getRawFullId(), fld.getAlias());
}
}
for (MethodNode mth : cls.getMethods()) {
MethodInfo methodInfo = mth.getMethodInfo();
if (methodInfo.hasAlias()) {
deobfPresets.getFldPresetMap().put(methodInfo.getRawFullId(), methodInfo.getAlias());
}
} }
}
for (FieldInfo fld : fldMap.keySet()) {
deobfPresets.getFldPresetMap().put(fld.getRawFullId(), fld.getAlias());
}
for (MethodInfo mth : mthMap.keySet()) {
deobfPresets.getMthPresetMap().put(mth.getRawFullId(), mth.getAlias());
} }
} }
@@ -377,10 +390,10 @@ public class Deobfuscator {
String alias = null; String alias = null;
String pkgName = null; String pkgName = null;
if (this.parseKotlinMetadata) { if (this.parseKotlinMetadata) {
ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls); ClsAliasPair kotlinCls = KotlinMetadataUtils.getClassAlias(cls);
if (kotlinCls != null) { if (kotlinCls != null) {
alias = prepareNameFull(kotlinCls.getShortName(), "C"); alias = kotlinCls.getName();
pkgName = kotlinCls.getPackage(); pkgName = kotlinCls.getPkg();
} }
} }
if (alias == null && this.useSourceNameAsAlias) { if (alias == null && this.useSourceNameAsAlias) {
@@ -572,6 +585,7 @@ public class Deobfuscator {
if (!pkg.hasAlias()) { if (!pkg.hasAlias()) {
String pkgName = pkg.getName(); String pkgName = pkg.getName();
if ((args.isDeobfuscationOn() && shouldRename(pkgName)) if ((args.isDeobfuscationOn() && shouldRename(pkgName))
&& (pkg.getParentPackage() != rootPackage || !TldHelper.contains(pkgName)) // check if first level is a valid tld
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName)) || (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName))
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) { || (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName())); String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
@@ -592,20 +606,6 @@ public class Deobfuscator {
return NameMapper.removeInvalidCharsMiddle(name); return NameMapper.removeInvalidCharsMiddle(name);
} }
private String prepareNameFull(String name, String prefix) {
if (name.length() > maxLength) {
return makeHashName(name, prefix);
}
String result = NameMapper.removeInvalidChars(name, prefix);
if (result.isEmpty()) {
return makeHashName(name, prefix);
}
if (NameMapper.isReserved(result)) {
return prefix + result;
}
return result;
}
private static String makeHashName(String name, String invalidPrefix) { private static String makeHashName(String name, String invalidPrefix) {
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode()); return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
} }
@@ -143,19 +143,15 @@ public class NameMapper {
/** /**
* Return modified string with removed: * Return modified string with removed:
* <p>
* <ul> * <ul>
* <li>not printable chars (including unicode) * <li>not printable chars (including unicode)
* <li>chars not valid for java identifier part * <li>chars not valid for java identifier part
* </ul> * </ul>
* <p>
* Note: this 'middle' method must be used with prefixed string: * Note: this 'middle' method must be used with prefixed string:
* <p>
* <ul> * <ul>
* <li>can leave invalid chars for java identifier start (i.e numbers) * <li>can leave invalid chars for java identifier start (i.e numbers)
* <li>result not checked for reserved words * <li>result not checked for reserved words
* </ul> * </ul>
* <p>
*/ */
public static String removeInvalidCharsMiddle(String name) { public static String removeInvalidCharsMiddle(String name) {
if (isValidIdentifier(name) && isAllCharsPrintable(name)) { if (isValidIdentifier(name) && isAllCharsPrintable(name)) {
@@ -0,0 +1,38 @@
package jadx.core.deobf;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Provides a list of all top level domains with 3 characters and less,
* so we can exclude them from deobfuscation.
*/
public class TldHelper {
private static final Set<String> TLD_SET = loadTldFile();
private static Set<String> loadTldFile() {
Set<String> tldNames = new HashSet<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(Deobfuscator.class.getResourceAsStream("tld_3.txt")))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (!line.startsWith("#") && !line.isEmpty()) {
tldNames.add(line);
}
}
return tldNames;
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load top level domain list tld_3.txt", e);
}
}
public static boolean contains(String name) {
return TLD_SET.contains(name);
}
}
@@ -33,8 +33,8 @@ public enum AFlag {
SKIP_FIRST_ARG, SKIP_FIRST_ARG,
SKIP_ARG, // skip argument in invoke call SKIP_ARG, // skip argument in invoke call
NO_SKIP_ARGS,
ANONYMOUS_CONSTRUCTOR, ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS,
THIS, THIS,
SUPER, SUPER,
@@ -76,8 +76,11 @@ public enum AFlag {
INCONSISTENT_CODE, // warning about incorrect decompilation INCONSISTENT_CODE, // warning about incorrect decompilation
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
REQUEST_CODE_SHRINK,
RERUN_SSA_TRANSFORM, RERUN_SSA_TRANSFORM,
METHOD_CANDIDATE_FOR_INLINE,
// Class processing flags // Class processing flags
RESTART_CODEGEN, // codegen must be executed again RESTART_CODEGEN, // codegen must be executed again
RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage
@@ -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.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr; import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EdgeInsnAttr; import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
@@ -16,6 +17,7 @@ import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr; import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
import jadx.core.dex.attributes.nodes.LoopInfo; import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr; import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr; import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr; import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
@@ -23,6 +25,7 @@ import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr; import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr; import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.attributes.nodes.SpecialEdgeAttr;
import jadx.core.dex.attributes.nodes.TmpEdgeAttr; import jadx.core.dex.attributes.nodes.TmpEdgeAttr;
import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.CatchAttr;
@@ -51,6 +54,7 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>(); public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>(); public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>(); public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
public static final AType<AnonymousClassAttr> ANONYMOUS_CLASS = new AType<>();
// field // field
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>(); public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
@@ -63,6 +67,7 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>(); public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>(); public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>(); public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>();
public static final AType<MethodBridgeAttr> BRIDGED_BY = new AType<>();
// region // region
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>(); public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
@@ -72,6 +77,7 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>(); public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>(); public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>(); public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
public static final AType<AttrList<SpecialEdgeAttr>> SPECIAL_EDGE = new AType<>();
public static final AType<TmpEdgeAttr> TMP_EDGE = new AType<>(); public static final AType<TmpEdgeAttr> TMP_EDGE = new AType<>();
public static final AType<TryCatchBlockAttr> TRY_BLOCK = new AType<>(); public static final AType<TryCatchBlockAttr> TRY_BLOCK = new AType<>();
@@ -0,0 +1,35 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
public class AnonymousClassAttr extends PinnedAttribute {
private final ClassNode outerCls;
private final ArgType baseType;
public AnonymousClassAttr(ClassNode outerCls, ArgType baseType) {
this.outerCls = outerCls;
this.baseType = baseType;
}
public ClassNode getOuterCls() {
return outerCls;
}
public ArgType getBaseType() {
return baseType;
}
@Override
public AType<AnonymousClassAttr> getAttrType() {
return AType.ANONYMOUS_CLASS;
}
@Override
public String toString() {
return "AnonymousClass{" + outerCls + ", base: " + baseType + '}';
}
}
@@ -1,7 +1,6 @@
package jadx.core.dex.attributes.nodes; package jadx.core.dex.attributes.nodes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -20,10 +19,10 @@ public class LoopInfo {
private int id; private int id;
private LoopInfo parentLoop; private LoopInfo parentLoop;
public LoopInfo(BlockNode start, BlockNode end) { public LoopInfo(BlockNode start, BlockNode end, Set<BlockNode> loopBlocks) {
this.start = start; this.start = start;
this.end = end; this.end = end;
this.loopBlocks = Collections.unmodifiableSet(BlockUtils.getAllPathsBlocks(start, end)); this.loopBlocks = loopBlocks;
} }
public BlockNode getStart() { public BlockNode getStart() {
@@ -0,0 +1,28 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.MethodNode;
public class MethodBridgeAttr extends PinnedAttribute {
private final MethodNode bridgeMth;
public MethodBridgeAttr(MethodNode bridgeMth) {
this.bridgeMth = bridgeMth;
}
public MethodNode getBridgeMth() {
return bridgeMth;
}
@Override
public AType<MethodBridgeAttr> getAttrType() {
return AType.BRIDGED_BY;
}
@Override
public String toString() {
return "BRIDGED_BY: " + bridgeMth;
}
}
@@ -1,6 +1,7 @@
package jadx.core.dex.attributes.nodes; package jadx.core.dex.attributes.nodes;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import jadx.api.plugins.input.data.attributes.PinnedAttribute; import jadx.api.plugins.input.data.attributes.PinnedAttribute;
@@ -20,27 +21,26 @@ public class MethodOverrideAttr extends PinnedAttribute {
*/ */
private SortedSet<MethodNode> relatedMthNodes; private SortedSet<MethodNode> relatedMthNodes;
public MethodOverrideAttr(List<IMethodDetails> overrideList, SortedSet<MethodNode> relatedMthNodes) { private Set<IMethodDetails> baseMethods;
public MethodOverrideAttr(List<IMethodDetails> overrideList, SortedSet<MethodNode> relatedMthNodes, Set<IMethodDetails> baseMethods) {
this.overrideList = overrideList; this.overrideList = overrideList;
this.relatedMthNodes = relatedMthNodes; this.relatedMthNodes = relatedMthNodes;
} this.baseMethods = baseMethods;
public boolean isAtBaseMth() {
return overrideList.isEmpty();
} }
public List<IMethodDetails> getOverrideList() { public List<IMethodDetails> getOverrideList() {
return overrideList; return overrideList;
} }
public void setOverrideList(List<IMethodDetails> overrideList) {
this.overrideList = overrideList;
}
public SortedSet<MethodNode> getRelatedMthNodes() { public SortedSet<MethodNode> getRelatedMthNodes() {
return relatedMthNodes; return relatedMthNodes;
} }
public Set<IMethodDetails> getBaseMethods() {
return baseMethods;
}
public void setRelatedMthNodes(SortedSet<MethodNode> relatedMthNodes) { public void setRelatedMthNodes(SortedSet<MethodNode> relatedMthNodes) {
this.relatedMthNodes = relatedMthNodes; this.relatedMthNodes = relatedMthNodes;
} }
@@ -52,6 +52,6 @@ public class MethodOverrideAttr extends PinnedAttribute {
@Override @Override
public String toString() { public String toString() {
return "METHOD_OVERRIDE: " + overrideList; return "METHOD_OVERRIDE: " + getBaseMethods();
} }
} }
@@ -6,6 +6,16 @@ import jadx.core.dex.attributes.AttrNode;
public class RenameReasonAttr implements IJadxAttribute { public class RenameReasonAttr implements IJadxAttribute {
public static RenameReasonAttr forNode(AttrNode node) {
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
if (renameReasonAttr != null) {
return renameReasonAttr;
}
RenameReasonAttr newAttr = new RenameReasonAttr();
node.addAttr(newAttr);
return newAttr;
}
private String description; private String description;
public RenameReasonAttr() { public RenameReasonAttr() {
@@ -0,0 +1,46 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrList;
import jadx.core.dex.nodes.BlockNode;
public class SpecialEdgeAttr implements IJadxAttribute {
public enum SpecialEdgeType {
BACK_EDGE,
CROSS_EDGE
}
private final SpecialEdgeType type;
private final BlockNode start;
private final BlockNode end;
public SpecialEdgeAttr(SpecialEdgeType type, BlockNode start, BlockNode end) {
this.type = type;
this.start = start;
this.end = end;
}
public SpecialEdgeType getType() {
return type;
}
public BlockNode getStart() {
return start;
}
public BlockNode getEnd() {
return end;
}
@Override
public AType<AttrList<SpecialEdgeAttr>> getAttrType() {
return AType.SPECIAL_EDGE;
}
@Override
public String toString() {
return type + ": " + start + " -> " + end;
}
}
@@ -246,13 +246,13 @@ public final class ClassInfo implements Comparable<ClassInfo> {
} }
public void notInner(RootNode root) { public void notInner(RootNode root) {
this.parentClass = null;
splitAndApplyNames(root, type, false); splitAndApplyNames(root, type, false);
this.parentClass = null;
} }
public void convertToInner(ClassNode parent) { public void convertToInner(ClassNode parent) {
this.parentClass = parent.getClassInfo();
splitAndApplyNames(parent.root(), type, true); splitAndApplyNames(parent.root(), type, true);
this.parentClass = parent.getClassInfo();
} }
public void updateNames(RootNode root) { public void updateNames(RootNode root) {
@@ -176,6 +176,9 @@ public class ConstStorage {
@Nullable @Nullable
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) { public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
if (!replaceEnabled) {
return null;
}
PrimitiveType type = arg.getType().getPrimitiveType(); PrimitiveType type = arg.getType().getPrimitiveType();
if (type == null) { if (type == null) {
return null; return null;
@@ -508,7 +508,17 @@ public class InsnDecoder {
private InsnNode makeNewArray(InsnData insn) { private InsnNode makeNewArray(InsnData insn) {
ArgType indexType = ArgType.parse(insn.getIndexAsType()); ArgType indexType = ArgType.parse(insn.getIndexAsType());
int dim = (int) insn.getLiteral(); int dim = (int) insn.getLiteral();
ArgType arrType = dim == 0 ? indexType : ArgType.array(indexType, dim); ArgType arrType;
if (dim == 0) {
arrType = indexType;
} else {
if (indexType.isArray()) {
// java bytecode can pass array as a base type
arrType = indexType;
} else {
arrType = ArgType.array(indexType, dim);
}
}
int regsCount = insn.getRegsCount(); int regsCount = insn.getRegsCount();
NewArrayNode newArr = new NewArrayNode(arrType, regsCount - 1); NewArrayNode newArr = new NewArrayNode(arrType, regsCount - 1);
newArr.setResult(InsnArg.reg(insn, 0, arrType)); newArr.setResult(InsnArg.reg(insn, 0, arrType));
@@ -655,7 +655,7 @@ public abstract class ArgType {
if (from.equals(to)) { if (from.equals(to)) {
return false; return false;
} }
TypeCompareEnum result = root.getTypeUpdate().getTypeCompare().compareTypes(from, to); TypeCompareEnum result = root.getTypeCompare().compareTypes(from, to);
return !result.isNarrow(); return !result.isNarrow();
} }
@@ -1,5 +1,7 @@
package jadx.core.dex.instructions.args; package jadx.core.dex.instructions.args;
import org.jetbrains.annotations.Nullable;
import jadx.core.codegen.TypeGen; import jadx.core.codegen.TypeGen;
import jadx.core.utils.StringUtils; import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -57,12 +59,48 @@ public final class LiteralArg extends InsnArg {
} }
public boolean isInteger() { public boolean isInteger() {
PrimitiveType type = this.type.getPrimitiveType(); switch (type.getPrimitiveType()) {
return type == PrimitiveType.INT case INT:
|| type == PrimitiveType.BYTE case BYTE:
|| type == PrimitiveType.CHAR case CHAR:
|| type == PrimitiveType.SHORT case SHORT:
|| type == PrimitiveType.LONG; case LONG:
return true;
default:
return false;
}
}
public boolean isNegative() {
if (isInteger()) {
return literal < 0;
}
if (type == ArgType.FLOAT) {
float val = Float.intBitsToFloat(((int) literal));
return val < 0 && Float.isFinite(val);
}
if (type == ArgType.DOUBLE) {
double val = Double.longBitsToDouble(literal);
return val < 0 && Double.isFinite(val);
}
return false;
}
@Nullable
public LiteralArg negate() {
long neg;
if (isInteger()) {
neg = -literal;
} else if (type == ArgType.FLOAT) {
float val = Float.intBitsToFloat(((int) literal));
neg = Float.floatToIntBits(-val);
} else if (type == ArgType.DOUBLE) {
double val = Double.longBitsToDouble(literal);
neg = Double.doubleToLongBits(-val);
} else {
return null;
}
return new LiteralArg(neg, type);
} }
@Override @Override
@@ -196,17 +196,6 @@ public class SSAVar {
return usedInPhi != null && !usedInPhi.isEmpty(); return usedInPhi != null && !usedInPhi.isEmpty();
} }
public int getVariableUseCount() {
int count = useList.size();
if (usedInPhi == null) {
return count;
}
for (PhiInsn phiInsn : usedInPhi) {
count += phiInsn.getResult().getSVar().getUseCount();
}
return count;
}
public void setName(String name) { public void setName(String name) {
if (name != null) { if (name != null) {
if (codeVar == null) { if (codeVar == null) {
@@ -33,6 +33,7 @@ import jadx.api.plugins.input.data.impl.ListConsumer;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.ProcessClass; import jadx.core.ProcessClass;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.NotificationAttrNode; import jadx.core.dex.attributes.nodes.NotificationAttrNode;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType; import jadx.core.dex.info.AccessInfo.AFType;
@@ -42,12 +43,12 @@ import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.nodes.utils.TypeUtils; import jadx.core.dex.nodes.utils.TypeUtils;
import jadx.core.utils.ListUtils;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.LOADED; import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED; import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> { public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
@@ -79,6 +80,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
* Top level classes used in this class (only for top level classes, empty for inners) * Top level classes used in this class (only for top level classes, empty for inners)
*/ */
private List<ClassNode> dependencies = Collections.emptyList(); private List<ClassNode> dependencies = Collections.emptyList();
/**
* Top level classes needed for code generation stage
*/
private List<ClassNode> codegenDeps = Collections.emptyList();
/** /**
* Classes which uses this class * Classes which uses this class
*/ */
@@ -283,12 +288,15 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return true; return true;
} }
public boolean checkProcessed() {
return getTopParentClass().getState().isProcessComplete();
}
public void ensureProcessed() { public void ensureProcessed() {
ClassNode topClass = getTopParentClass(); if (!checkProcessed()) {
ProcessState state = topClass.getState(); ClassNode topParentClass = getTopParentClass();
if (state != PROCESS_COMPLETE) {
throw new JadxRuntimeException("Expected class to be processed at this point," throw new JadxRuntimeException("Expected class to be processed at this point,"
+ " class: " + topClass + ", state: " + state); + " class: " + topParentClass + ", state: " + topParentClass.getState());
} }
} }
@@ -352,6 +360,17 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return codeInfo; return codeInfo;
} }
@Nullable
public ICodeInfo getCodeFromCache() {
ICodeCache codeCache = root().getCodeCache();
String clsRawName = getRawName();
ICodeInfo codeInfo = codeCache.get(clsRawName);
if (codeInfo == ICodeInfo.EMPTY) {
return null;
}
return codeInfo;
}
@Override @Override
public void load() { public void load() {
for (MethodNode mth : getMethods()) { for (MethodNode mth : getMethods()) {
@@ -602,7 +621,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
} }
public boolean isAnonymous() { public boolean isAnonymous() {
return contains(AFlag.ANONYMOUS_CLASS); return contains(AType.ANONYMOUS_CLASS);
} }
public boolean isInner() { public boolean isInner() {
@@ -733,6 +752,26 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
this.dependencies = dependencies; this.dependencies = dependencies;
} }
public void removeDependency(ClassNode dep) {
this.dependencies = ListUtils.safeRemoveAndTrim(this.dependencies, dep);
}
public List<ClassNode> getCodegenDeps() {
return codegenDeps;
}
public void setCodegenDeps(List<ClassNode> codegenDeps) {
this.codegenDeps = codegenDeps;
}
public void addCodegenDep(ClassNode dep) {
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
}
public int getTotalDepsCount() {
return dependencies.size() + codegenDeps.size();
}
public List<ClassNode> getUseIn() { public List<ClassNode> getUseIn() {
return useIn; return useIn;
} }
@@ -9,6 +9,7 @@ import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType; import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.ListUtils;
public class FieldNode extends NotificationAttrNode implements ICodeNode { public class FieldNode extends NotificationAttrNode implements ICodeNode {
@@ -80,6 +81,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
this.useIn = useIn; this.useIn = useIn;
} }
public synchronized void addUseIn(MethodNode mth) {
useIn = ListUtils.safeAdd(useIn, mth);
}
@Override @Override
public String typeName() { public String typeName() {
return "field"; return "field";
@@ -322,6 +322,40 @@ public class InsnNode extends LineAttrNode {
return null; return null;
} }
/**
* Visit all args recursively (including inner instructions), but excluding wrapped args
*/
public void visitArgs(Consumer<InsnArg> visitor) {
for (InsnArg arg : getArguments()) {
if (arg.isInsnWrap()) {
((InsnWrapArg) arg).getWrapInsn().visitArgs(visitor);
} else {
visitor.accept(arg);
}
}
}
/**
* Visit all args recursively (including inner instructions), but excluding wrapped args.
* To terminate visiting return non-null value
*/
@Nullable
public <R> R visitArgs(Function<InsnArg, R> visitor) {
for (InsnArg arg : getArguments()) {
R result;
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
result = wrapInsn.visitArgs(visitor);
} else {
result = visitor.apply(arg);
}
if (result != null) {
return result;
}
}
return null;
}
/** /**
* 'Soft' equals, don't compare arguments, only instruction specific parameters. * 'Soft' equals, don't compare arguments, only instruction specific parameters.
*/ */
@@ -385,17 +419,16 @@ public class InsnNode extends LineAttrNode {
/** /**
* Make copy of InsnNode object. * Make copy of InsnNode object.
* <p> * <br>
* NOTE: can't copy instruction with result argument * NOTE: can't copy instruction with result argument
* (SSA variable can't be used in two different assigns). * (SSA variable can't be used in two different assigns).
* <p> * <br>
* Prefer use next methods: * Prefer use next methods:
* <ul> * <ul>
* <li>{@link #copyWithoutResult()} to explicitly state that result not needed * <li>{@link #copyWithoutResult()} to explicitly state that result not needed
* <li>{@link #copy(RegisterArg)} to provide new result arg * <li>{@link #copy(RegisterArg)} to provide new result arg
* <li>{@link #copyWithNewSsaVar(MethodNode)} to make new SSA variable for result arg * <li>{@link #copyWithNewSsaVar(MethodNode)} to make new SSA variable for result arg
* </ul> * </ul>
* <p>
*/ */
public InsnNode copy() { public InsnNode copy() {
if (this.getClass() != InsnNode.class) { if (this.getClass() != InsnNode.class) {
@@ -99,9 +99,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
@Override @Override
public void unload() { public void unload() {
loaded = false; loaded = false;
if (noCode) {
return;
}
// don't unload retType, argTypes, typeParameters // don't unload retType, argTypes, typeParameters
thisArg = null; thisArg = null;
argsList = null; argsList = null;
@@ -336,12 +336,9 @@ public class RootNode {
/** /**
* Searches for ClassNode by its full name (original or alias name) * Searches for ClassNode by its full name (original or alias name)
* * <br>
* Warning: This method has a runtime of O(n) (n = number of classes). * Warning: This method has a runtime of O(n) (n = number of classes).
* If you need to call it more than once consider {@link #buildFullAliasClassCache()} instead * If you need to call it more than once consider {@link #buildFullAliasClassCache()} instead
*
* @param fullName
* @return
*/ */
@Nullable @Nullable
public ClassNode searchClassByFullAlias(String fullName) { public ClassNode searchClassByFullAlias(String fullName) {
@@ -355,10 +352,6 @@ public class RootNode {
return null; return null;
} }
/**
*
* @return
*/
public Map<String, ClassNode> buildFullAliasClassCache() { public Map<String, ClassNode> buildFullAliasClassCache() {
Map<String, ClassNode> classNameCache = new HashMap<>(classes.size()); Map<String, ClassNode> classNameCache = new HashMap<>(classes.size());
for (ClassNode cls : classes) { for (ClassNode cls : classes) {
@@ -385,15 +378,6 @@ public class RootNode {
@Nullable @Nullable
public MethodNode resolveMethod(@NotNull MethodInfo mth) { public MethodNode resolveMethod(@NotNull MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass());
if (cls != null) {
return cls.searchMethod(mth);
}
return null;
}
@Nullable
public MethodNode deepResolveMethod(@NotNull MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass()); ClassNode cls = resolveClass(mth.getDeclClass());
if (cls == null) { if (cls == null) {
return null; return null;
@@ -437,19 +421,14 @@ public class RootNode {
@Nullable @Nullable
public FieldNode resolveField(FieldInfo field) { public FieldNode resolveField(FieldInfo field) {
ClassNode cls = resolveClass(field.getDeclClass());
if (cls != null) {
return cls.searchField(field);
}
return null;
}
@Nullable
public FieldNode deepResolveField(@NotNull FieldInfo field) {
ClassNode cls = resolveClass(field.getDeclClass()); ClassNode cls = resolveClass(field.getDeclClass());
if (cls == null) { if (cls == null) {
return null; return null;
} }
FieldNode fieldNode = cls.searchField(field);
if (fieldNode != null) {
return fieldNode;
}
return deepResolveField(cls, field); return deepResolveField(cls, field);
} }
@@ -2,7 +2,6 @@ package jadx.core.dex.nodes.parser;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -197,6 +196,8 @@ public class SignatureParser {
String obj = slice(); String obj = slice();
if (!innerType) { if (!innerType) {
obj += ';'; obj += ';';
} else {
obj = obj.replace('/', '.');
} }
List<ArgType> typeVars = consumeGenericArgs(); List<ArgType> typeVars = consumeGenericArgs();
consume('>'); consume('>');
@@ -227,7 +228,7 @@ public class SignatureParser {
} }
private List<ArgType> consumeGenericArgs() { private List<ArgType> consumeGenericArgs() {
List<ArgType> list = new LinkedList<>(); List<ArgType> list = new ArrayList<>();
ArgType type; ArgType type;
do { do {
if (lookAhead('*')) { if (lookAhead('*')) {
@@ -8,6 +8,9 @@ import org.jetbrains.annotations.Nullable;
import jadx.core.clsp.ClspClass; import jadx.core.clsp.ClspClass;
import jadx.core.clsp.ClspMethod; import jadx.core.clsp.ClspMethod;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.BaseInvokeNode; import jadx.core.dex.instructions.BaseInvokeNode;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
@@ -15,6 +18,7 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.IMethodDetails;
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.Utils;
public class MethodUtils { public class MethodUtils {
private final RootNode root; private final RootNode root;
@@ -34,7 +38,7 @@ public class MethodUtils {
@Nullable @Nullable
public IMethodDetails getMethodDetails(MethodInfo callMth) { public IMethodDetails getMethodDetails(MethodInfo callMth) {
MethodNode mthNode = root.deepResolveMethod(callMth); MethodNode mthNode = root.resolveMethod(callMth);
if (mthNode != null) { if (mthNode != null) {
return mthNode; return mthNode;
} }
@@ -67,7 +71,7 @@ public class MethodUtils {
return null; return null;
} }
public boolean processMethodArgsOverloaded(ArgType startCls, MethodInfo mthInfo, @Nullable List<IMethodDetails> collectedMths) { private boolean processMethodArgsOverloaded(ArgType startCls, MethodInfo mthInfo, @Nullable List<IMethodDetails> collectedMths) {
if (startCls == null || !startCls.isObject()) { if (startCls == null || !startCls.isObject()) {
return false; return false;
} }
@@ -122,4 +126,25 @@ public class MethodUtils {
} }
return false; return false;
} }
@Nullable
public IMethodDetails getOverrideBaseMth(MethodNode mth) {
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr == null) {
return null;
}
return Utils.getOne(overrideAttr.getBaseMethods());
}
public ClassInfo getMethodOriginDeclClass(MethodNode mth) {
IMethodDetails baseMth = getOverrideBaseMth(mth);
if (baseMth != null) {
return baseMth.getMethodInfo().getDeclClass();
}
MethodBridgeAttr bridgeAttr = mth.get(AType.BRIDGED_BY);
if (bridgeAttr != null) {
return getMethodOriginDeclClass(bridgeAttr.getBridgeMth());
}
return mth.getMethodInfo().getDeclClass();
}
} }
@@ -63,7 +63,7 @@ public class TypeUtils {
public ArgType expandTypeVariables(ClassNode cls, ArgType type) { public ArgType expandTypeVariables(ClassNode cls, ArgType type) {
if (type.containsTypeVariable()) { if (type.containsTypeVariable()) {
expandTypeVar(cls, type, cls.getGenericTypeParameters()); expandTypeVar(cls, type, getKnownTypeVarsAtClass(cls));
} }
return type; return type;
} }
@@ -115,11 +115,18 @@ public class TypeUtils {
return varsAttr.getTypeVars(); return varsAttr.getTypeVars();
} }
private static Set<ArgType> collectKnownTypeVarsAtMethod(MethodNode mth) { private static Collection<ArgType> getKnownTypeVarsAtClass(ClassNode cls) {
ClassNode declCls = mth.getParentClass(); if (cls.isInner()) {
Set<ArgType> typeVars = new HashSet<>(declCls.getGenericTypeParameters()); Set<ArgType> typeVars = new HashSet<>(cls.getGenericTypeParameters());
declCls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters())); cls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters()));
return typeVars;
}
return cls.getGenericTypeParameters();
}
private static Set<ArgType> collectKnownTypeVarsAtMethod(MethodNode mth) {
Set<ArgType> typeVars = new HashSet<>();
typeVars.addAll(getKnownTypeVarsAtClass(mth.getParentClass()));
typeVars.addAll(mth.getTypeParameters()); typeVars.addAll(mth.getTypeParameters());
return typeVars.isEmpty() ? Collections.emptySet() : typeVars; return typeVars.isEmpty() ? Collections.emptySet() : typeVars;
} }
@@ -1,5 +1,6 @@
package jadx.core.dex.trycatch; package jadx.core.dex.trycatch;
import java.util.Comparator;
import java.util.List; import java.util.List;
import jadx.api.plugins.input.data.attributes.IJadxAttribute; import jadx.api.plugins.input.data.attributes.IJadxAttribute;
@@ -8,9 +9,14 @@ import jadx.core.utils.Utils;
public class CatchAttr implements IJadxAttribute { public class CatchAttr implements IJadxAttribute {
public static CatchAttr build(List<ExceptionHandler> handlers) {
handlers.sort(Comparator.comparingInt(ExceptionHandler::getHandlerOffset));
return new CatchAttr(handlers);
}
private final List<ExceptionHandler> handlers; private final List<ExceptionHandler> handlers;
public CatchAttr(List<ExceptionHandler> handlers) { private CatchAttr(List<ExceptionHandler> handlers) {
this.handlers = handlers; this.handlers = handlers;
} }
@@ -0,0 +1,133 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
name = "AnonymousClassVisitor",
desc = "Prepare anonymous class for inline",
runBefore = {
ModVisitor.class,
CodeShrinkVisitor.class
}
)
public class AnonymousClassVisitor extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws JadxException {
if (cls.contains(AType.ANONYMOUS_CLASS)) {
for (MethodNode mth : cls.getMethods()) {
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
processAnonymousConstructor(mth);
break;
}
}
}
return true;
}
private static void processAnonymousConstructor(MethodNode mth) {
List<InsnNode> usedInsns = new ArrayList<>();
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(mth, usedInsns);
if (argsMap.isEmpty()) {
mth.add(AFlag.NO_SKIP_ARGS);
} else {
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
FieldNode field = entry.getValue();
if (field == null) {
continue;
}
InsnArg arg = entry.getKey();
field.addAttr(new FieldReplaceAttr(arg));
field.add(AFlag.DONT_GENERATE);
if (arg.isRegister()) {
arg.add(AFlag.SKIP_ARG);
SkipMethodArgsAttr.skipArg(mth, ((RegisterArg) arg));
}
}
}
for (InsnNode usedInsn : usedInsns) {
usedInsn.add(AFlag.DONT_GENERATE);
}
}
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode mth, List<InsnNode> usedInsns) {
MethodInfo callMth = mth.getMethodInfo();
ClassNode cls = mth.getParentClass();
List<RegisterArg> argList = mth.getArgRegs();
ClassNode outerCls = mth.getUseIn().get(0).getParentClass();
int startArg = 0;
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(outerCls.getClassInfo().getType())) {
startArg = 1;
}
Map<InsnArg, FieldNode> map = new LinkedHashMap<>();
int argsCount = argList.size();
for (int i = startArg; i < argsCount; i++) {
RegisterArg arg = argList.get(i);
InsnNode useInsn = getParentInsnSkipMove(arg);
if (useInsn == null) {
return Collections.emptyMap();
}
switch (useInsn.getType()) {
case IPUT:
FieldNode fieldNode = cls.searchField((FieldInfo) ((IndexInsnNode) useInsn).getIndex());
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
return Collections.emptyMap();
}
map.put(arg, fieldNode);
usedInsns.add(useInsn);
break;
case CONSTRUCTOR:
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
if (!superConstr.isSuper()) {
return Collections.emptyMap();
}
usedInsns.add(useInsn);
break;
default:
return Collections.emptyMap();
}
}
return map;
}
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar.getUseCount() != 1) {
return null;
}
RegisterArg useArg = sVar.getUseList().get(0);
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn == null) {
return null;
}
if (parentInsn.getType() == InsnType.MOVE) {
return getParentInsnSkipMove(parentInsn.getResult());
}
return parentInsn;
}
}
@@ -1,6 +1,7 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -15,12 +16,16 @@ import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.visitors.typeinference.TypeCompare;
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset; import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset;
@@ -51,11 +56,11 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
tries.forEach(tryData -> LOG.debug(" - {}", tryData)); tries.forEach(tryData -> LOG.debug(" - {}", tryData));
} }
for (ITry tryData : tries) { for (ITry tryData : tries) {
List<ExceptionHandler> handlers = attachHandlers(mth, tryData.getCatch(), insnByOffset); List<ExceptionHandler> handlers = convertToHandlers(mth, tryData.getCatch(), insnByOffset);
if (handlers.isEmpty()) { if (handlers.isEmpty()) {
continue; continue;
} }
markTryBounds(insnByOffset, tryData, new CatchAttr(handlers)); markTryBounds(insnByOffset, tryData, CatchAttr.build(handlers));
} }
} }
@@ -96,13 +101,13 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
if (existAttr != null) { if (existAttr != null) {
// merge handlers // merge handlers
List<ExceptionHandler> handlers = Utils.concat(existAttr.getHandlers(), catchAttr.getHandlers()); List<ExceptionHandler> handlers = Utils.concat(existAttr.getHandlers(), catchAttr.getHandlers());
insn.addAttr(new CatchAttr(handlers)); insn.addAttr(CatchAttr.build(handlers));
} else { } else {
insn.addAttr(catchAttr); insn.addAttr(catchAttr);
} }
} }
private static List<ExceptionHandler> attachHandlers(MethodNode mth, ICatch catchBlock, InsnNode[] insnByOffset) { private static List<ExceptionHandler> convertToHandlers(MethodNode mth, ICatch catchBlock, InsnNode[] insnByOffset) {
int[] handlerOffsetArr = catchBlock.getHandlers(); int[] handlerOffsetArr = catchBlock.getHandlers();
String[] handlerTypes = catchBlock.getTypes(); String[] handlerTypes = catchBlock.getTypes();
@@ -117,6 +122,7 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
if (allHandlerOffset >= 0) { if (allHandlerOffset >= 0) {
Utils.addToList(list, createHandler(mth, insnByOffset, allHandlerOffset, null)); Utils.addToList(list, createHandler(mth, insnByOffset, allHandlerOffset, null));
} }
checkAndFilterHandlers(mth, list);
return list; return list;
} }
@@ -143,6 +149,45 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
return handler; return handler;
} }
private static void checkAndFilterHandlers(MethodNode mth, List<ExceptionHandler> list) {
if (list.size() <= 1) {
return;
}
// Remove shadowed handlers (with same or narrow type compared to previous)
TypeCompare typeCompare = mth.root().getTypeCompare();
Iterator<ExceptionHandler> it = list.iterator();
ArgType maxType = null;
while (it.hasNext()) {
ExceptionHandler handler = it.next();
ArgType maxCatch = maxCatchFromHandler(handler, typeCompare);
if (maxType == null) {
maxType = maxCatch;
} else {
TypeCompareEnum result = typeCompare.compareObjects(maxType, maxCatch);
if (result.isWiderOrEqual()) {
if (Consts.DEBUG_EXC_HANDLERS) {
LOG.debug("Removed shadowed catch handler: {}, from list: {}", handler, list);
}
it.remove();
}
}
}
}
private static ArgType maxCatchFromHandler(ExceptionHandler handler, TypeCompare typeCompare) {
List<ClassInfo> catchTypes = handler.getCatchTypes();
if (catchTypes.isEmpty()) {
return ArgType.THROWABLE;
}
if (catchTypes.size() == 1) {
return catchTypes.get(0).getType();
}
return catchTypes.stream()
.map(ClassInfo::getType)
.max(typeCompare.getComparator())
.orElseThrow(() -> new JadxRuntimeException("Failed to get max type from catch list: " + catchTypes));
}
private static InsnNode insertNOP(InsnNode[] insnByOffset, int offset) { private static InsnNode insertNOP(InsnNode[] insnByOffset, int offset) {
InsnNode nop = new InsnNode(InsnType.NOP, 0); InsnNode nop = new InsnNode(InsnType.NOP, 0);
nop.setOffset(offset); nop.setOffset(offset);
@@ -1,7 +1,10 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import jadx.api.plugins.input.data.AccessFlags; import jadx.api.plugins.input.data.AccessFlags;
@@ -55,7 +58,7 @@ public class ClassModifier extends AbstractVisitor {
removeSyntheticFields(cls); removeSyntheticFields(cls);
cls.getMethods().forEach(ClassModifier::removeSyntheticMethods); cls.getMethods().forEach(ClassModifier::removeSyntheticMethods);
cls.getMethods().forEach(ClassModifier::removeEmptyMethods); cls.getMethods().forEach(ClassModifier::removeEmptyMethods);
cls.getMethods().forEach(ClassModifier::cleanInsnsInAnonymousConstructor); cls.getMethods().forEach(ClassModifier::processAnonymousConstructor);
return false; return false;
} }
@@ -241,7 +244,7 @@ public class ClassModifier extends AbstractVisitor {
return false; return false;
} }
MethodInfo callMth = invokeInsn.getCallMth(); MethodInfo callMth = invokeInsn.getCallMth();
MethodNode wrappedMth = mth.root().deepResolveMethod(callMth); MethodNode wrappedMth = mth.root().resolveMethod(callMth);
if (wrappedMth == null) { if (wrappedMth == null) {
return false; return false;
} }
@@ -326,27 +329,86 @@ public class ClassModifier extends AbstractVisitor {
/** /**
* Remove super call and put into removed fields from anonymous constructor * Remove super call and put into removed fields from anonymous constructor
*/ */
private static void cleanInsnsInAnonymousConstructor(MethodNode mth) { private static void processAnonymousConstructor(MethodNode mth) {
if (!mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) { if (!mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
return; return;
} }
for (BlockNode block : mth.getBasicBlocks()) { List<InsnNode> usedInsns = new ArrayList<>();
for (InsnNode insn : block.getInstructions()) { Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(mth, usedInsns);
InsnType type = insn.getType(); for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
if (type == InsnType.CONSTRUCTOR) { FieldNode field = entry.getValue();
ConstructorInsn ctorInsn = (ConstructorInsn) insn; if (field == null) {
if (ctorInsn.isSuper()) { continue;
ctorInsn.add(AFlag.DONT_GENERATE); }
} InsnArg arg = entry.getKey();
} else if (type == InsnType.IPUT) { field.addAttr(new FieldReplaceAttr(arg));
FieldInfo fldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex(); field.add(AFlag.DONT_GENERATE);
FieldNode fieldNode = mth.root().resolveField(fldInfo); if (arg.isRegister()) {
if (fieldNode != null && fieldNode.contains(AFlag.DONT_GENERATE)) { arg.add(AFlag.SKIP_ARG);
insn.add(AFlag.DONT_GENERATE); SkipMethodArgsAttr.skipArg(mth, ((RegisterArg) arg));
}
}
} }
} }
for (InsnNode usedInsn : usedInsns) {
usedInsn.add(AFlag.DONT_GENERATE);
}
}
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode mth, List<InsnNode> usedInsns) {
MethodInfo callMth = mth.getMethodInfo();
ClassNode cls = mth.getParentClass();
List<RegisterArg> argList = mth.getArgRegs();
ClassNode outerCls = mth.getUseIn().get(0).getParentClass();
int startArg = 0;
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(outerCls.getClassInfo().getType())) {
startArg = 1;
}
Map<InsnArg, FieldNode> map = new LinkedHashMap<>();
int argsCount = argList.size();
for (int i = startArg; i < argsCount; i++) {
RegisterArg arg = argList.get(i);
InsnNode useInsn = getParentInsnSkipMove(arg);
if (useInsn == null) {
return Collections.emptyMap();
}
switch (useInsn.getType()) {
case IPUT:
FieldNode fieldNode = cls.searchField((FieldInfo) ((IndexInsnNode) useInsn).getIndex());
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
return Collections.emptyMap();
}
map.put(arg, fieldNode);
usedInsns.add(useInsn);
break;
case CONSTRUCTOR:
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
if (!superConstr.isSuper()) {
return Collections.emptyMap();
}
usedInsns.add(useInsn);
break;
default:
return Collections.emptyMap();
}
}
return map;
}
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar.getUseCount() != 1) {
return null;
}
RegisterArg useArg = sVar.getUseList().get(0);
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn == null) {
return null;
}
if (parentInsn.getType() == InsnType.MOVE) {
return getParentInsnSkipMove(parentInsn.getResult());
}
return parentInsn;
} }
private static boolean isNonDefaultConstructorExists(MethodNode defCtor) { private static boolean isNonDefaultConstructorExists(MethodNode defCtor) {
@@ -65,6 +65,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
SSAVar sVar = insn.getResult().getSVar(); SSAVar sVar = insn.getResult().getSVar();
InsnArg constArg; InsnArg constArg;
Runnable onSuccess = null;
InsnType insnType = insn.getType(); InsnType insnType = insn.getType();
if (insnType == InsnType.CONST || insnType == InsnType.MOVE) { if (insnType == InsnType.CONST || insnType == InsnType.MOVE) {
@@ -90,6 +91,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
constArg = InsnArg.wrapArg(constGet); constArg = InsnArg.wrapArg(constGet);
constArg.setType(ArgType.STRING); constArg.setType(ArgType.STRING);
onSuccess = () -> f.addUseIn(mth);
} }
} else if (insnType == InsnType.CONST_CLASS) { } else if (insnType == InsnType.CONST_CLASS) {
if (sVar.isUsedInPhi()) { if (sVar.isUsedInPhi()) {
@@ -104,6 +106,9 @@ public class ConstInlineVisitor extends AbstractVisitor {
// all check passed, run replace // all check passed, run replace
if (replaceConst(mth, insn, constArg)) { if (replaceConst(mth, insn, constArg)) {
toRemove.add(insn); toRemove.add(insn);
if (onSuccess != null) {
onSuccess.run();
}
} }
} }
@@ -235,7 +240,10 @@ public class ConstInlineVisitor extends AbstractVisitor {
fieldNode = mth.getParentClass().getConstField((int) literal, false); fieldNode = mth.getParentClass().getConstField((int) literal, false);
} }
if (fieldNode != null) { if (fieldNode != null) {
litArg.wrapInstruction(mth, new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0)); IndexInsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0);
if (litArg.wrapInstruction(mth, sgetInsn) != null) {
fieldNode.addUseIn(mth);
}
} else { } else {
if (needExplicitCast(useInsn, litArg)) { if (needExplicitCast(useInsn, litArg)) {
litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE); litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
@@ -15,6 +15,7 @@ import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.codegen.TypeGen; import jadx.core.codegen.TypeGen;
import jadx.core.deobf.NameMapper; import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField; import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
@@ -71,7 +72,14 @@ public class EnumVisitor extends AbstractVisitor {
@Override @Override
public boolean visit(ClassNode cls) throws JadxException { public boolean visit(ClassNode cls) throws JadxException {
if (!convertToEnum(cls)) { boolean converted;
try {
converted = convertToEnum(cls);
} catch (Exception e) {
cls.addWarnComment("Enum visitor error", e);
converted = false;
}
if (!converted) {
AccessInfo accessFlags = cls.getAccessFlags(); AccessInfo accessFlags = cls.getAccessFlags();
if (accessFlags.isEnum()) { if (accessFlags.isEnum()) {
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM)); cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
@@ -179,8 +187,7 @@ public class EnumVisitor extends AbstractVisitor {
if (!enumClsInfo.equals(cls.getClassInfo())) { if (!enumClsInfo.equals(cls.getClassInfo())) {
ClassNode enumCls = cls.root().resolveClass(enumClsInfo); ClassNode enumCls = cls.root().resolveClass(enumClsInfo);
if (enumCls != null) { if (enumCls != null) {
processEnumCls(enumField, enumCls); processEnumCls(cls, enumField, enumCls);
cls.addInlinedClass(enumCls);
} }
} }
List<RegisterArg> regs = new ArrayList<>(); List<RegisterArg> regs = new ArrayList<>();
@@ -381,7 +388,11 @@ public class EnumVisitor extends AbstractVisitor {
if (constrCls == null) { if (constrCls == null) {
return null; return null;
} }
if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum()) { if (constrCls.equals(cls)) {
// allow same class
} else if (constrCls.contains(AType.ANONYMOUS_CLASS)) {
// allow external class already marked as anonymous
} else {
return null; return null;
} }
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth()); MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
@@ -466,7 +477,7 @@ public class EnumVisitor extends AbstractVisitor {
return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null; return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null;
} }
private static void processEnumCls(EnumField field, ClassNode innerCls) { private static void processEnumCls(ClassNode cls, EnumField field, ClassNode innerCls) {
// remove constructor, because it is anonymous class // remove constructor, because it is anonymous class
for (MethodNode innerMth : innerCls.getMethods()) { for (MethodNode innerMth : innerCls.getMethods()) {
if (innerMth.getAccessFlags().isConstructor()) { if (innerMth.getAccessFlags().isConstructor()) {
@@ -474,7 +485,11 @@ public class EnumVisitor extends AbstractVisitor {
} }
} }
field.setCls(innerCls); field.setCls(innerCls);
innerCls.add(AFlag.DONT_GENERATE); if (!innerCls.getParentClass().equals(cls)) {
// not inner
cls.addInlinedClass(innerCls);
innerCls.add(AFlag.DONT_GENERATE);
}
} }
private ConstructorInsn getConstructorInsn(InsnNode insn) { private ConstructorInsn getConstructorInsn(InsnNode insn) {
@@ -86,7 +86,7 @@ public class FixAccessModifiers extends AbstractVisitor {
if (!accessFlags.isPublic()) { if (!accessFlags.isPublic()) {
// if class is used in inlinable method => make it public // if class is used in inlinable method => make it public
for (MethodNode useMth : cls.getUseInMth()) { for (MethodNode useMth : cls.getUseInMth()) {
boolean canInline = MarkMethodsForInline.canInline(useMth) || useMth.contains(AType.METHOD_INLINE); boolean canInline = useMth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE) || useMth.contains(AType.METHOD_INLINE);
if (canInline && !useMth.getUseIn().isEmpty()) { if (canInline && !useMth.getUseIn().isEmpty()) {
return AccessFlags.PUBLIC; return AccessFlags.PUBLIC;
} }
@@ -26,9 +26,6 @@ public class InitCodeVariables extends AbstractVisitor {
@Override @Override
public void visit(MethodNode mth) throws JadxException { public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode()) {
return;
}
initCodeVars(mth); initCodeVars(mth);
} }
@@ -42,16 +39,24 @@ public class InitCodeVariables extends AbstractVisitor {
private static void initCodeVars(MethodNode mth) { private static void initCodeVars(MethodNode mth) {
RegisterArg thisArg = mth.getThisArg(); RegisterArg thisArg = mth.getThisArg();
if (thisArg != null) { if (thisArg != null) {
initCodeVar(thisArg.getSVar()); initCodeVar(mth, thisArg);
} }
for (RegisterArg mthArg : mth.getArgRegs()) { for (RegisterArg mthArg : mth.getArgRegs()) {
initCodeVar(mthArg.getSVar()); initCodeVar(mth, mthArg);
} }
for (SSAVar ssaVar : mth.getSVars()) { for (SSAVar ssaVar : mth.getSVars()) {
initCodeVar(ssaVar); initCodeVar(ssaVar);
} }
} }
public static void initCodeVar(MethodNode mth, RegisterArg regArg) {
SSAVar ssaVar = regArg.getSVar();
if (ssaVar == null) {
ssaVar = mth.makeNewSVar(regArg);
}
initCodeVar(ssaVar);
}
public static void initCodeVar(SSAVar ssaVar) { public static void initCodeVar(SSAVar ssaVar) {
if (ssaVar.isCodeVarSet()) { if (ssaVar.isCodeVarSet()) {
return; return;
@@ -10,7 +10,6 @@ import jadx.core.Consts;
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.MethodInlineAttr; import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
@@ -47,7 +46,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
if (mia != null) { if (mia != null) {
return mia; return mia;
} }
if (canInline(mth)) { if (mth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE)) {
if (mth.getBasicBlocks() == null) { if (mth.getBasicBlocks() == null) {
return null; return null;
} }
@@ -59,14 +58,6 @@ public class MarkMethodsForInline extends AbstractVisitor {
return MethodInlineAttr.inlineNotNeeded(mth); return MethodInlineAttr.inlineNotNeeded(mth);
} }
public static boolean canInline(MethodNode mth) {
if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) {
return false;
}
AccessInfo accessFlags = mth.getAccessFlags();
return accessFlags.isSynthetic() && accessFlags.isStatic();
}
@Nullable @Nullable
private static MethodInlineAttr inlineMth(MethodNode mth) { private static MethodInlineAttr inlineMth(MethodNode mth) {
List<InsnNode> insns = BlockUtils.collectInsnsWithLimit(mth.getBasicBlocks(), 2); List<InsnNode> insns = BlockUtils.collectInsnsWithLimit(mth.getBasicBlocks(), 2);
@@ -79,7 +70,11 @@ public class MarkMethodsForInline extends AbstractVisitor {
if (insn.getType() == InsnType.RETURN && insn.getArgsCount() == 1) { if (insn.getType() == InsnType.RETURN && insn.getArgsCount() == 1) {
// synthetic field getter // synthetic field getter
// set arg from 'return' instruction // set arg from 'return' instruction
return addInlineAttr(mth, InsnNode.wrapArg(insn.getArg(0))); InsnArg arg = insn.getArg(0);
if (!arg.isInsnWrap()) {
return null;
}
return addInlineAttr(mth, ((InsnWrapArg) arg).getWrapInsn());
} }
// method invoke // method invoke
return addInlineAttr(mth, insn); return addInlineAttr(mth, insn);
@@ -111,7 +106,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
InsnType insnType = insn.getType(); InsnType insnType = insn.getType();
if (insnType == InsnType.INVOKE) { if (insnType == InsnType.INVOKE) {
InvokeNode invoke = (InvokeNode) insn; InvokeNode invoke = (InvokeNode) insn;
MethodNode callMthNode = mth.root().deepResolveMethod(invoke.getCallMth()); MethodNode callMthNode = mth.root().resolveMethod(invoke.getCallMth());
if (callMthNode != null) { if (callMthNode != null) {
FixAccessModifiers.changeVisibility(callMthNode, newVisFlag); FixAccessModifiers.changeVisibility(callMthNode, newVisFlag);
} }
@@ -1,7 +1,5 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -19,10 +17,9 @@ import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode; import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ConstClassNode; import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode; import jadx.core.dex.instructions.ConstStringNode;
@@ -46,6 +43,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.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;
import jadx.core.dex.regions.conditions.IfCondition; import jadx.core.dex.regions.conditions.IfCondition;
@@ -116,7 +114,7 @@ public class ModVisitor extends AbstractVisitor {
break; break;
case SWITCH: case SWITCH:
replaceConstKeys(parentClass, (SwitchInsn) insn); replaceConstKeys(mth, parentClass, (SwitchInsn) insn);
break; break;
case NEW_ARRAY: case NEW_ARRAY:
@@ -230,13 +228,14 @@ public class ModVisitor extends AbstractVisitor {
return result == TypeCompareEnum.NARROW; // true if use class is subclass of field class return result == TypeCompareEnum.NARROW; // true if use class is subclass of field class
} }
private static void replaceConstKeys(ClassNode parentClass, SwitchInsn insn) { private static void replaceConstKeys(MethodNode mth, ClassNode parentClass, SwitchInsn insn) {
int[] keys = insn.getKeys(); int[] keys = insn.getKeys();
int len = keys.length; int len = keys.length;
for (int k = 0; k < len; k++) { for (int k = 0; k < len; k++) {
FieldNode f = parentClass.getConstField(keys[k]); FieldNode f = parentClass.getConstField(keys[k]);
if (f != null) { if (f != null) {
insn.modifyKey(k, f); insn.modifyKey(k, f);
f.addUseIn(mth);
} }
} }
} }
@@ -293,6 +292,13 @@ public class ModVisitor extends AbstractVisitor {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private EncodedValue replaceConstValue(ClassNode parentCls, EncodedValue encodedValue) { private EncodedValue replaceConstValue(ClassNode parentCls, EncodedValue encodedValue) {
if (encodedValue.getType() == EncodedType.ENCODED_ANNOTATION) {
IAnnotation annotation = (IAnnotation) encodedValue.getValue();
for (Map.Entry<String, EncodedValue> entry : annotation.getValues().entrySet()) {
entry.setValue(replaceConstValue(parentCls, entry.getValue()));
}
return encodedValue;
}
if (encodedValue.getType() == EncodedType.ENCODED_ARRAY) { if (encodedValue.getType() == EncodedType.ENCODED_ARRAY) {
List<EncodedValue> listVal = (List<EncodedValue>) encodedValue.getValue(); List<EncodedValue> listVal = (List<EncodedValue>) encodedValue.getValue();
if (!listVal.isEmpty()) { if (!listVal.isEmpty()) {
@@ -322,6 +328,7 @@ public class ModVisitor extends AbstractVisitor {
InsnNode inode = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); InsnNode inode = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
inode.setResult(insn.getResult()); inode.setResult(insn.getResult());
replaceInsn(mth, block, i, inode); replaceInsn(mth, block, i, inode);
f.addUseIn(mth);
} }
} }
@@ -334,7 +341,9 @@ public class ModVisitor extends AbstractVisitor {
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg); FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg);
if (f != null) { if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet)); if (arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet))) {
f.addUseIn(mth);
}
} }
} }
} }
@@ -432,96 +441,39 @@ public class ModVisitor extends AbstractVisitor {
return false; return false;
} }
/**
* For args in anonymous constructor invoke apply:
* - forbid inline into constructor call
* - make variables final (compiler require this implicitly)
*/
private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) { private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) {
MethodInfo callMth = co.getCallMth(); IMethodDetails callMthDetails = mth.root().getMethodUtils().getMethodDetails(co);
MethodNode callMthNode = mth.root().resolveMethod(callMth); if (!(callMthDetails instanceof MethodNode)) {
if (callMthNode == null) {
return; return;
} }
MethodNode callMth = (MethodNode) callMthDetails;
ClassNode classNode = callMthNode.getParentClass(); if (!callMth.contains(AFlag.ANONYMOUS_CONSTRUCTOR) || callMth.contains(AFlag.NO_SKIP_ARGS)) {
if (!classNode.isAnonymous()) {
return; return;
} }
if (!mth.getParentClass().getInnerClasses().contains(classNode)) { SkipMethodArgsAttr attr = callMth.get(AType.SKIP_MTH_ARGS);
return; if (attr != null) {
} int argsCount = Math.min(callMth.getMethodInfo().getArgsCount(), co.getArgsCount());
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(callMthNode, co); for (int i = 0; i < argsCount; i++) {
if (argsMap.isEmpty() && !callMthNode.getArgRegs().isEmpty()) { if (attr.isSkip(i)) {
return; anonymousCallArgMod(co.getArg(i));
}
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
FieldNode field = entry.getValue();
if (field == null) {
continue;
}
InsnArg arg = entry.getKey();
field.addAttr(new FieldReplaceAttr(arg));
field.add(AFlag.DONT_GENERATE);
if (arg.isRegister()) {
RegisterArg reg = (RegisterArg) arg;
SSAVar sVar = reg.getSVar();
if (sVar != null) {
sVar.getCodeVar().setFinal(true);
} }
reg.add(AFlag.DONT_INLINE);
reg.add(AFlag.SKIP_ARG);
} }
} else {
// additional info not available apply mods to all args (the safest solution)
co.getArguments().forEach(ModVisitor::anonymousCallArgMod);
} }
} }
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode callMthNode, ConstructorInsn co) { private static void anonymousCallArgMod(InsnArg arg) {
Map<InsnArg, FieldNode> map = new LinkedHashMap<>(); arg.add(AFlag.DONT_INLINE);
MethodInfo callMth = callMthNode.getMethodInfo(); if (arg.isRegister()) {
ClassNode cls = callMthNode.getParentClass(); ((RegisterArg) arg).getSVar().getCodeVar().setFinal(true);
ClassNode parentClass = cls.getParentClass();
List<RegisterArg> argList = callMthNode.getArgRegs();
int startArg = 0;
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(parentClass.getClassInfo().getType())) {
startArg = 1;
} }
int argsCount = argList.size();
for (int i = startArg; i < argsCount; i++) {
RegisterArg arg = argList.get(i);
InsnNode useInsn = getParentInsnSkipMove(arg);
if (useInsn == null) {
return Collections.emptyMap();
}
FieldNode fieldNode = null;
if (useInsn.getType() == InsnType.IPUT) {
FieldInfo field = (FieldInfo) ((IndexInsnNode) useInsn).getIndex();
fieldNode = cls.searchField(field);
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
return Collections.emptyMap();
}
} else if (useInsn.getType() == InsnType.CONSTRUCTOR) {
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
if (!superConstr.isSuper()) {
return Collections.emptyMap();
}
} else {
return Collections.emptyMap();
}
map.put(co.getArg(i), fieldNode);
}
return map;
}
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar.getUseCount() != 1) {
return null;
}
RegisterArg useArg = sVar.getUseList().get(0);
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn == null) {
return null;
}
if (parentInsn.getType() == InsnType.MOVE) {
return getParentInsnSkipMove(parentInsn.getResult());
}
return parentInsn;
} }
/** /**
@@ -575,6 +527,7 @@ public class ModVisitor extends AbstractVisitor {
if (f != null) { if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
filledArr.addArg(InsnArg.wrapArg(fGet)); filledArr.addArg(InsnArg.wrapArg(fGet));
f.addUseIn(mth);
} else { } else {
filledArr.addArg(arg); filledArr.addArg(arg);
} }
@@ -1,11 +1,11 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -17,6 +17,7 @@ import jadx.core.clsp.ClspClass;
import jadx.core.clsp.ClspMethod; import jadx.core.clsp.ClspMethod;
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.MethodBridgeAttr;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr; import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr; import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
@@ -32,6 +33,7 @@ import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@JadxVisitor( @JadxVisitor(
name = "OverrideMethodVisitor", name = "OverrideMethodVisitor",
@@ -45,70 +47,86 @@ public class OverrideMethodVisitor extends AbstractVisitor {
@Override @Override
public boolean visit(ClassNode cls) throws JadxException { public boolean visit(ClassNode cls) throws JadxException {
processCls(cls); SuperTypesData superData = collectSuperTypes(cls);
if (superData != null) {
for (MethodNode mth : cls.getMethods()) {
processMth(mth, superData);
}
}
return true; return true;
} }
private void processCls(ClassNode cls) { private void processMth(MethodNode mth, SuperTypesData superData) {
List<ArgType> superTypes = collectSuperTypes(cls);
if (!superTypes.isEmpty()) {
for (MethodNode mth : cls.getMethods()) {
processMth(cls, superTypes, mth);
}
}
}
private void processMth(ClassNode cls, List<ArgType> superTypes, MethodNode mth) {
if (mth.isConstructor() || mth.getAccessFlags().isStatic() || mth.getAccessFlags().isPrivate()) { if (mth.isConstructor() || mth.getAccessFlags().isStatic() || mth.getAccessFlags().isPrivate()) {
return; return;
} }
MethodOverrideAttr attr = processOverrideMethods(cls, mth, superTypes); MethodOverrideAttr attr = processOverrideMethods(mth, superData);
if (attr != null) { if (attr != null) {
if (attr.getBaseMethods().isEmpty()) {
throw new JadxRuntimeException("No base methods for override attribute: " + attr.getOverrideList());
}
mth.addAttr(attr); mth.addAttr(attr);
IMethodDetails baseMth = Utils.last(attr.getOverrideList()); IMethodDetails baseMth = Utils.getOne(attr.getBaseMethods());
if (baseMth != null) { if (baseMth != null) {
boolean updated = fixMethodReturnType(mth, baseMth, superTypes); boolean updated = fixMethodReturnType(mth, baseMth, superData);
updated |= fixMethodArgTypes(mth, baseMth, superTypes); updated |= fixMethodArgTypes(mth, baseMth, superData);
if (updated && cls.root().getArgs().isRenameValid()) { if (updated) {
// check if new signature cause method collisions // check if new signature cause method collisions
fixMethodSignatureCollisions(mth); checkMethodSignatureCollisions(mth, mth.root().getArgs().isRenameValid());
} }
} }
} }
} }
private MethodOverrideAttr processOverrideMethods(ClassNode cls, MethodNode mth, List<ArgType> superTypes) { private MethodOverrideAttr processOverrideMethods(MethodNode mth, SuperTypesData superData) {
MethodOverrideAttr result = mth.get(AType.METHOD_OVERRIDE); MethodOverrideAttr result = mth.get(AType.METHOD_OVERRIDE);
if (result != null) { if (result != null) {
return result; return result;
} }
ClassNode cls = mth.getParentClass();
String signature = mth.getMethodInfo().makeSignature(false); String signature = mth.getMethodInfo().makeSignature(false);
List<IMethodDetails> overrideList = new ArrayList<>(); List<IMethodDetails> overrideList = new ArrayList<>();
for (ArgType superType : superTypes) { Set<IMethodDetails> baseMethods = new HashSet<>();
ClassNode classNode = cls.root().resolveClass(superType); for (ArgType superType : superData.getSuperTypes()) {
ClassNode classNode = mth.root().resolveClass(superType);
if (classNode != null) { if (classNode != null) {
MethodNode ovrdMth = searchOverriddenMethod(classNode, signature); MethodNode ovrdMth = searchOverriddenMethod(classNode, signature);
if (ovrdMth != null && isMethodVisibleInCls(ovrdMth, cls)) { if (ovrdMth != null) {
overrideList.add(ovrdMth); if (isMethodVisibleInCls(ovrdMth, cls)) {
MethodOverrideAttr attr = ovrdMth.get(AType.METHOD_OVERRIDE); overrideList.add(ovrdMth);
if (attr != null) { MethodOverrideAttr attr = ovrdMth.get(AType.METHOD_OVERRIDE);
return buildOverrideAttr(mth, overrideList, attr); if (attr != null) {
addBaseMethod(superData, overrideList, baseMethods, superType);
return buildOverrideAttr(mth, overrideList, baseMethods, attr);
}
} }
} }
} else { } else {
ClspClass clsDetails = cls.root().getClsp().getClsDetails(superType); ClspClass clsDetails = mth.root().getClsp().getClsDetails(superType);
if (clsDetails != null) { if (clsDetails != null) {
Map<String, ClspMethod> methodsMap = clsDetails.getMethodsMap(); Map<String, ClspMethod> methodsMap = clsDetails.getMethodsMap();
for (Map.Entry<String, ClspMethod> entry : methodsMap.entrySet()) { for (Map.Entry<String, ClspMethod> entry : methodsMap.entrySet()) {
String mthShortId = entry.getKey(); String mthShortId = entry.getKey();
if (mthShortId.startsWith(signature)) { if (mthShortId.startsWith(signature)) {
overrideList.add(entry.getValue()); overrideList.add(entry.getValue());
break;
} }
} }
} }
} }
addBaseMethod(superData, overrideList, baseMethods, superType);
}
return buildOverrideAttr(mth, overrideList, baseMethods, null);
}
private void addBaseMethod(SuperTypesData superData, List<IMethodDetails> overrideList, Set<IMethodDetails> baseMethods,
ArgType superType) {
if (superData.getEndTypes().contains(superType.getObject())) {
IMethodDetails last = Utils.last(overrideList);
if (last != null) {
baseMethods.add(last);
}
} }
return buildOverrideAttr(mth, overrideList, null);
} }
@Nullable @Nullable
@@ -123,22 +141,24 @@ public class OverrideMethodVisitor extends AbstractVisitor {
@Nullable @Nullable
private MethodOverrideAttr buildOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList, private MethodOverrideAttr buildOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList,
@Nullable MethodOverrideAttr attr) { Set<IMethodDetails> baseMethods, @Nullable MethodOverrideAttr attr) {
if (overrideList.isEmpty() && attr == null) { if (overrideList.isEmpty() && attr == null) {
return null; return null;
} }
if (attr == null) { if (attr == null) {
// traced to base method // traced to base method
List<IMethodDetails> cleanOverrideList = overrideList.stream().distinct().collect(Collectors.toList()); List<IMethodDetails> cleanOverrideList = overrideList.stream().distinct().collect(Collectors.toList());
return applyOverrideAttr(mth, cleanOverrideList, false); return applyOverrideAttr(mth, cleanOverrideList, baseMethods, false);
} }
// trace stopped at already processed method -> start merging // trace stopped at already processed method -> start merging
List<IMethodDetails> mergedOverrideList = Utils.mergeLists(overrideList, attr.getOverrideList()); List<IMethodDetails> mergedOverrideList = Utils.mergeLists(overrideList, attr.getOverrideList());
List<IMethodDetails> cleanOverrideList = mergedOverrideList.stream().distinct().collect(Collectors.toList()); List<IMethodDetails> cleanOverrideList = mergedOverrideList.stream().distinct().collect(Collectors.toList());
return applyOverrideAttr(mth, cleanOverrideList, true); Set<IMethodDetails> mergedBaseMethods = Utils.mergeSets(baseMethods, attr.getBaseMethods());
return applyOverrideAttr(mth, cleanOverrideList, mergedBaseMethods, true);
} }
private MethodOverrideAttr applyOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList, boolean update) { private MethodOverrideAttr applyOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList,
Set<IMethodDetails> baseMethods, boolean update) {
// don't rename method if override list contains not resolved method // don't rename method if override list contains not resolved method
boolean dontRename = overrideList.stream().anyMatch(m -> !(m instanceof MethodNode)); boolean dontRename = overrideList.stream().anyMatch(m -> !(m instanceof MethodNode));
SortedSet<MethodNode> relatedMethods = null; SortedSet<MethodNode> relatedMethods = null;
@@ -188,10 +208,10 @@ public class OverrideMethodVisitor extends AbstractVisitor {
continue; continue;
} }
} }
mthNode.addAttr(new MethodOverrideAttr(Utils.listTail(overrideList, depth), relatedMethods)); mthNode.addAttr(new MethodOverrideAttr(Utils.listTail(overrideList, depth), relatedMethods, baseMethods));
depth++; depth++;
} }
return new MethodOverrideAttr(overrideList, relatedMethods); return new MethodOverrideAttr(overrideList, relatedMethods, baseMethods);
} }
@NotNull @NotNull
@@ -221,52 +241,92 @@ public class OverrideMethodVisitor extends AbstractVisitor {
return Objects.equals(superMth.getParentClass().getPackage(), cls.getPackage()); return Objects.equals(superMth.getParentClass().getPackage(), cls.getPackage());
} }
private List<ArgType> collectSuperTypes(ClassNode cls) { private static final class SuperTypesData {
Map<String, ArgType> superTypes = new LinkedHashMap<>(); private final List<ArgType> superTypes;
collectSuperTypes(cls, superTypes); private final Set<String> endTypes;
if (superTypes.isEmpty()) {
return Collections.emptyList(); private SuperTypesData(List<ArgType> superTypes, Set<String> endTypes) {
this.superTypes = superTypes;
this.endTypes = endTypes;
}
public List<ArgType> getSuperTypes() {
return superTypes;
}
public Set<String> getEndTypes() {
return endTypes;
} }
return new ArrayList<>(superTypes.values());
} }
private void collectSuperTypes(ClassNode cls, Map<String, ArgType> superTypes) { @Nullable
private SuperTypesData collectSuperTypes(ClassNode cls) {
List<ArgType> superTypes = new ArrayList<>();
Set<String> endTypes = new HashSet<>();
collectSuperTypes(cls, superTypes, endTypes);
if (superTypes.isEmpty()) {
return null;
}
if (endTypes.isEmpty()) {
throw new JadxRuntimeException("No end types in class hierarchy: " + cls);
}
return new SuperTypesData(superTypes, endTypes);
}
private void collectSuperTypes(ClassNode cls, List<ArgType> superTypes, Set<String> endTypes) {
RootNode root = cls.root(); RootNode root = cls.root();
int k = 0;
ArgType superClass = cls.getSuperClass(); ArgType superClass = cls.getSuperClass();
if (superClass != null && !Objects.equals(superClass, ArgType.OBJECT)) { if (superClass != null) {
addSuperType(root, superTypes, superClass); k += addSuperType(root, superTypes, endTypes, superClass);
} }
for (ArgType iface : cls.getInterfaces()) { for (ArgType iface : cls.getInterfaces()) {
addSuperType(root, superTypes, iface); k += addSuperType(root, superTypes, endTypes, iface);
}
if (k == 0) {
endTypes.add(cls.getType().getObject());
} }
} }
private void addSuperType(RootNode root, Map<String, ArgType> superTypesMap, ArgType superType) { private int addSuperType(RootNode root, List<ArgType> superTypesMap, Set<String> endTypes, ArgType superType) {
superTypesMap.put(superType.getObject(), superType); if (Objects.equals(superType, ArgType.OBJECT)) {
return 0;
}
superTypesMap.add(superType);
ClassNode classNode = root.resolveClass(superType); ClassNode classNode = root.resolveClass(superType);
if (classNode == null) { if (classNode != null) {
for (String superCls : root.getClsp().getSuperTypes(superType.getObject())) { collectSuperTypes(classNode, superTypesMap, endTypes);
ArgType type = ArgType.object(superCls); return 1;
superTypesMap.put(type.getObject(), type);
}
} else {
collectSuperTypes(classNode, superTypesMap);
} }
ClspClass clsDetails = root.getClsp().getClsDetails(superType);
if (clsDetails != null) {
int k = 0;
for (ArgType parentType : clsDetails.getParents()) {
k += addSuperType(root, superTypesMap, endTypes, parentType);
}
if (k == 0) {
endTypes.add(superType.getObject());
}
return 1;
}
// no info found => treat as hierarchy end
endTypes.add(superType.getObject());
return 1;
} }
private boolean fixMethodReturnType(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) { private boolean fixMethodReturnType(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData) {
ArgType returnType = mth.getReturnType(); ArgType returnType = mth.getReturnType();
if (returnType == ArgType.VOID) { if (returnType == ArgType.VOID) {
return false; return false;
} }
boolean updated = updateReturnType(mth, baseMth, superTypes); boolean updated = updateReturnType(mth, baseMth, superData);
if (updated) { if (updated) {
mth.addDebugComment("Return type fixed from '" + returnType + "' to match base method"); mth.addDebugComment("Return type fixed from '" + returnType + "' to match base method");
} }
return updated; return updated;
} }
private boolean updateReturnType(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) { private boolean updateReturnType(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData) {
ArgType baseReturnType = baseMth.getReturnType(); ArgType baseReturnType = baseMth.getReturnType();
if (mth.getReturnType().equals(baseReturnType)) { if (mth.getReturnType().equals(baseReturnType)) {
return false; return false;
@@ -276,7 +336,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
} }
TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare(); TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare();
ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType(); ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType();
for (ArgType superType : superTypes) { for (ArgType superType : superData.getSuperTypes()) {
TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls); TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls);
if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) { if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) {
ArgType targetRetType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseReturnType); ArgType targetRetType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseReturnType);
@@ -291,7 +351,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
return false; return false;
} }
private boolean fixMethodArgTypes(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) { private boolean fixMethodArgTypes(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData) {
List<ArgType> mthArgTypes = mth.getArgTypes(); List<ArgType> mthArgTypes = mth.getArgTypes();
List<ArgType> baseArgTypes = baseMth.getArgTypes(); List<ArgType> baseArgTypes = baseMth.getArgTypes();
if (mthArgTypes.equals(baseArgTypes)) { if (mthArgTypes.equals(baseArgTypes)) {
@@ -304,7 +364,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
boolean changed = false; boolean changed = false;
List<ArgType> newArgTypes = new ArrayList<>(argCount); List<ArgType> newArgTypes = new ArrayList<>(argCount);
for (int argNum = 0; argNum < argCount; argNum++) { for (int argNum = 0; argNum < argCount; argNum++) {
ArgType newType = updateArgType(mth, baseMth, superTypes, argNum); ArgType newType = updateArgType(mth, baseMth, superData, argNum);
if (newType != null) { if (newType != null) {
changed = true; changed = true;
newArgTypes.add(newType); newArgTypes.add(newType);
@@ -318,7 +378,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
return changed; return changed;
} }
private ArgType updateArgType(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes, int argNum) { private ArgType updateArgType(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData, int argNum) {
ArgType arg = mth.getArgTypes().get(argNum); ArgType arg = mth.getArgTypes().get(argNum);
ArgType baseArg = baseMth.getArgTypes().get(argNum); ArgType baseArg = baseMth.getArgTypes().get(argNum);
if (arg.equals(baseArg)) { if (arg.equals(baseArg)) {
@@ -329,7 +389,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
} }
TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare(); TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare();
ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType(); ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType();
for (ArgType superType : superTypes) { for (ArgType superType : superData.getSuperTypes()) {
TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls); TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls);
if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) { if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) {
ArgType targetArgType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseArg); ArgType targetArgType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseArg);
@@ -343,7 +403,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
return null; return null;
} }
private void fixMethodSignatureCollisions(MethodNode mth) { private void checkMethodSignatureCollisions(MethodNode mth, boolean rename) {
String mthName = mth.getMethodInfo().getAlias(); String mthName = mth.getMethodInfo().getAlias();
String newSignature = MethodInfo.makeShortId(mthName, mth.getArgTypes(), null); String newSignature = MethodInfo.makeShortId(mthName, mth.getArgTypes(), null);
for (MethodNode otherMth : mth.getParentClass().getMethods()) { for (MethodNode otherMth : mth.getParentClass().getMethods()) {
@@ -351,12 +411,16 @@ public class OverrideMethodVisitor extends AbstractVisitor {
if (otherMthName.equals(mthName) && otherMth != mth) { if (otherMthName.equals(mthName) && otherMth != mth) {
String otherSignature = otherMth.getMethodInfo().makeSignature(true, false); String otherSignature = otherMth.getMethodInfo().makeSignature(true, false);
if (otherSignature.equals(newSignature)) { if (otherSignature.equals(newSignature)) {
if (otherMth.contains(AFlag.DONT_RENAME) || otherMth.contains(AType.METHOD_OVERRIDE)) { if (rename) {
otherMth.addWarnComment("Can't rename method to resolve collision"); if (otherMth.contains(AFlag.DONT_RENAME) || otherMth.contains(AType.METHOD_OVERRIDE)) {
} else { otherMth.addWarnComment("Can't rename method to resolve collision");
otherMth.getMethodInfo().setAlias(makeNewAlias(otherMth)); } else {
otherMth.addAttr(new RenameReasonAttr("avoid collision after fix types in other method")); otherMth.getMethodInfo().setAlias(makeNewAlias(otherMth));
otherMth.addAttr(new RenameReasonAttr("avoid collision after fix types in other method"));
}
} }
otherMth.addAttr(new MethodBridgeAttr(mth));
return;
} }
} }
} }
@@ -5,18 +5,28 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.IFieldRef;
import jadx.api.plugins.input.data.annotations.AnnotationVisibility;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp; import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
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;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
@@ -24,6 +34,7 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.instructions.mods.TernaryInsn; import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnContainer; import jadx.core.dex.nodes.InsnContainer;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
@@ -53,6 +64,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
if (cls.root().getArgs().isDebugInfo()) { if (cls.root().getArgs().isDebugInfo()) {
setClassSourceLine(cls); setClassSourceLine(cls);
} }
collectFieldsUsageInAnnotations(cls);
return true; return true;
} }
@@ -69,8 +81,10 @@ public class PrepareForCodeGen extends AbstractVisitor {
checkInline(block); checkInline(block);
removeParenthesis(block); removeParenthesis(block);
modifyArith(block); modifyArith(block);
checkConstUsage(block);
} }
moveConstructorInConstructor(mth); moveConstructorInConstructor(mth);
collectFieldsUsageInAnnotations(mth, mth);
} }
private static void removeInstructions(BlockNode block) { private static void removeInstructions(BlockNode block) {
@@ -122,6 +136,38 @@ public class PrepareForCodeGen extends AbstractVisitor {
} }
} }
/**
* Add explicit type for non int constants
*/
private static void checkConstUsage(BlockNode block) {
for (InsnNode blockInsn : block.getInstructions()) {
blockInsn.visitInsns(insn -> {
if (forbidExplicitType(insn.getType())) {
return;
}
for (InsnArg arg : insn.getArguments()) {
if (arg.isLiteral() && arg.getType() != ArgType.INT) {
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
}
}
});
}
}
private static boolean forbidExplicitType(InsnType type) {
switch (type) {
case CONST:
case CAST:
case IF:
case FILLED_NEW_ARRAY:
case APUT:
case ARITH:
return true;
default:
return false;
}
}
private static void removeParenthesis(BlockNode block) { private static void removeParenthesis(BlockNode block) {
for (InsnNode insn : block.getInstructions()) { for (InsnNode insn : block.getInstructions()) {
removeParenthesis(insn); removeParenthesis(insn);
@@ -276,4 +322,61 @@ public class PrepareForCodeGen extends AbstractVisitor {
cls.setSourceLine(minLine - 1); cls.setSourceLine(minLine - 1);
} }
} }
private void collectFieldsUsageInAnnotations(ClassNode cls) {
MethodNode useMth = cls.getDefaultConstructor();
if (useMth == null && !cls.getMethods().isEmpty()) {
useMth = cls.getMethods().get(0);
}
if (useMth == null) {
return;
}
collectFieldsUsageInAnnotations(useMth, cls);
MethodNode finalUseMth = useMth;
cls.getFields().forEach(f -> collectFieldsUsageInAnnotations(finalUseMth, f));
}
private void collectFieldsUsageInAnnotations(MethodNode mth, AttrNode attrNode) {
AnnotationsAttr annotationsList = attrNode.get(JadxAttrType.ANNOTATION_LIST);
if (annotationsList == null) {
return;
}
for (IAnnotation annotation : annotationsList.getAll()) {
if (annotation.getVisibility() == AnnotationVisibility.SYSTEM) {
continue;
}
for (Map.Entry<String, EncodedValue> entry : annotation.getValues().entrySet()) {
checkEncodedValue(mth, entry.getValue());
}
}
}
@SuppressWarnings("unchecked")
private void checkEncodedValue(MethodNode mth, EncodedValue encodedValue) {
switch (encodedValue.getType()) {
case ENCODED_FIELD:
Object fieldData = encodedValue.getValue();
FieldInfo fieldInfo;
if (fieldData instanceof IFieldRef) {
fieldInfo = FieldInfo.fromRef(mth.root(), (IFieldRef) fieldData);
} else {
fieldInfo = (FieldInfo) fieldData;
}
FieldNode fieldNode = mth.root().resolveField(fieldInfo);
if (fieldNode != null) {
fieldNode.addUseIn(mth);
}
break;
case ENCODED_ANNOTATION:
IAnnotation annotation = (IAnnotation) encodedValue.getValue();
annotation.getValues().forEach((k, v) -> checkEncodedValue(mth, v));
break;
case ENCODED_ARRAY:
List<EncodedValue> valueList = (List<EncodedValue>) encodedValue.getValue();
valueList.forEach(v -> checkEncodedValue(mth, v));
break;
}
}
} }
@@ -1,11 +1,26 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
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.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.usage.UsageInfoVisitor; import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
@JadxVisitor( @JadxVisitor(
@@ -17,95 +32,266 @@ import jadx.core.utils.exceptions.JadxException;
) )
public class ProcessAnonymous extends AbstractVisitor { public class ProcessAnonymous extends AbstractVisitor {
private boolean inlineAnonymous; private boolean inlineAnonymousClasses;
@Override @Override
public void init(RootNode root) { public void init(RootNode root) {
inlineAnonymous = root.getArgs().isInlineAnonymousClasses(); inlineAnonymousClasses = root.getArgs().isInlineAnonymousClasses();
if (!inlineAnonymousClasses) {
return;
}
for (ClassNode cls : root.getClasses()) {
markAnonymousClass(cls);
}
mergeAnonymousDeps(root);
} }
@Override @Override
public boolean visit(ClassNode cls) throws JadxException { public boolean visit(ClassNode cls) throws JadxException {
if (!inlineAnonymous) { if (inlineAnonymousClasses && cls.contains(AFlag.CLASS_UNLOADED)) {
return false; // enter only on class reload
visitClassAndInners(cls);
} }
return false;
}
private void visitClassAndInners(ClassNode cls) {
markAnonymousClass(cls); markAnonymousClass(cls);
return true; cls.getInnerClasses().forEach(this::visitClassAndInners);
} }
private static void markAnonymousClass(ClassNode cls) { private static void markAnonymousClass(ClassNode cls) {
if (usedOnlyOnce(cls) || isAnonymous(cls) || isLambdaCls(cls)) { if (!canBeAnonymous(cls)) {
if (isStaticFieldUsedOutside(cls)) { return;
}
MethodNode anonymousConstructor = checkUsage(cls);
if (anonymousConstructor == null) {
return;
}
ArgType baseType = getBaseType(cls);
if (baseType == null) {
return;
}
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
cls.addAttr(new AnonymousClassAttr(outerCls, baseType));
cls.add(AFlag.DONT_GENERATE);
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
// force anonymous class to be processed before outer class,
// actual usage of outer class will be removed at anonymous class process,
// see ModVisitor.processAnonymousConstructor method
ClassNode topOuterCls = outerCls.getTopParentClass();
cls.removeDependency(topOuterCls);
ListUtils.safeRemove(outerCls.getUseIn(), cls);
// move dependency to codegen stage
if (cls.isTopClass()) {
topOuterCls.removeDependency(cls);
topOuterCls.addCodegenDep(cls);
}
}
private static void undoAnonymousMark(ClassNode cls) {
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
ClassNode outerCls = attr.getOuterCls();
cls.setDependencies(ListUtils.safeAdd(cls.getDependencies(), outerCls.getTopParentClass()));
outerCls.setUseIn(ListUtils.safeAdd(outerCls.getUseIn(), cls));
cls.remove(AType.ANONYMOUS_CLASS);
cls.remove(AFlag.DONT_GENERATE);
for (MethodNode mth : cls.getMethods()) {
if (mth.isConstructor()) {
mth.remove(AFlag.ANONYMOUS_CONSTRUCTOR);
}
}
cls.addDebugComment("Anonymous mark cleared");
}
private void mergeAnonymousDeps(RootNode root) {
// Collect edges to build bidirectional tree:
// inline edge: anonymous -> outer (one-to-one)
// use edges: outer -> *anonymous (one-to-many)
Map<ClassNode, ClassNode> inlineMap = new HashMap<>();
Map<ClassNode, List<ClassNode>> useMap = new HashMap<>();
for (ClassNode anonymousCls : root.getClasses()) {
AnonymousClassAttr attr = anonymousCls.get(AType.ANONYMOUS_CLASS);
if (attr != null) {
ClassNode outerCls = attr.getOuterCls();
List<ClassNode> list = useMap.get(outerCls);
if (list == null || list.isEmpty()) {
list = new ArrayList<>(2);
useMap.put(outerCls, list);
}
list.add(anonymousCls);
useMap.putIfAbsent(anonymousCls, Collections.emptyList()); // put leaf explicitly
inlineMap.put(anonymousCls, outerCls);
}
}
if (inlineMap.isEmpty()) {
return;
}
// starting from leaf process deps in nodes up to root
Set<ClassNode> added = new HashSet<>();
useMap.forEach((key, list) -> {
if (list.isEmpty()) {
added.clear();
updateDeps(key, inlineMap, added);
}
});
for (ClassNode cls : root.getClasses()) {
List<ClassNode> deps = cls.getCodegenDeps();
if (deps.size() > 1) {
// distinct sorted dep, reusing collections to reduce memory allocations :)
added.clear();
added.addAll(deps);
deps.clear();
deps.addAll(added);
Collections.sort(deps);
}
}
}
private void updateDeps(ClassNode leafCls, Map<ClassNode, ClassNode> inlineMap, Set<ClassNode> added) {
ClassNode topNode;
ClassNode current = leafCls;
while (true) {
if (!added.add(current)) {
current.addWarnComment("Loop in anonymous inline: " + current + ", path: " + added);
added.forEach(ProcessAnonymous::undoAnonymousMark);
return; return;
} }
cls.add(AFlag.ANONYMOUS_CLASS); ClassNode next = inlineMap.get(current);
cls.add(AFlag.DONT_GENERATE); if (next == null) {
topNode = current.getTopParentClass();
for (MethodNode mth : cls.getMethods()) { break;
if (mth.isConstructor()) {
mth.add(AFlag.ANONYMOUS_CONSTRUCTOR);
}
} }
current = next;
}
if (added.size() <= 2) {
// first level deps already processed
return;
}
List<ClassNode> deps = topNode.getCodegenDeps();
if (deps.isEmpty()) {
deps = new ArrayList<>(added.size());
topNode.setCodegenDeps(deps);
}
for (ClassNode add : added) {
deps.add(add.getTopParentClass());
} }
} }
private static boolean isStaticFieldUsedOutside(ClassNode cls) { private static boolean canBeAnonymous(ClassNode cls) {
ClassNode topCls = cls.getTopParentClass(); if (cls.getAccessFlags().isSynthetic()) {
for (FieldNode field : cls.getFields()) { return true;
if (field.isStatic()) { }
for (MethodNode useMth : field.getUseIn()) { String shortName = cls.getClassInfo().getShortName();
ClassNode useCls = useMth.getParentClass().getTopParentClass(); if (shortName.contains("$") || Character.isDigit(shortName.charAt(0))) {
if (!useCls.equals(topCls)) { return true;
return true;
}
}
}
} }
return false;
}
private static boolean usedOnlyOnce(ClassNode cls) {
if (cls.getUseIn().size() == 1 && cls.getUseInMth().size() == 1) { if (cls.getUseIn().size() == 1 && cls.getUseInMth().size() == 1) {
// used only once MethodNode useMth = cls.getUseInMth().get(0);
boolean synthetic = cls.getAccessFlags().isSynthetic() || cls.getClassInfo().getShortName().contains("$"); // allow use in enum class init
if (synthetic) { return useMth.getMethodInfo().isClassInit() && useMth.getParentClass().isEnum();
// must have only one constructor which used only once
MethodNode ctr = null;
for (MethodNode mth : cls.getMethods()) {
if (mth.isConstructor()) {
if (ctr != null) {
ctr = null;
break;
}
ctr = mth;
}
}
return ctr != null && ctr.getUseIn().size() == 1;
}
} }
return false; return false;
} }
private static boolean isAnonymous(ClassNode cls) { /**
return cls.getClassInfo().isInner() * Checks:
&& Character.isDigit(cls.getClassInfo().getShortName().charAt(0)) * - class have only one constructor which used only once (allow common code for field init)
&& cls.getMethods().stream().filter(MethodNode::isConstructor).count() == 1; * - methods or fields not used outside (allow only nested inner classes with synthetic usage)
} *
* @return anonymous constructor method
private static boolean isLambdaCls(ClassNode cls) { */
return cls.getAccessFlags().isSynthetic() private static MethodNode checkUsage(ClassNode cls) {
&& cls.getAccessFlags().isFinal() MethodNode ctr = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
&& cls.getClassInfo().getRawName().contains(".-$$Lambda$") if (ctr == null) {
&& countStaticFields(cls) == 0; return null;
} }
if (ctr.getUseIn().size() != 1) {
private static int countStaticFields(ClassNode cls) { // check if used in common field init in all constructors
int c = 0; if (!checkForCommonFieldInit(ctr)) {
for (FieldNode field : cls.getFields()) { return null;
if (field.getAccessFlags().isStatic()) {
c++;
} }
} }
return c; MethodNode ctrUseMth = ctr.getUseIn().get(0);
ClassNode ctrUseCls = ctrUseMth.getParentClass();
if (ctrUseCls.equals(cls)) {
// exclude self usage
return null;
}
for (MethodNode mth : cls.getMethods()) {
if (mth == ctr) {
continue;
}
for (MethodNode useMth : mth.getUseIn()) {
if (useMth.equals(ctrUseMth)) {
continue;
}
if (badMethodUsage(cls, useMth, mth.getAccessFlags())) {
return null;
}
}
}
for (FieldNode field : cls.getFields()) {
for (MethodNode useMth : field.getUseIn()) {
if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
return null;
}
}
}
return ctr;
}
private static boolean badMethodUsage(ClassNode cls, MethodNode useMth, AccessInfo accessFlags) {
ClassNode useCls = useMth.getParentClass();
if (useCls.equals(cls)) {
return false;
}
if (accessFlags.isSynthetic()) {
// allow synthetic usage in inner class
return !useCls.getParentClass().equals(cls);
}
return true;
}
/**
* Checks:
* + all in constructors
* + all usage in one class
* - same field put (ignored: methods not loaded yet)
*/
private static boolean checkForCommonFieldInit(MethodNode ctrMth) {
List<MethodNode> ctrUse = ctrMth.getUseIn();
if (ctrUse.isEmpty()) {
return false;
}
ClassNode firstUseCls = ctrUse.get(0).getParentClass();
return ListUtils.allMatch(ctrUse, m -> m.isConstructor() && m.getParentClass().equals(firstUseCls));
}
@Nullable
private static ArgType getBaseType(ClassNode cls) {
int interfacesCount = cls.getInterfaces().size();
if (interfacesCount > 1) {
return null;
}
ArgType superCls = cls.getSuperClass();
if (superCls == null || superCls.equals(ArgType.OBJECT)) {
if (interfacesCount == 1) {
return cls.getInterfaces().get(0);
}
return ArgType.OBJECT;
}
if (interfacesCount == 0) {
return superCls;
}
// check if super class already implement that interface (weird case)
ArgType interfaceType = cls.getInterfaces().get(0);
if (cls.root().getClsp().isImplements(superCls.getObject(), interfaceType.getObject())) {
return superCls;
}
return null;
} }
} }
@@ -0,0 +1,60 @@
package jadx.core.dex.visitors;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
name = "ProcessMethodsForInline",
desc = "Mark methods for future inline",
runAfter = {
UsageInfoVisitor.class
}
)
public class ProcessMethodsForInline extends AbstractVisitor {
private boolean inlineMethods;
@Override
public void init(RootNode root) {
inlineMethods = root.getArgs().isInlineMethods();
}
@Override
public boolean visit(ClassNode cls) throws JadxException {
if (!inlineMethods) {
return false;
}
for (MethodNode mth : cls.getMethods()) {
if (canInline(mth)) {
mth.add(AFlag.METHOD_CANDIDATE_FOR_INLINE);
fixClassDependencies(mth);
}
}
return true;
}
private static boolean canInline(MethodNode mth) {
if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) {
return false;
}
AccessInfo accessFlags = mth.getAccessFlags();
boolean isSynthetic = accessFlags.isSynthetic() || mth.getName().contains("$");
return isSynthetic && accessFlags.isStatic();
}
private static void fixClassDependencies(MethodNode mth) {
ClassNode parentClass = mth.getTopParentClass();
for (MethodNode useInMth : mth.getUseIn()) {
// remove possible cross dependency to force class with inline method to be processed before its
// usage
ClassNode useTopCls = useInMth.getTopParentClass();
parentClass.setDependencies(ListUtils.safeRemoveAndTrim(parentClass.getDependencies(), useTopCls));
}
}
}
@@ -164,7 +164,9 @@ public class ReSugarCode extends AbstractVisitor {
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg((LiteralArg) valueArg); FieldNode f = mth.getParentClass().getConstFieldByLiteralArg((LiteralArg) valueArg);
if (f != null) { if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
return InsnArg.wrapArg(fGet); InsnArg arg = InsnArg.wrapArg(fGet);
f.addUseIn(mth);
return arg;
} }
} }
return valueArg.duplicate(); return valueArg.duplicate();
@@ -11,6 +11,7 @@ import jadx.api.JadxArgs;
import jadx.api.plugins.utils.ZipSecurity; import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
@@ -34,7 +35,10 @@ public class SaveCode {
if (codeStr.isEmpty()) { if (codeStr.isEmpty()) {
return; return;
} }
String fileName = cls.getClassInfo().getAliasFullPath() + getFileExtension(cls); if (cls.root().getArgs().isSkipFilesSave()) {
return;
}
String fileName = cls.getClassInfo().getAliasFullPath() + getFileExtension(cls.root());
save(codeStr, dir, fileName); save(codeStr, dir, fileName);
} }
@@ -58,8 +62,8 @@ public class SaveCode {
} }
} }
private static String getFileExtension(ClassNode cls) { public static String getFileExtension(RootNode root) {
JadxArgs.OutputFormatEnum outputFormat = cls.root().getArgs().getOutputFormat(); JadxArgs.OutputFormatEnum outputFormat = root.getArgs().getOutputFormat();
switch (outputFormat) { switch (outputFormat) {
case JAVA: case JAVA:
return ".java"; return ".java";
@@ -80,11 +80,15 @@ public class SignatureProcessor extends AbstractVisitor {
} }
ClassNode cls = field.getParentClass(); ClassNode cls = field.getParentClass();
try { try {
ArgType gType = sp.consumeType(); ArgType signatureType = sp.consumeType();
if (gType == null) { if (signatureType == null) {
return; return;
} }
ArgType type = root.getTypeUtils().expandTypeVariables(cls, gType); if (!validateInnerType(signatureType)) {
field.addWarnComment("Incorrect inner types in field signature: " + sp.getSignature());
return;
}
ArgType type = root.getTypeUtils().expandTypeVariables(cls, signatureType);
if (!validateParsedType(type, field.getType())) { if (!validateParsedType(type, field.getType())) {
cls.addWarnComment("Incorrect field signature: " + sp.getSignature()); cls.addWarnComment("Incorrect field signature: " + sp.getSignature());
return; return;
@@ -105,6 +109,11 @@ public class SignatureProcessor extends AbstractVisitor {
List<ArgType> parsedArgTypes = sp.consumeMethodArgs(mth.getMethodInfo().getArgsCount()); List<ArgType> parsedArgTypes = sp.consumeMethodArgs(mth.getMethodInfo().getArgsCount());
ArgType parsedRetType = sp.consumeType(); ArgType parsedRetType = sp.consumeType();
if (!validateInnerType(parsedRetType) || !validateInnerType(parsedArgTypes)) {
mth.addWarnComment("Incorrect inner types in method signature: " + sp.getSignature());
return;
}
mth.updateTypeParameters(typeParameters); // apply before expand args mth.updateTypeParameters(typeParameters); // apply before expand args
TypeUtils typeUtils = root.getTypeUtils(); TypeUtils typeUtils = root.getTypeUtils();
ArgType retType = typeUtils.expandTypeVariables(mth, parsedRetType); ArgType retType = typeUtils.expandTypeVariables(mth, parsedRetType);
@@ -154,7 +163,7 @@ public class SignatureProcessor extends AbstractVisitor {
return newArgTypes; return newArgTypes;
} }
} }
mth.addWarnComment("Incorrect args count in method signature: " + sp.getSignature()); mth.addDebugComment("Incorrect args count in method signature: " + sp.getSignature());
return null; return null;
} }
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
@@ -172,4 +181,54 @@ public class SignatureProcessor extends AbstractVisitor {
TypeCompareEnum result = root.getTypeCompare().compareTypes(parsedType, currentType); TypeCompareEnum result = root.getTypeCompare().compareTypes(parsedType, currentType);
return result != TypeCompareEnum.CONFLICT; return result != TypeCompareEnum.CONFLICT;
} }
private boolean validateInnerType(List<ArgType> types) {
for (ArgType type : types) {
if (!validateInnerType(type)) {
return false;
}
}
return true;
}
private boolean validateInnerType(ArgType type) {
ArgType innerType = type.getInnerType();
if (innerType == null) {
return true;
}
// check in outer type has inner type as inner class
ArgType outerType = type.getOuterType();
ClassNode outerCls = root.resolveClass(outerType);
if (outerCls == null) {
// can't check class not found
return true;
}
String innerObj;
if (innerType.getOuterType() != null) {
innerObj = innerType.getOuterType().getObject();
// "next" inner type will be processed at end of method
} else {
innerObj = innerType.getObject();
}
if (!innerObj.contains(".")) {
// short reference
for (ClassNode innerClass : outerCls.getInnerClasses()) {
if (innerClass.getShortName().equals(innerObj)) {
return true;
}
}
return false;
}
// full name
ClassNode innerCls = root.resolveClass(innerObj);
if (innerCls == null) {
return false;
}
if (!innerCls.getParentClass().equals(outerCls)) {
// not inner => fixing
outerCls.addInnerClass(innerCls);
innerCls.getClassInfo().convertToInner(outerCls);
}
return validateInnerType(innerType);
}
} }
@@ -38,6 +38,7 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.regions.conditions.IfCondition; import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnList; import jadx.core.utils.InsnList;
import jadx.core.utils.InsnRemover; import jadx.core.utils.InsnRemover;
@@ -82,7 +83,7 @@ public class SimplifyVisitor extends AbstractVisitor {
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
InsnNode insn = list.get(i); InsnNode insn = list.get(i);
int insnCount = list.size(); int insnCount = list.size();
InsnNode modInsn = simplifyInsn(mth, insn); InsnNode modInsn = simplifyInsn(mth, insn, null);
if (modInsn != null) { if (modInsn != null) {
modInsn.rebindArgs(); modInsn.rebindArgs();
if (i < list.size() && list.get(i) == insn) { if (i < list.size() && list.get(i) == insn) {
@@ -110,7 +111,7 @@ public class SimplifyVisitor extends AbstractVisitor {
for (InsnArg arg : insn.getArguments()) { for (InsnArg arg : insn.getArguments()) {
if (arg.isInsnWrap()) { if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn(); InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
InsnNode replaceInsn = simplifyInsn(mth, wrapInsn); InsnNode replaceInsn = simplifyInsn(mth, wrapInsn, insn);
if (replaceInsn != null) { if (replaceInsn != null) {
arg.wrapInstruction(mth, replaceInsn); arg.wrapInstruction(mth, replaceInsn);
InsnRemover.unbindInsn(mth, wrapInsn); InsnRemover.unbindInsn(mth, wrapInsn);
@@ -123,7 +124,7 @@ public class SimplifyVisitor extends AbstractVisitor {
} }
} }
private InsnNode simplifyInsn(MethodNode mth, InsnNode insn) { private InsnNode simplifyInsn(MethodNode mth, InsnNode insn, @Nullable InsnNode parentInsn) {
if (insn.contains(AFlag.DONT_GENERATE)) { if (insn.contains(AFlag.DONT_GENERATE)) {
return null; return null;
} }
@@ -146,8 +147,9 @@ public class SimplifyVisitor extends AbstractVisitor {
case SPUT: case SPUT:
return convertFieldArith(mth, insn); return convertFieldArith(mth, insn);
case CAST:
case CHECK_CAST: case CHECK_CAST:
return processCast(mth, (IndexInsnNode) insn); return processCast(mth, (IndexInsnNode) insn, parentInsn);
case MOVE: case MOVE:
InsnArg firstArg = insn.getArg(0); InsnArg firstArg = insn.getArg(0);
@@ -212,7 +214,7 @@ public class SimplifyVisitor extends AbstractVisitor {
return null; return null;
} }
private static InsnNode processCast(MethodNode mth, IndexInsnNode castInsn) { private static InsnNode processCast(MethodNode mth, IndexInsnNode castInsn, @Nullable InsnNode parentInsn) {
if (castInsn.contains(AFlag.EXPLICIT_CAST)) { if (castInsn.contains(AFlag.EXPLICIT_CAST)) {
return null; return null;
} }
@@ -229,7 +231,8 @@ public class SimplifyVisitor extends AbstractVisitor {
ArgType castToType = (ArgType) castInsn.getIndex(); ArgType castToType = (ArgType) castInsn.getIndex();
if (!ArgType.isCastNeeded(mth.root(), argType, castToType) if (!ArgType.isCastNeeded(mth.root(), argType, castToType)
|| isCastDuplicate(castInsn)) { || isCastDuplicate(castInsn)
|| shadowedByOuterCast(mth.root(), castToType, parentInsn)) {
InsnNode insnNode = new InsnNode(InsnType.MOVE, 1); InsnNode insnNode = new InsnNode(InsnType.MOVE, 1);
insnNode.setOffset(castInsn.getOffset()); insnNode.setOffset(castInsn.getOffset());
insnNode.setResult(castInsn.getResult()); insnNode.setResult(castInsn.getResult());
@@ -254,6 +257,15 @@ public class SimplifyVisitor extends AbstractVisitor {
return false; return false;
} }
private static boolean shadowedByOuterCast(RootNode root, ArgType castType, @Nullable InsnNode parentInsn) {
if (parentInsn != null && parentInsn.getType() == InsnType.CAST) {
ArgType parentCastType = (ArgType) ((IndexInsnNode) parentInsn).getIndex();
TypeCompareEnum result = root.getTypeCompare().compareTypes(parentCastType, castType);
return result.isNarrow();
}
return false;
}
/** /**
* Simplify 'cmp' instruction in if condition * Simplify 'cmp' instruction in if condition
*/ */
@@ -532,32 +544,44 @@ public class SimplifyVisitor extends AbstractVisitor {
if (arith.getArgsCount() != 2) { if (arith.getArgsCount() != 2) {
return null; return null;
} }
InsnArg litArg = null; LiteralArg litArg = null;
InsnArg secondArg = arith.getArg(1); InsnArg secondArg = arith.getArg(1);
if (secondArg.isInsnWrap()) { if (secondArg.isInsnWrap()) {
InsnNode wr = ((InsnWrapArg) secondArg).getWrapInsn(); InsnNode wr = ((InsnWrapArg) secondArg).getWrapInsn();
if (wr.getType() == InsnType.CONST) { if (wr.getType() == InsnType.CONST) {
litArg = wr.getArg(0); InsnArg arg = wr.getArg(0);
if (arg.isLiteral()) {
litArg = (LiteralArg) arg;
}
} }
} else if (secondArg.isLiteral()) { } else if (secondArg.isLiteral()) {
litArg = secondArg; litArg = (LiteralArg) secondArg;
} }
if (litArg != null) { if (litArg == null) {
long lit = ((LiteralArg) litArg).getLiteral(); return null;
// fix 'c + (-1)' => 'c - (1)' }
if (arith.getOp() == ArithOp.ADD && lit < 0) { switch (arith.getOp()) {
return new ArithNode(ArithOp.SUB, case ADD:
arith.getResult(), arith.getArg(0), // fix 'c + (-1)' to 'c - (1)'
InsnArg.lit(-lit, litArg.getType())); if (litArg.isNegative()) {
} LiteralArg negLitArg = litArg.negate();
InsnArg firstArg = arith.getArg(0); if (negLitArg != null) {
if (arith.getOp() == ArithOp.XOR && firstArg.getType() == ArgType.BOOLEAN return new ArithNode(ArithOp.SUB, arith.getResult(), arith.getArg(0), negLitArg);
&& (lit == 0 || lit == 1)) { }
InsnNode node = new InsnNode(lit == 0 ? InsnType.MOVE : InsnType.NOT, 1); }
node.setResult(arith.getResult()); break;
node.addArg(firstArg);
return node; case XOR:
} // simplify xor on boolean
InsnArg firstArg = arith.getArg(0);
long lit = litArg.getLiteral();
if (firstArg.getType() == ArgType.BOOLEAN && (lit == 0 || lit == 1)) {
InsnNode node = new InsnNode(lit == 0 ? InsnType.MOVE : InsnType.NOT, 1);
node.setResult(arith.getResult());
node.addArg(firstArg);
return node;
}
break;
} }
return null; return null;
} }
@@ -126,7 +126,7 @@ public class BlockExceptionHandler {
commonCatchAttr = catchAttr; commonCatchAttr = catchAttr;
continue; continue;
} }
if (commonCatchAttr != catchAttr) { if (!commonCatchAttr.equals(catchAttr)) {
return null; return null;
} }
} }
@@ -390,15 +390,23 @@ public class BlockExceptionHandler {
private static BlockNode searchTopBlock(MethodNode mth, List<BlockNode> blocks) { private static BlockNode searchTopBlock(MethodNode mth, List<BlockNode> blocks) {
BlockNode top = BlockUtils.getTopBlock(blocks); BlockNode top = BlockUtils.getTopBlock(blocks);
if (top != null) { if (top != null) {
return top; return adjustTopBlock(top);
} }
BlockNode topDom = BlockUtils.getCommonDominator(mth, blocks); BlockNode topDom = BlockUtils.getCommonDominator(mth, blocks);
if (topDom != null) { if (topDom != null) {
return topDom; return adjustTopBlock(topDom);
} }
throw new JadxRuntimeException("Failed to find top block for try-catch from: " + blocks); throw new JadxRuntimeException("Failed to find top block for try-catch from: " + blocks);
} }
private static BlockNode adjustTopBlock(BlockNode topBlock) {
if (topBlock.getSuccessors().size() == 1 && !topBlock.contains(AType.EXC_CATCH)) {
// top block can be lifted by other exception handlers included in blocks list, trying to undo that
return topBlock.getSuccessors().get(0);
}
return topBlock;
}
@Nullable @Nullable
private static BlockNode searchBottomBlock(MethodNode mth, List<BlockNode> blocks) { private static BlockNode searchBottomBlock(MethodNode mth, List<BlockNode> blocks) {
// search common post-dominator block inside input set // search common post-dominator block inside input set
@@ -53,6 +53,10 @@ public class BlockProcessor extends AbstractVisitor {
clearBlocksState(mth); clearBlocksState(mth);
computeDominators(mth); computeDominators(mth);
} }
if (FixMultiEntryLoops.process(mth)) {
clearBlocksState(mth);
computeDominators(mth);
}
updateCleanSuccessors(mth); updateCleanSuccessors(mth);
int i = 0; int i = 0;
@@ -347,7 +351,8 @@ public class BlockProcessor extends AbstractVisitor {
successor.add(AFlag.LOOP_START); successor.add(AFlag.LOOP_START);
block.add(AFlag.LOOP_END); block.add(AFlag.LOOP_END);
LoopInfo loop = new LoopInfo(successor, block); Set<BlockNode> loopBlocks = BlockUtils.getAllPathsBlocks(successor, block);
LoopInfo loop = new LoopInfo(successor, block, loopBlocks);
successor.addAttr(AType.LOOP, loop); successor.addAttr(AType.LOOP, loop);
block.addAttr(AType.LOOP, loop); block.addAttr(AType.LOOP, loop);
} }
@@ -192,6 +192,14 @@ public class BlockSplitter extends AbstractVisitor {
return newBlock; return newBlock;
} }
static void copyBlockData(BlockNode from, BlockNode to) {
List<InsnNode> toInsns = to.getInstructions();
for (InsnNode insn : from.getInstructions()) {
toInsns.add(insn.copyWithoutSsa());
}
to.copyAttributesFrom(from);
}
static void replaceTarget(BlockNode source, BlockNode oldTarget, BlockNode newTarget) { static void replaceTarget(BlockNode source, BlockNode oldTarget, BlockNode newTarget) {
InsnNode lastInsn = BlockUtils.getLastInsn(source); InsnNode lastInsn = BlockUtils.getLastInsn(source);
if (lastInsn instanceof TargetInsnNode) { if (lastInsn instanceof TargetInsnNode) {
@@ -0,0 +1,104 @@
package jadx.core.dex.visitors.blocks;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.SpecialEdgeAttr;
import jadx.core.dex.attributes.nodes.SpecialEdgeAttr.SpecialEdgeType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.ListUtils;
public class FixMultiEntryLoops {
public static boolean process(MethodNode mth) {
try {
detectSpecialEdges(mth);
} catch (Exception e) {
mth.addWarnComment("Failed to detect multi-entry loops", e);
return false;
}
List<SpecialEdgeAttr> specialEdges = mth.getAll(AType.SPECIAL_EDGE);
List<SpecialEdgeAttr> multiEntryLoops = specialEdges.stream()
.filter(e -> e.getType() == SpecialEdgeType.BACK_EDGE)
.filter(e -> !isSingleEntryLoop(e))
.collect(Collectors.toList());
if (multiEntryLoops.isEmpty()) {
return false;
}
try {
List<SpecialEdgeAttr> crossEdges = ListUtils.filter(specialEdges, e -> e.getType() == SpecialEdgeType.CROSS_EDGE);
boolean changed = false;
for (SpecialEdgeAttr backEdge : multiEntryLoops) {
changed |= fixLoop(mth, backEdge, crossEdges);
}
return changed;
} catch (Exception e) {
mth.addWarnComment("Failed to fix multi-entry loops", e);
return false;
}
}
private static boolean fixLoop(MethodNode mth, SpecialEdgeAttr backEdge, List<SpecialEdgeAttr> crossEdges) {
BlockNode header = backEdge.getEnd();
BlockNode headerIDom = header.getIDom();
SpecialEdgeAttr subEntry = ListUtils.filterOnlyOne(crossEdges, e -> e.getStart() == headerIDom);
if (subEntry == null || !isSupportedPattern(header, subEntry)) {
// TODO: for now only sub entry in header successor is supported
mth.addWarnComment("Unsupported multi-entry loop pattern (" + backEdge + "). Please submit an issue!!!");
return false;
}
BlockNode loopEnd = backEdge.getStart();
BlockNode subEntryBlock = subEntry.getEnd();
BlockNode copyHeader = BlockSplitter.insertBlockBetween(mth, loopEnd, header);
BlockSplitter.copyBlockData(header, copyHeader);
BlockSplitter.replaceConnection(copyHeader, header, subEntryBlock);
mth.addDebugComment("Duplicate block to fix multi-entry loop: " + backEdge);
return true;
}
private static boolean isSupportedPattern(BlockNode header, SpecialEdgeAttr subEntry) {
return ListUtils.isSingleElement(header.getSuccessors(), subEntry.getEnd());
}
private static boolean isSingleEntryLoop(SpecialEdgeAttr e) {
BlockNode header = e.getEnd();
BlockNode loopEnd = e.getStart();
return header == loopEnd
|| loopEnd.getDoms().get(header.getId()); // header dominates loop end
}
private enum BlockColor {
WHITE, GRAY, BLACK
}
private static void detectSpecialEdges(MethodNode mth) {
List<BlockNode> blocks = mth.getBasicBlocks();
BlockColor[] colors = new BlockColor[blocks.size()];
Arrays.fill(colors, BlockColor.WHITE);
colorDFS(mth, blocks, colors, mth.getEnterBlock().getId());
}
// TODO: transform to non-recursive form
private static void colorDFS(MethodNode mth, List<BlockNode> blocks, BlockColor[] colors, int cur) {
colors[cur] = BlockColor.GRAY;
BlockNode block = blocks.get(cur);
for (BlockNode v : block.getSuccessors()) {
int vId = v.getId();
switch (colors[vId]) {
case WHITE:
colorDFS(mth, blocks, colors, vId);
break;
case GRAY:
mth.addAttr(AType.SPECIAL_EDGE, new SpecialEdgeAttr(SpecialEdgeType.BACK_EDGE, block, v));
break;
case BLACK:
mth.addAttr(AType.SPECIAL_EDGE, new SpecialEdgeAttr(SpecialEdgeType.CROSS_EDGE, block, v));
break;
}
}
colors[cur] = BlockColor.BLACK;
}
}
@@ -3,7 +3,7 @@ package jadx.core.dex.visitors.debuginfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.OptionalInt;
import java.util.Set; import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -73,31 +73,22 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
if (Consts.DEBUG_TYPE_INFERENCE) { if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.info("Apply debug info for method: {}", mth); LOG.info("Apply debug info for method: {}", mth);
} }
mth.getSVars().forEach(ssaVar -> collectVarDebugInfo(mth, ssaVar)); mth.getSVars().forEach(ssaVar -> searchAndApplyVarDebugInfo(mth, ssaVar));
fixLinesForReturn(mth); fixLinesForReturn(mth);
fixNamesForPhiInsns(mth); fixNamesForPhiInsns(mth);
} }
private static void collectVarDebugInfo(MethodNode mth, SSAVar ssaVar) { private static void searchAndApplyVarDebugInfo(MethodNode mth, SSAVar ssaVar) {
Set<RegDebugInfoAttr> debugInfoSet = new HashSet<>(ssaVar.getUseCount() + 1); if (applyDebugInfo(mth, ssaVar, ssaVar.getAssign())) {
addRegDbdInfo(debugInfoSet, ssaVar.getAssign());
ssaVar.getUseList().forEach(registerArg -> addRegDbdInfo(debugInfoSet, registerArg));
int dbgCount = debugInfoSet.size();
if (dbgCount == 0) {
searchDebugInfoByOffset(mth, ssaVar);
return; return;
} }
if (dbgCount == 1) { for (RegisterArg useArg : ssaVar.getUseList()) {
RegDebugInfoAttr debugInfo = debugInfoSet.iterator().next(); if (applyDebugInfo(mth, ssaVar, useArg)) {
applyDebugInfo(mth, ssaVar, debugInfo.getRegType(), debugInfo.getName()); return;
} else {
mth.addInfoComment("Multiple debug info for " + ssaVar + ": " + debugInfoSet);
for (RegDebugInfoAttr debugInfo : debugInfoSet) {
applyDebugInfo(mth, ssaVar, debugInfo.getRegType(), debugInfo.getName());
} }
} }
searchDebugInfoByOffset(mth, ssaVar);
} }
private static void searchDebugInfoByOffset(MethodNode mth, SSAVar ssaVar) { private static void searchDebugInfoByOffset(MethodNode mth, SSAVar ssaVar) {
@@ -105,14 +96,12 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
if (debugInfoAttr == null) { if (debugInfoAttr == null) {
return; return;
} }
Optional<Integer> max = ssaVar.getUseList().stream() OptionalInt max = ssaVar.getUseList().stream().mapToInt(DebugInfoApplyVisitor::getInsnOffsetByArg).max();
.map(DebugInfoApplyVisitor::getInsnOffsetByArg)
.max(Integer::compareTo);
if (!max.isPresent()) { if (!max.isPresent()) {
return; return;
} }
int startOffset = getInsnOffsetByArg(ssaVar.getAssign()); int startOffset = getInsnOffsetByArg(ssaVar.getAssign());
int endOffset = max.get(); int endOffset = max.getAsInt();
int regNum = ssaVar.getRegNum(); int regNum = ssaVar.getRegNum();
for (ILocalVar localVar : debugInfoAttr.getLocalVars()) { for (ILocalVar localVar : debugInfoAttr.getLocalVars()) {
if (localVar.getRegNum() == regNum) { if (localVar.getRegNum() == regNum) {
@@ -144,24 +133,26 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
return -1; return -1;
} }
public static void applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType type, String varName) { public static boolean applyDebugInfo(MethodNode mth, SSAVar ssaVar, RegisterArg arg) {
TypeUpdateResult result = mth.root().getTypeUpdate().applyWithWiderAllow(mth, ssaVar, type); RegDebugInfoAttr debugInfoAttr = arg.get(AType.REG_DEBUG_INFO);
if (debugInfoAttr == null) {
return false;
}
return applyDebugInfo(mth, ssaVar, debugInfoAttr.getRegType(), debugInfoAttr.getName());
}
public static boolean applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType type, String varName) {
TypeUpdateResult result = mth.root().getTypeUpdate().applyWithWiderIgnoreUnknown(mth, ssaVar, type);
if (result == TypeUpdateResult.REJECT) { if (result == TypeUpdateResult.REJECT) {
if (Consts.DEBUG_TYPE_INFERENCE) { if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth); LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth);
} }
} else { return false;
if (NameMapper.isValidAndPrintable(varName)) {
ssaVar.setName(varName);
}
} }
} if (NameMapper.isValidAndPrintable(varName)) {
ssaVar.setName(varName);
private static void addRegDbdInfo(Set<RegDebugInfoAttr> debugInfo, RegisterArg reg) {
RegDebugInfoAttr debugInfoAttr = reg.get(AType.REG_DEBUG_INFO);
if (debugInfoAttr != null) {
debugInfo.add(debugInfoAttr);
} }
return true;
} }
/** /**
@@ -89,25 +89,33 @@ public class DebugInfoAttachVisitor extends AbstractVisitor {
} }
for (int i = start; i <= end; i++) { for (int i = start; i <= end; i++) {
InsnNode insn = insnArr[i]; InsnNode insn = insnArr[i];
if (insn != null) { if (insn == null) {
attachDebugInfo(insn.getResult(), debugInfoAttr, regNum); continue;
for (InsnArg arg : insn.getArguments()) {
attachDebugInfo(arg, debugInfoAttr, regNum);
}
} }
int count = 0;
for (InsnArg arg : insn.getArguments()) {
count += attachDebugInfo(arg, debugInfoAttr, regNum);
}
if (count != 0) {
// don't apply same info for result if applied to args
continue;
}
attachDebugInfo(insn.getResult(), debugInfoAttr, regNum);
} }
} }
mth.addAttr(new LocalVarsDebugInfoAttr(localVars)); mth.addAttr(new LocalVarsDebugInfoAttr(localVars));
} }
private void attachDebugInfo(InsnArg arg, RegDebugInfoAttr debugInfoAttr, int regNum) { private int attachDebugInfo(InsnArg arg, RegDebugInfoAttr debugInfoAttr, int regNum) {
if (arg instanceof RegisterArg) { if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg; RegisterArg reg = (RegisterArg) arg;
if (regNum == reg.getRegNum()) { if (regNum == reg.getRegNum()) {
reg.addAttr(debugInfoAttr); reg.addAttr(debugInfoAttr);
return 1;
} }
} }
return 0;
} }
public static ArgType getVarType(MethodNode mth, ILocalVar var) { public static ArgType getVarType(MethodNode mth, ILocalVar var) {
@@ -154,7 +154,11 @@ public class MarkFinallyVisitor extends AbstractVisitor {
// remove 'finally' from 'try' blocks, check all up paths on each exit (connected with finally exit) // remove 'finally' from 'try' blocks, check all up paths on each exit (connected with finally exit)
List<BlockNode> tryBlocks = allHandler.getTryBlock().getBlocks(); List<BlockNode> tryBlocks = allHandler.getTryBlock().getBlocks();
BlockNode bottomFinallyBlock = BlockUtils.followEmptyPath(BlockUtils.getBottomBlock(allHandler.getBlocks())); BlockNode bottomBlock = BlockUtils.getBottomBlock(allHandler.getBlocks());
if (bottomBlock == null) {
return false;
}
BlockNode bottomFinallyBlock = BlockUtils.followEmptyPath(bottomBlock);
BlockNode bottom = BlockUtils.getNextBlock(bottomFinallyBlock); BlockNode bottom = BlockUtils.getNextBlock(bottomFinallyBlock);
if (bottom == null) { if (bottom == null) {
return false; return false;
@@ -0,0 +1,226 @@
package jadx.core.dex.visitors.kotlin;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.rename.CodeRenameVisitor;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
name = "ProcessKotlinInternals",
desc = "Use variable names from Kotlin intrinsic1 methods",
runAfter = {
InitCodeVariables.class,
DebugInfoApplyVisitor.class
},
runBefore = {
CodeRenameVisitor.class
}
)
public class ProcessKotlinInternals extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(ProcessKotlinInternals.class);
private static final String KOTLIN_INTERNAL_PKG = "kotlin.jvm.internal.";
private static final String KOTLIN_INTRINSICS_CLS = KOTLIN_INTERNAL_PKG + "Intrinsics";
private static final String KOTLIN_VARNAME_SOURCE_MTH1 = "(Ljava/lang/Object;Ljava/lang/String;)V";
private static final String KOTLIN_VARNAME_SOURCE_MTH2 = "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V";
private @Nullable ClassInfo kotlinIntrinsicsCls;
private Set<MethodInfo> kotlinVarNameSourceMethods;
private boolean hideInsns;
@Override
public void init(RootNode root) throws JadxException {
ClassNode kotlinCls = searchKotlinIntrinsicsClass(root);
if (kotlinCls != null) {
kotlinIntrinsicsCls = kotlinCls.getClassInfo();
kotlinVarNameSourceMethods = collectMethods(kotlinCls);
LOG.debug("Kotlin Intrinsics class: {}, methods: {}", kotlinCls, kotlinVarNameSourceMethods.size());
} else {
kotlinIntrinsicsCls = null;
LOG.debug("Kotlin Intrinsics class not found");
}
hideInsns = root.getArgs().getUseKotlinMethodsForVarNames() == UseKotlinMethodsForVarNames.APPLY_AND_HIDE;
}
@Override
public boolean visit(ClassNode cls) {
if (kotlinIntrinsicsCls == null) {
return false;
}
for (MethodNode mth : cls.getMethods()) {
processMth(mth);
}
return true;
}
private void processMth(MethodNode mth) {
if (mth.isNoCode()) {
return;
}
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
if (insn.getType() == InsnType.INVOKE) {
try {
processInvoke(mth, insn);
} catch (Exception e) {
mth.addWarnComment("Failed to extract var names", e);
}
}
}
}
}
private void processInvoke(MethodNode mth, InsnNode insn) {
int argsCount = insn.getArgsCount();
if (argsCount < 2) {
return;
}
MethodInfo invokeMth = ((InvokeNode) insn).getCallMth();
if (!kotlinVarNameSourceMethods.contains(invokeMth)) {
return;
}
InsnArg firstArg = insn.getArg(0);
if (!firstArg.isRegister()) {
return;
}
RegisterArg varArg = (RegisterArg) firstArg;
boolean renamed = false;
if (argsCount == 2) {
String str = getConstString(mth, insn, 1);
if (str != null) {
renamed = checkAndRename(varArg, str);
}
} else if (argsCount == 3) {
// TODO: use second arg for rename class
String str = getConstString(mth, insn, 2);
if (str != null) {
renamed = checkAndRename(varArg, str);
}
}
if (renamed && hideInsns) {
insn.add(AFlag.DONT_GENERATE);
}
}
private boolean checkAndRename(RegisterArg arg, String str) {
String name = trimName(str);
if (NameMapper.isValidAndPrintable(name)) {
arg.getSVar().getCodeVar().setName(name);
return true;
}
return false;
}
@Nullable
private String getConstString(MethodNode mth, InsnNode insn, int arg) {
InsnArg strArg = insn.getArg(arg);
if (!strArg.isInsnWrap()) {
return null;
}
InsnNode constInsn = ((InsnWrapArg) strArg).getWrapInsn();
InsnType insnType = constInsn.getType();
if (insnType == InsnType.CONST_STR) {
return ((ConstStringNode) constInsn).getString();
}
if (insnType == InsnType.SGET) {
// revert const field inline :(
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) constInsn).getIndex();
FieldNode fieldNode = mth.root().resolveField(fieldInfo);
if (fieldNode != null) {
String str = (String) fieldNode.get(JadxAttrType.CONSTANT_VALUE).getValue();
InsnArg newArg = InsnArg.wrapArg(new ConstStringNode(str));
insn.replaceArg(strArg, newArg);
return str;
}
}
return null;
}
private String trimName(String str) {
if (str.startsWith("$this$")) {
return str.substring(6);
}
if (str.startsWith("$")) {
return str.substring(1);
}
return str;
}
@Nullable
private static ClassNode searchKotlinIntrinsicsClass(RootNode root) {
ClassNode kotlinCls = root.resolveClass(KOTLIN_INTRINSICS_CLS);
if (kotlinCls != null) {
return kotlinCls;
}
List<ClassNode> candidates = new ArrayList<>();
for (ClassNode cls : root.getClasses()) {
if (isKotlinIntrinsicsClass(cls)) {
candidates.add(cls);
}
}
return Utils.getOne(candidates);
}
private static boolean isKotlinIntrinsicsClass(ClassNode cls) {
if (!cls.getClassInfo().getFullName().startsWith(KOTLIN_INTERNAL_PKG)) {
return false;
}
if (cls.getMethods().size() < 5) {
return false;
}
int mthCount = 0;
for (MethodNode mth : cls.getMethods()) {
if (mth.getAccessFlags().isStatic()
&& mth.getMethodInfo().getShortId().endsWith(KOTLIN_VARNAME_SOURCE_MTH1)) {
mthCount++;
}
}
return mthCount > 2;
}
private Set<MethodInfo> collectMethods(ClassNode kotlinCls) {
Set<MethodInfo> set = new HashSet<>();
for (MethodNode mth : kotlinCls.getMethods()) {
if (!mth.getAccessFlags().isStatic()) {
continue;
}
String shortId = mth.getMethodInfo().getShortId();
if (shortId.endsWith(KOTLIN_VARNAME_SOURCE_MTH1) || shortId.endsWith(KOTLIN_VARNAME_SOURCE_MTH2)) {
set.add(mth.getMethodInfo());
}
}
return set;
}
}
@@ -382,6 +382,9 @@ public class IfMakerHelper {
} }
if (useCount > 1) { if (useCount > 1) {
forceInlineInsns.add(insn); forceInlineInsns.add(insn);
} else {
// allow only forced assign inline
pass = false;
} }
} }
} }
@@ -16,8 +16,6 @@ import jadx.core.utils.RegionUtils;
import static jadx.core.utils.RegionUtils.insnsCount; import static jadx.core.utils.RegionUtils.insnsCount;
public class IfRegionVisitor extends AbstractVisitor { public class IfRegionVisitor extends AbstractVisitor {
private static final TernaryMod TERNARY_VISITOR = new TernaryMod();
private static final ProcessIfRegionVisitor PROCESS_IF_REGION_VISITOR = new ProcessIfRegionVisitor(); private static final ProcessIfRegionVisitor PROCESS_IF_REGION_VISITOR = new ProcessIfRegionVisitor();
private static final RemoveRedundantElseVisitor REMOVE_REDUNDANT_ELSE_VISITOR = new RemoveRedundantElseVisitor(); private static final RemoveRedundantElseVisitor REMOVE_REDUNDANT_ELSE_VISITOR = new RemoveRedundantElseVisitor();
@@ -30,7 +28,7 @@ public class IfRegionVisitor extends AbstractVisitor {
} }
public static void process(MethodNode mth) { public static void process(MethodNode mth) {
DepthRegionTraversal.traverseIterative(mth, TERNARY_VISITOR); TernaryMod.process(mth);
DepthRegionTraversal.traverse(mth, PROCESS_IF_REGION_VISITOR); DepthRegionTraversal.traverse(mth, PROCESS_IF_REGION_VISITOR);
DepthRegionTraversal.traverseIterative(mth, REMOVE_REDUNDANT_ELSE_VISITOR); DepthRegionTraversal.traverseIterative(mth, REMOVE_REDUNDANT_ELSE_VISITOR);
} }
@@ -25,10 +25,38 @@ import jadx.core.utils.InsnRemover;
/** /**
* Convert 'if' to ternary operation * Convert 'if' to ternary operation
*/ */
public class TernaryMod implements IRegionIterativeVisitor { public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativeVisitor {
private static final TernaryMod INSTANCE = new TernaryMod();
public static void process(MethodNode mth) {
// first: convert all found ternary nodes in one iteration
DepthRegionTraversal.traverse(mth, INSTANCE);
if (mth.contains(AFlag.REQUEST_CODE_SHRINK)) {
CodeShrinkVisitor.shrinkMethod(mth);
}
// second: iterative runs with shrink after each change
DepthRegionTraversal.traverseIterative(mth, INSTANCE);
}
@Override
public boolean enterRegion(MethodNode mth, IRegion region) {
if (processRegion(mth, region)) {
mth.add(AFlag.REQUEST_CODE_SHRINK);
}
return true;
}
@Override @Override
public boolean visitRegion(MethodNode mth, IRegion region) { public boolean visitRegion(MethodNode mth, IRegion region) {
if (processRegion(mth, region)) {
CodeShrinkVisitor.shrinkMethod(mth);
return true;
}
return false;
}
private static boolean processRegion(MethodNode mth, IRegion region) {
if (region instanceof IfRegion) { if (region instanceof IfRegion) {
return makeTernaryInsn(mth, (IfRegion) region); return makeTernaryInsn(mth, (IfRegion) region);
} }
@@ -115,9 +143,6 @@ public class TernaryMod implements IRegionIterativeVisitor {
header.getInstructions().add(ternInsn); header.getInstructions().add(ternInsn);
clearConditionBlocks(conditionBlocks, header); clearConditionBlocks(conditionBlocks, header);
// shrink method again
CodeShrinkVisitor.shrinkMethod(mth);
return true; return true;
} }
@@ -151,8 +176,6 @@ public class TernaryMod implements IRegionIterativeVisitor {
header.add(AFlag.RETURN); header.add(AFlag.RETURN);
clearConditionBlocks(conditionBlocks, header); clearConditionBlocks(conditionBlocks, header);
CodeShrinkVisitor.shrinkMethod(mth);
return true; return true;
} }
return false; return false;
@@ -291,4 +314,7 @@ public class TernaryMod implements IRegionIterativeVisitor {
// shrink method again // shrink method again
CodeShrinkVisitor.shrinkMethod(mth); CodeShrinkVisitor.shrinkMethod(mth);
} }
private TernaryMod() {
}
} }
@@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -25,6 +26,7 @@ import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
public class RenameVisitor extends AbstractVisitor { public class RenameVisitor extends AbstractVisitor {
private static final Pattern ANONYMOUS_CLASS_PATTERN = Pattern.compile("^\\d+$");
@Override @Override
public void init(RootNode root) { public void init(RootNode root) {
@@ -130,11 +132,12 @@ public class RenameVisitor extends AbstractVisitor {
private static String fixClsShortName(JadxArgs args, String clsName) { private static String fixClsShortName(JadxArgs args, String clsName) {
boolean renameValid = args.isRenameValid(); boolean renameValid = args.isRenameValid();
if (renameValid) { if (renameValid) {
char firstChar = clsName.charAt(0); if (ANONYMOUS_CLASS_PATTERN.matcher(clsName).matches()) {
if (Character.isDigit(firstChar)) {
return Consts.ANONYMOUS_CLASS_PREFIX + NameMapper.removeInvalidCharsMiddle(clsName); return Consts.ANONYMOUS_CLASS_PREFIX + NameMapper.removeInvalidCharsMiddle(clsName);
} }
if (firstChar == '$') {
char firstChar = clsName.charAt(0);
if (firstChar == '$' || Character.isDigit(firstChar)) {
return 'C' + NameMapper.removeInvalidCharsMiddle(clsName); return 'C' + NameMapper.removeInvalidCharsMiddle(clsName);
} }
} }
@@ -4,12 +4,14 @@ import java.util.ArrayList;
import java.util.BitSet; import java.util.BitSet;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.Named;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
@@ -40,6 +42,7 @@ public class CodeShrinkVisitor extends AbstractVisitor {
if (mth.isNoCode()) { if (mth.isNoCode()) {
return; return;
} }
mth.remove(AFlag.REQUEST_CODE_SHRINK);
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
shrinkBlock(mth, block); shrinkBlock(mth, block);
simplifyMoveInsns(mth, block); simplifyMoveInsns(mth, block);
@@ -76,7 +79,9 @@ public class CodeShrinkVisitor extends AbstractVisitor {
private static void checkInline(MethodNode mth, BlockNode block, InsnList insnList, private static void checkInline(MethodNode mth, BlockNode block, InsnList insnList,
List<WrapInfo> wrapList, ArgsInfo argsInfo, RegisterArg arg) { List<WrapInfo> wrapList, ArgsInfo argsInfo, RegisterArg arg) {
if (arg.contains(AFlag.DONT_INLINE)) { if (arg.contains(AFlag.DONT_INLINE)
|| arg.getParentInsn() == null
|| arg.getParentInsn().contains(AFlag.DONT_GENERATE)) {
return; return;
} }
SSAVar sVar = arg.getSVar(); SSAVar sVar = arg.getSVar();
@@ -89,21 +94,34 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|| assignInsn.contains(AFlag.WRAPPED)) { || assignInsn.contains(AFlag.WRAPPED)) {
return; return;
} }
// allow inline only one use arg
boolean assignInline = assignInsn.contains(AFlag.FORCE_ASSIGN_INLINE); boolean assignInline = assignInsn.contains(AFlag.FORCE_ASSIGN_INLINE);
if (!assignInline && sVar.getVariableUseCount() != 1) { if (!assignInline && sVar.isUsedInPhi()) {
return; return;
} }
List<RegisterArg> useList = sVar.getUseList(); // allow inline only one use arg
if (!useList.isEmpty()) { int useCount = 0;
RegisterArg useArg = useList.get(0); for (RegisterArg useArg : sVar.getUseList()) {
InsnNode parentInsn = useArg.getParentInsn(); InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn != null && parentInsn.contains(AFlag.DONT_GENERATE)) { if (parentInsn != null && parentInsn.contains(AFlag.DONT_GENERATE)) {
return; continue;
} }
if (!assignInline && useArg.contains(AFlag.DONT_INLINE_CONST)) { if (!assignInline && useArg.contains(AFlag.DONT_INLINE_CONST)) {
return; return;
} }
useCount++;
}
if (!assignInline && useCount != 1) {
return;
}
if (!assignInline && sVar.getName() != null) {
if (searchArgWithName(assignInsn, sVar.getName())) {
// allow inline if name is reused in result
} else if (varWithSameNameExists(mth, sVar)) {
// allow inline if var name is duplicated
} else {
// reject inline of named variable
return;
}
} }
int assignPos = insnList.getIndex(assignInsn); int assignPos = insnList.getIndex(assignInsn);
@@ -127,6 +145,31 @@ public class CodeShrinkVisitor extends AbstractVisitor {
} }
} }
private static boolean varWithSameNameExists(MethodNode mth, SSAVar inlineVar) {
for (SSAVar ssaVar : mth.getSVars()) {
if (ssaVar == inlineVar || ssaVar.getCodeVar() == inlineVar.getCodeVar()) {
continue;
}
if (Objects.equals(ssaVar.getName(), inlineVar.getName())) {
return ssaVar.getUseCount() > inlineVar.getUseCount();
}
}
return false;
}
private static boolean searchArgWithName(InsnNode assignInsn, String varName) {
InsnArg result = assignInsn.visitArgs(insnArg -> {
if (insnArg instanceof Named) {
String argName = ((Named) insnArg).getName();
if (Objects.equals(argName, varName)) {
return insnArg;
}
}
return null;
});
return result != null;
}
private static boolean assignInline(MethodNode mth, RegisterArg arg, InsnNode assignInsn, BlockNode assignBlock) { private static boolean assignInline(MethodNode mth, RegisterArg arg, InsnNode assignInsn, BlockNode assignBlock) {
RegisterArg useArg = arg.getSVar().getUseList().get(0); RegisterArg useArg = arg.getSVar().getUseList().get(0);
InsnNode useInsn = useArg.getParentInsn(); InsnNode useInsn = useArg.getParentInsn();
@@ -140,7 +140,7 @@ public class SSATransform extends AbstractVisitor {
stack.push(initState); stack.push(initState);
while (!stack.isEmpty()) { while (!stack.isEmpty()) {
RenameState state = stack.pop(); RenameState state = stack.pop();
renameVarsInBlock(state); renameVarsInBlock(mth, state);
for (BlockNode dominated : state.getBlock().getDominatesOn()) { for (BlockNode dominated : state.getBlock().getDominatesOn()) {
stack.push(RenameState.copyFrom(state, dominated)); stack.push(RenameState.copyFrom(state, dominated));
} }
@@ -156,7 +156,7 @@ public class SSATransform extends AbstractVisitor {
} }
} }
private static void renameVarsInBlock(RenameState state) { private static void renameVarsInBlock(MethodNode mth, RenameState state) {
BlockNode block = state.getBlock(); BlockNode block = state.getBlock();
for (InsnNode insn : block.getInstructions()) { for (InsnNode insn : block.getInstructions()) {
if (insn.getType() != InsnType.PHI) { if (insn.getType() != InsnType.PHI) {
@@ -168,8 +168,9 @@ public class SSATransform extends AbstractVisitor {
int regNum = reg.getRegNum(); int regNum = reg.getRegNum();
SSAVar var = state.getVar(regNum); SSAVar var = state.getVar(regNum);
if (var == null) { if (var == null) {
throw new JadxRuntimeException("Not initialized variable reg: " + regNum // TODO: in most cases issue in incorrectly attached exception handlers
+ ", insn: " + insn + ", block:" + block); mth.addWarnComment("Not initialized variable reg: " + regNum + ", insn: " + insn + ", block:" + block);
var = state.startVar(reg);
} }
var.use(reg); var.use(reg);
} }
@@ -9,6 +9,7 @@ import java.util.Objects;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.ArgType.WildcardBound; import jadx.core.dex.instructions.args.ArgType.WildcardBound;
import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.instructions.args.PrimitiveType;
@@ -42,6 +43,17 @@ public class TypeCompare {
return compareObjects(first.getType(), second.getType()); return compareObjects(first.getType(), second.getType());
} }
public TypeCompareEnum compareTypes(ClassInfo first, ClassInfo second) {
return compareObjects(first.getType(), second.getType());
}
public TypeCompareEnum compareObjects(ArgType first, ArgType second) {
if (first == second || Objects.equals(first, second)) {
return TypeCompareEnum.EQUAL;
}
return compareObjectsNoPreCheck(first, second);
}
/** /**
* Compare two type and return result for first argument (narrow, wider or conflict) * Compare two type and return result for first argument (narrow, wider or conflict)
*/ */
@@ -81,7 +93,7 @@ public class TypeCompare {
boolean firstObj = first.isObject(); boolean firstObj = first.isObject();
boolean secondObj = second.isObject(); boolean secondObj = second.isObject();
if (firstObj && secondObj) { if (firstObj && secondObj) {
return compareObjects(first, second); return compareObjectsNoPreCheck(first, second);
} else { } else {
// primitive types conflicts with objects // primitive types conflicts with objects
if (firstObj && secondPrimitive) { if (firstObj && secondPrimitive) {
@@ -98,6 +110,10 @@ public class TypeCompare {
|| secondPrimitiveType == PrimitiveType.BOOLEAN) { || secondPrimitiveType == PrimitiveType.BOOLEAN) {
return CONFLICT; return CONFLICT;
} }
if (swapEquals(firstPrimitiveType, secondPrimitiveType, PrimitiveType.CHAR, PrimitiveType.BYTE)
|| swapEquals(firstPrimitiveType, secondPrimitiveType, PrimitiveType.CHAR, PrimitiveType.SHORT)) {
return CONFLICT;
}
return firstPrimitiveType.compareTo(secondPrimitiveType) > 0 ? WIDER : NARROW; return firstPrimitiveType.compareTo(secondPrimitiveType) > 0 ? WIDER : NARROW;
} }
@@ -105,6 +121,10 @@ public class TypeCompare {
return TypeCompareEnum.CONFLICT; return TypeCompareEnum.CONFLICT;
} }
private boolean swapEquals(PrimitiveType first, PrimitiveType second, PrimitiveType a, PrimitiveType b) {
return (first == a && second == b) || (first == b && second == a);
}
private TypeCompareEnum compareArrayWithOtherType(ArgType array, ArgType other) { private TypeCompareEnum compareArrayWithOtherType(ArgType array, ArgType other) {
if (!other.isTypeKnown()) { if (!other.isTypeKnown()) {
if (other.contains(PrimitiveType.ARRAY)) { if (other.contains(PrimitiveType.ARRAY)) {
@@ -151,7 +171,7 @@ public class TypeCompare {
return CONFLICT; return CONFLICT;
} }
private TypeCompareEnum compareObjects(ArgType first, ArgType second) { private TypeCompareEnum compareObjectsNoPreCheck(ArgType first, ArgType second) {
boolean objectsEquals = first.getObject().equals(second.getObject()); boolean objectsEquals = first.getObject().equals(second.getObject());
boolean firstGenericType = first.isGenericType(); boolean firstGenericType = first.isGenericType();
boolean secondGenericType = second.isGenericType(); boolean secondGenericType = second.isGenericType();
@@ -254,7 +274,7 @@ public class TypeCompare {
return NARROW; return NARROW;
} }
for (ArgType extendType : extendTypes) { for (ArgType extendType : extendTypes) {
TypeCompareEnum res = compareObjects(extendType, objType); TypeCompareEnum res = compareObjectsNoPreCheck(extendType, objType);
if (!res.isNarrow()) { if (!res.isNarrow()) {
return res; return res;
} }
@@ -39,6 +39,10 @@ public enum TypeCompareEnum {
return this == WIDER || this == WIDER_BY_GENERIC; return this == WIDER || this == WIDER_BY_GENERIC;
} }
public boolean isWiderOrEqual() {
return isEqual() || isWider();
}
public boolean isNarrow() { public boolean isNarrow() {
return this == NARROW || this == NARROW_BY_GENERIC; return this == NARROW || this == NARROW_BY_GENERIC;
} }
@@ -19,6 +19,7 @@ import jadx.core.Consts;
import jadx.core.clsp.ClspGraph; import jadx.core.clsp.ClspGraph;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr; import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ArithNode;
@@ -35,12 +36,15 @@ 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.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.TernaryInsn; import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.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;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.nodes.utils.MethodUtils;
import jadx.core.dex.trycatch.ExcHandlerAttr; 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;
@@ -273,6 +277,11 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
addBound(typeInfo, new TypeBoundConst(BoundEnum.ASSIGN, clsType)); addBound(typeInfo, new TypeBoundConst(BoundEnum.ASSIGN, clsType));
break; break;
case CONSTRUCTOR:
ArgType ctrClsType = replaceAnonymousType((ConstructorInsn) insn);
addBound(typeInfo, new TypeBoundConst(BoundEnum.ASSIGN, ctrClsType));
break;
case CONST: case CONST:
LiteralArg constLit = (LiteralArg) insn.getArg(0); LiteralArg constLit = (LiteralArg) insn.getArg(0);
addBound(typeInfo, new TypeBoundConst(BoundEnum.ASSIGN, constLit.getType())); addBound(typeInfo, new TypeBoundConst(BoundEnum.ASSIGN, constLit.getType()));
@@ -308,6 +317,19 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
} }
} }
private ArgType replaceAnonymousType(ConstructorInsn ctr) {
if (ctr.isNewInstance()) {
ClassNode ctrCls = root.resolveClass(ctr.getClassType());
if (ctrCls != null && ctrCls.contains(AFlag.DONT_GENERATE)) {
AnonymousClassAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS);
if (baseTypeAttr != null) {
return baseTypeAttr.getBaseType();
}
}
}
return ctr.getClassType().getType();
}
private ITypeBound makeAssignFieldGetBound(IndexInsnNode insn) { private ITypeBound makeAssignFieldGetBound(IndexInsnNode insn) {
ArgType initType = insn.getResult().getInitType(); ArgType initType = insn.getResult().getInitType();
if (initType.containsTypeVariable()) { if (initType.containsTypeVariable()) {
@@ -340,7 +362,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
return null; return null;
} }
if (insn instanceof BaseInvokeNode) { if (insn instanceof BaseInvokeNode) {
TypeBoundInvokeUse invokeUseBound = makeInvokeUseBound(regArg, (BaseInvokeNode) insn); ITypeBound invokeUseBound = makeInvokeUseBound(regArg, (BaseInvokeNode) insn);
if (invokeUseBound != null) { if (invokeUseBound != null) {
return invokeUseBound; return invokeUseBound;
} }
@@ -352,21 +374,32 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
return new TypeBoundConst(BoundEnum.USE, regArg.getInitType(), regArg); return new TypeBoundConst(BoundEnum.USE, regArg.getInitType(), regArg);
} }
private TypeBoundInvokeUse makeInvokeUseBound(RegisterArg regArg, BaseInvokeNode invoke) { private ITypeBound makeInvokeUseBound(RegisterArg regArg, BaseInvokeNode invoke) {
InsnArg instanceArg = invoke.getInstanceArg(); InsnArg instanceArg = invoke.getInstanceArg();
if (instanceArg == null || instanceArg == regArg) { if (instanceArg == null) {
return null; return null;
} }
IMethodDetails methodDetails = root.getMethodUtils().getMethodDetails(invoke); MethodUtils methodUtils = root.getMethodUtils();
IMethodDetails methodDetails = methodUtils.getMethodDetails(invoke);
if (methodDetails == null) { if (methodDetails == null) {
return null; return null;
} }
int argIndex = invoke.getArgIndex(regArg) - invoke.getFirstArgOffset(); if (instanceArg != regArg) {
ArgType argType = methodDetails.getArgTypes().get(argIndex); int argIndex = invoke.getArgIndex(regArg) - invoke.getFirstArgOffset();
if (!argType.containsTypeVariable()) { ArgType argType = methodDetails.getArgTypes().get(argIndex);
return null; if (!argType.containsTypeVariable()) {
return null;
}
return new TypeBoundInvokeUse(root, invoke, regArg, argType);
} }
return new TypeBoundInvokeUse(root, invoke, regArg, argType);
// for override methods use origin declared class as type
if (methodDetails instanceof MethodNode) {
MethodNode callMth = (MethodNode) methodDetails;
ClassInfo declCls = methodUtils.getMethodOriginDeclClass(callMth);
return new TypeBoundConst(BoundEnum.USE, declCls.getType(), regArg);
}
return null;
} }
private boolean tryPossibleTypes(MethodNode mth, SSAVar var, ArgType type) { private boolean tryPossibleTypes(MethodNode mth, SSAVar var, ArgType type) {
@@ -66,7 +66,11 @@ public final class TypeUpdate {
* Force type setting * Force type setting
*/ */
public TypeUpdateResult applyWithWiderIgnSame(MethodNode mth, SSAVar ssaVar, ArgType candidateType) { public TypeUpdateResult applyWithWiderIgnSame(MethodNode mth, SSAVar ssaVar, ArgType candidateType) {
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNSAME); return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNORE_SAME);
}
public TypeUpdateResult applyWithWiderIgnoreUnknown(MethodNode mth, SSAVar ssaVar, ArgType candidateType) {
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNORE_UNKNOWN);
} }
private TypeUpdateResult apply(MethodNode mth, SSAVar ssaVar, ArgType candidateType, TypeUpdateFlags flags) { private TypeUpdateResult apply(MethodNode mth, SSAVar ssaVar, ArgType candidateType, TypeUpdateFlags flags) {
@@ -110,6 +114,9 @@ public final class TypeUpdate {
} }
TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType); TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType);
if (compareResult == TypeCompareEnum.UNKNOWN && updateInfo.getFlags().isIgnoreUnknown()) {
return REJECT;
}
if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) { if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) {
// don't changed type // don't changed type
if (compareResult == TypeCompareEnum.EQUAL) { if (compareResult == TypeCompareEnum.EQUAL) {
@@ -1,39 +1,58 @@
package jadx.core.dex.visitors.typeinference; package jadx.core.dex.visitors.typeinference;
import org.jetbrains.annotations.NotNull;
public class TypeUpdateFlags { public class TypeUpdateFlags {
private static final int ALLOW_WIDER = 1;
private static final int IGNORE_SAME = 2;
private static final int IGNORE_UNKNOWN = 4;
public static final TypeUpdateFlags FLAGS_EMPTY = new TypeUpdateFlags(false, false); public static final TypeUpdateFlags FLAGS_EMPTY = build(0);
public static final TypeUpdateFlags FLAGS_WIDER = new TypeUpdateFlags(true, false); public static final TypeUpdateFlags FLAGS_WIDER = build(ALLOW_WIDER);
public static final TypeUpdateFlags FLAGS_WIDER_IGNSAME = new TypeUpdateFlags(true, true); public static final TypeUpdateFlags FLAGS_WIDER_IGNORE_SAME = build(ALLOW_WIDER | IGNORE_SAME);
public static final TypeUpdateFlags FLAGS_WIDER_IGNORE_UNKNOWN = build(ALLOW_WIDER | IGNORE_UNKNOWN);
private final boolean allowWider; private final int flags;
private final boolean ignoreSame;
private TypeUpdateFlags(boolean allowWider, boolean ignoreSame) { @NotNull
this.allowWider = allowWider; private static TypeUpdateFlags build(int flags) {
this.ignoreSame = ignoreSame; return new TypeUpdateFlags(flags);
}
private TypeUpdateFlags(int flags) {
this.flags = flags;
} }
public boolean isAllowWider() { public boolean isAllowWider() {
return allowWider; return (flags & ALLOW_WIDER) != 0;
} }
public boolean isIgnoreSame() { public boolean isIgnoreSame() {
return ignoreSame; return (flags & IGNORE_SAME) != 0;
}
public boolean isIgnoreUnknown() {
return (flags & IGNORE_UNKNOWN) != 0;
} }
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (allowWider) { if (isAllowWider()) {
sb.append("ALLOW_WIDER"); sb.append("ALLOW_WIDER");
} }
if (ignoreSame) { if (isIgnoreSame()) {
if (sb.length() != 0) { if (sb.length() != 0) {
sb.append('|'); sb.append('|');
} }
sb.append("IGNORE_SAME"); sb.append("IGNORE_SAME");
} }
if (isIgnoreUnknown()) {
if (sb.length() != 0) {
sb.append('|');
}
sb.append("IGNORE_UNKNOWN");
}
return sb.toString(); return sb.toString();
} }
} }
@@ -8,7 +8,6 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -24,6 +23,7 @@ import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.ResContainer; import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.XmlSecurity;
public class ExportGradleProject { public class ExportGradleProject {
@@ -110,7 +110,8 @@ public class ExportGradleProject {
Integer versionCode = Integer.valueOf(manifest.getAttribute("android:versionCode")); Integer versionCode = Integer.valueOf(manifest.getAttribute("android:versionCode"));
String versionName = manifest.getAttribute("android:versionName"); String versionName = manifest.getAttribute("android:versionName");
Integer minSdk = Integer.valueOf(usesSdk.getAttribute("android:minSdkVersion")); Integer minSdk = Integer.valueOf(usesSdk.getAttribute("android:minSdkVersion"));
Integer targetSdk = Integer.valueOf(usesSdk.getAttribute("android:targetSdkVersion")); String stringTargetSdk = usesSdk.getAttribute("android:targetSdkVersion");
Integer targetSdk = stringTargetSdk.isEmpty() ? minSdk : Integer.valueOf(stringTargetSdk);
String appName = "UNKNOWN"; String appName = "UNKNOWN";
if (application.hasAttribute("android:label")) { if (application.hasAttribute("android:label")) {
@@ -139,7 +140,7 @@ public class ExportGradleProject {
private Document parseXml(String xmlContent) { private Document parseXml(String xmlContent) {
try { try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); DocumentBuilder builder = XmlSecurity.getSecureDbf().newDocumentBuilder();
Document document = builder.parse(new InputSource(new StringReader(xmlContent))); Document document = builder.parse(new InputSource(new StringReader(xmlContent)));
document.getDocumentElement().normalize(); document.getDocumentElement().normalize();
@@ -35,6 +35,7 @@ import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.conditions.IfCondition; import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
public class BlockUtils { public class BlockUtils {
@@ -382,6 +383,7 @@ public class BlockUtils {
/** /**
* Return first successor which not exception handler and not follow loop back edge * Return first successor which not exception handler and not follow loop back edge
*/ */
@Nullable
public static BlockNode getNextBlock(BlockNode block) { public static BlockNode getNextBlock(BlockNode block) {
List<BlockNode> s = block.getCleanSuccessors(); List<BlockNode> s = block.getCleanSuccessors();
return s.isEmpty() ? null : s.get(0); return s.isEmpty() ? null : s.get(0);
@@ -594,6 +596,7 @@ public class BlockUtils {
/** /**
* Search last block in control flow graph from input set. * Search last block in control flow graph from input set.
*/ */
@Nullable
public static BlockNode getBottomBlock(List<BlockNode> blocks) { public static BlockNode getBottomBlock(List<BlockNode> blocks) {
if (blocks.size() == 1) { if (blocks.size() == 1) {
return blocks.get(0); return blocks.get(0);
@@ -701,7 +704,7 @@ public class BlockUtils {
mth.getLoops().forEach(l -> excluded.set(l.getStart().getId())); mth.getLoops().forEach(l -> excluded.set(l.getStart().getId()));
if (!mth.isNoExceptionHandlers()) { if (!mth.isNoExceptionHandlers()) {
// exclude exception handlers paths // exclude exception handlers paths
mth.getExceptionHandlers().forEach(h -> excluded.or(h.getHandlerBlock().getDomFrontier())); mth.getExceptionHandlers().forEach(h -> mergeExcHandlerDomFrontier(mth, h, excluded));
} }
domFrontBS.andNot(excluded); domFrontBS.andNot(excluded);
oneBlock = bitSetToOneBlock(mth, domFrontBS); oneBlock = bitSetToOneBlock(mth, domFrontBS);
@@ -709,6 +712,7 @@ public class BlockUtils {
return oneBlock; return oneBlock;
} }
BitSet combinedDF = newBlocksBitSet(mth); BitSet combinedDF = newBlocksBitSet(mth);
int k = mth.getBasicBlocks().size();
while (true) { while (true) {
// collect dom frontier blocks from current set until only one block left // collect dom frontier blocks from current set until only one block left
forEachBlockFromBitSet(mth, domFrontBS, block -> { forEachBlockFromBitSet(mth, domFrontBS, block -> {
@@ -726,6 +730,10 @@ public class BlockUtils {
if (cardinality == 0) { if (cardinality == 0) {
return null; return null;
} }
if (k-- < 0) {
mth.addWarnComment("Path cross not found for " + blocks + ", limit reached: " + mth.getBasicBlocks().size());
return null;
}
// replace domFrontBS with combinedDF // replace domFrontBS with combinedDF
domFrontBS.clear(); domFrontBS.clear();
domFrontBS.or(combinedDF); domFrontBS.or(combinedDF);
@@ -733,6 +741,20 @@ public class BlockUtils {
} }
} }
private static void mergeExcHandlerDomFrontier(MethodNode mth, ExceptionHandler handler, BitSet set) {
BlockNode handlerBlock = handler.getHandlerBlock();
if (handlerBlock == null) {
mth.addDebugComment("Null handler block in: " + handler);
return;
}
BitSet domFrontier = handlerBlock.getDomFrontier();
if (domFrontier == null) {
mth.addDebugComment("Null dom frontier in handler: " + handler);
return;
}
set.or(domFrontier);
}
public static BlockNode getPathCross(MethodNode mth, BlockNode b1, BlockNode b2) { public static BlockNode getPathCross(MethodNode mth, BlockNode b1, BlockNode b2) {
if (b1 == b2) { if (b1 == b2) {
return b1; return b1;
@@ -3,11 +3,10 @@ package jadx.core.utils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -17,12 +16,13 @@ import jadx.api.IDecompileScheduler;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.JavaClass; import jadx.api.JavaClass;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class DecompilerScheduler implements IDecompileScheduler { public class DecompilerScheduler implements IDecompileScheduler {
private static final Logger LOG = LoggerFactory.getLogger(DecompilerScheduler.class); private static final Logger LOG = LoggerFactory.getLogger(DecompilerScheduler.class);
private static final int MERGED_BATCH_SIZE = 16; private static final int MERGED_BATCH_SIZE = 16;
private static final boolean DUMP_STATS = false; private static final boolean DEBUG_BATCHES = false;
private final JadxDecompiler decompiler; private final JadxDecompiler decompiler;
@@ -32,13 +32,21 @@ public class DecompilerScheduler implements IDecompileScheduler {
@Override @Override
public List<List<JavaClass>> buildBatches(List<JavaClass> classes) { public List<List<JavaClass>> buildBatches(List<JavaClass> classes) {
long start = System.currentTimeMillis(); try {
List<List<ClassNode>> batches = internalBatches(Utils.collectionMap(classes, JavaClass::getClassNode)); long start = System.currentTimeMillis();
List<List<JavaClass>> result = Utils.collectionMap(batches, l -> Utils.collectionMapNoNull(l, decompiler::getJavaClassByNode)); List<List<ClassNode>> batches = internalBatches(Utils.collectionMap(classes, JavaClass::getClassNode));
if (LOG.isDebugEnabled()) { List<List<JavaClass>> result = Utils.collectionMap(batches, l -> Utils.collectionMapNoNull(l, decompiler::getJavaClassByNode));
LOG.debug("Build decompilation batches in {}ms", System.currentTimeMillis() - start); if (LOG.isDebugEnabled()) {
LOG.debug("Build decompilation batches in {}ms", System.currentTimeMillis() - start);
}
if (DEBUG_BATCHES) {
check(result, classes);
}
return result;
} catch (Throwable e) {
LOG.warn("Build batches failed (continue with fallback)", e);
return buildFallback(classes);
} }
return result;
} }
/** /**
@@ -46,26 +54,20 @@ public class DecompilerScheduler implements IDecompileScheduler {
* Build batches for dependencies of single class to avoid locking from another thread. * Build batches for dependencies of single class to avoid locking from another thread.
*/ */
public List<List<ClassNode>> internalBatches(List<ClassNode> classes) { public List<List<ClassNode>> internalBatches(List<ClassNode> classes) {
Map<ClassNode, DepInfo> depsMap = new HashMap<>(classes.size()); List<DepInfo> deps = sumDependencies(classes);
Set<ClassNode> visited = new HashSet<>();
for (ClassNode classNode : classes) {
visited.clear();
sumDeps(classNode, depsMap, visited);
}
List<DepInfo> deps = new ArrayList<>(depsMap.values());
Collections.sort(deps);
Set<ClassNode> added = new HashSet<>(classes.size()); Set<ClassNode> added = new HashSet<>(classes.size());
Comparator<ClassNode> cmpDepSize = Comparator.comparingInt(c -> c.getDependencies().size()); Comparator<ClassNode> cmpDepSize = Comparator.comparingInt(ClassNode::getTotalDepsCount);
List<List<ClassNode>> result = new ArrayList<>(); List<List<ClassNode>> result = new ArrayList<>();
List<ClassNode> mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE); List<ClassNode> mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
for (DepInfo depInfo : deps) { for (DepInfo depInfo : deps) {
ClassNode cls = depInfo.getCls(); ClassNode cls = depInfo.getCls();
int depsSize = cls.getDependencies().size(); if (!added.add(cls)) {
continue;
}
int depsSize = cls.getTotalDepsCount();
if (depsSize == 0) { if (depsSize == 0) {
// add classes without dependencies in merged batch // add classes without dependencies in merged batch
mergedBatch.add(cls); mergedBatch.add(cls);
added.add(cls);
if (mergedBatch.size() >= MERGED_BATCH_SIZE) { if (mergedBatch.size() >= MERGED_BATCH_SIZE) {
result.add(mergedBatch); result.add(mergedBatch);
mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE); mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
@@ -76,38 +78,34 @@ public class DecompilerScheduler implements IDecompileScheduler {
ClassNode topDep = dep.getTopParentClass(); ClassNode topDep = dep.getTopParentClass();
if (!added.contains(topDep)) { if (!added.contains(topDep)) {
batch.add(topDep); batch.add(topDep);
added.add(topDep);
} }
} }
batch.sort(cmpDepSize); batch.sort(cmpDepSize);
batch.add(cls); batch.add(cls);
added.addAll(batch);
result.add(batch); result.add(batch);
} }
} }
if (mergedBatch.size() > 0) { if (mergedBatch.size() > 0) {
result.add(mergedBatch); result.add(mergedBatch);
} }
if (DUMP_STATS) { if (DEBUG_BATCHES) {
dumpBatchesStats(classes, result, deps); dumpBatchesStats(classes, result, deps);
} }
return result; return result;
} }
public int sumDeps(ClassNode cls, Map<ClassNode, DepInfo> depsMap, Set<ClassNode> visited) { private static List<DepInfo> sumDependencies(List<ClassNode> classes) {
visited.add(cls); List<DepInfo> deps = new ArrayList<>(classes.size());
DepInfo depInfo = depsMap.get(cls); for (ClassNode cls : classes) {
if (depInfo != null) { int count = 0;
return depInfo.getDepsCount(); for (ClassNode dep : cls.getDependencies()) {
} count += 1 + dep.getTotalDepsCount();
List<ClassNode> deps = cls.getDependencies();
int count = deps.size();
for (ClassNode dep : deps) {
if (!visited.contains(dep)) {
count += sumDeps(dep, depsMap, visited);
} }
deps.add(new DepInfo(cls, count));
} }
depsMap.put(cls, new DepInfo(cls, count)); Collections.sort(deps);
return count; return deps;
} }
private static final class DepInfo implements Comparable<DepInfo> { private static final class DepInfo implements Comparable<DepInfo> {
@@ -129,19 +127,43 @@ public class DecompilerScheduler implements IDecompileScheduler {
@Override @Override
public int compareTo(@NotNull DecompilerScheduler.DepInfo o) { public int compareTo(@NotNull DecompilerScheduler.DepInfo o) {
return Integer.compare(depsCount, o.depsCount); int deps = Integer.compare(depsCount, o.depsCount);
if (deps == 0) {
return cls.compareTo(o.cls);
}
return deps;
} }
@Override
public String toString() {
return cls + ":" + depsCount;
}
}
private static List<List<JavaClass>> buildFallback(List<JavaClass> classes) {
return classes.stream()
.sorted(Comparator.comparingInt(c -> c.getClassNode().getTotalDepsCount()))
.map(Collections::singletonList)
.collect(Collectors.toList());
} }
private void dumpBatchesStats(List<ClassNode> classes, List<List<ClassNode>> result, List<DepInfo> deps) { private void dumpBatchesStats(List<ClassNode> classes, List<List<ClassNode>> result, List<DepInfo> deps) {
double avg = result.stream().mapToInt(List::size).average().orElse(-1); double avg = result.stream().mapToInt(List::size).average().orElse(-1);
int maxSingleDeps = classes.stream().mapToInt(c -> c.getDependencies().size()).max().orElse(-1); int maxSingleDeps = classes.stream().mapToInt(ClassNode::getTotalDepsCount).max().orElse(-1);
int maxRecursiveDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1); int maxSubDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1);
LOG.info("Batches stats:" LOG.info("Batches stats:"
+ "\n input classes: " + classes.size() + "\n input classes: " + classes.size()
+ ",\n batches: " + result.size() + ",\n batches: " + result.size()
+ ",\n average batch size: " + avg + ",\n average batch size: " + String.format("%.2f", avg)
+ ",\n max single deps count: " + maxSingleDeps + ",\n max single deps count: " + maxSingleDeps
+ ",\n max recursive deps count: " + maxRecursiveDeps); + ",\n max sub deps count: " + maxSubDeps);
}
private static void check(List<List<JavaClass>> result, List<JavaClass> classes) {
int classInBatches = result.stream().mapToInt(List::size).sum();
if (classes.size() != classInBatches) {
throw new JadxRuntimeException(
"Incorrect number of classes in result batch: " + classInBatches + ", expected: " + classes.size());
}
} }
} }
@@ -96,7 +96,7 @@ public class InsnUtils {
return ((ConstClassNode) insn).getClsType(); return ((ConstClassNode) insn).getClsType();
case SGET: case SGET:
FieldInfo f = (FieldInfo) ((IndexInsnNode) insn).getIndex(); FieldInfo f = (FieldInfo) ((IndexInsnNode) insn).getIndex();
FieldNode fieldNode = root.deepResolveField(f); FieldNode fieldNode = root.resolveField(f);
if (fieldNode == null) { if (fieldNode == null) {
LOG.warn("Field {} not found", f); LOG.warn("Field {} not found", f);
return null; return null;
@@ -6,12 +6,13 @@ import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.core.dex.nodes.BlockNode;
public class ListUtils { public class ListUtils {
public static <T> boolean isSingleElement(@Nullable List<T> list, T obj) { public static <T> boolean isSingleElement(@Nullable List<T> list, T obj) {
@@ -47,7 +48,19 @@ public class ListUtils {
return list.get(list.size() - 1); return list.get(list.size() - 1);
} }
public static List<BlockNode> distinctList(List<BlockNode> list) { public static <T extends Comparable<T>> List<T> distinctMergeSortedLists(List<T> first, List<T> second) {
if (first.isEmpty()) {
return second;
}
if (second.isEmpty()) {
return first;
}
Set<T> set = new TreeSet<>(first);
set.addAll(second);
return new ArrayList<>(set);
}
public static <T> List<T> distinctList(List<T> list) {
return new ArrayList<>(new LinkedHashSet<>(list)); return new ArrayList<>(new LinkedHashSet<>(list));
} }
@@ -62,8 +75,100 @@ public class ListUtils {
newList.add(newObj); newList.add(newObj);
return newList; return newList;
} }
list.remove(oldObj); int idx = list.indexOf(oldObj);
list.add(newObj); if (idx != -1) {
list.set(idx, newObj);
} else {
list.add(newObj);
}
return list; return list;
} }
public static <T> void safeRemove(List<T> list, T obj) {
if (list != null && !list.isEmpty()) {
list.remove(obj);
}
}
public static <T> List<T> safeRemoveAndTrim(List<T> list, T obj) {
if (list == null || list.isEmpty()) {
return list;
}
if (list.remove(obj)) {
if (list.isEmpty()) {
return Collections.emptyList();
}
}
return list;
}
public static <T> List<T> safeAdd(List<T> list, T obj) {
if (list == null || list.isEmpty()) {
List<T> newList = new ArrayList<>(1);
newList.add(obj);
return newList;
}
list.add(obj);
return list;
}
public static <T> List<T> filter(List<T> list, Predicate<T> filter) {
if (list == null || list.isEmpty()) {
return Collections.emptyList();
}
List<T> result = new ArrayList<>();
for (T element : list) {
if (filter.test(element)) {
result.add(element);
}
}
return result;
}
/**
* Search exactly one element in list by filter
*
* @return null if found not exactly one element (zero or more than one)
*/
@Nullable
public static <T> T filterOnlyOne(List<T> list, Predicate<T> filter) {
if (list == null || list.isEmpty()) {
return null;
}
T found = null;
for (T element : list) {
if (filter.test(element)) {
if (found != null) {
// found second
return null;
}
found = element;
}
}
return found;
}
public static <T> boolean allMatch(List<T> list, Predicate<T> test) {
if (list == null || list.isEmpty()) {
return false;
}
for (T element : list) {
if (!test.test(element)) {
return false;
}
}
return true;
}
public static <T> boolean anyMatch(List<T> list, Predicate<T> test) {
if (list == null || list.isEmpty()) {
return false;
}
for (T element : list) {
if (test.test(element)) {
return true;
}
}
return false;
}
} }
@@ -8,10 +8,12 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -284,6 +286,19 @@ public class Utils {
return result; return result;
} }
public static <T> Set<T> mergeSets(Set<T> first, Set<T> second) {
if (isEmpty(first)) {
return second;
}
if (isEmpty(second)) {
return first;
}
Set<T> result = new HashSet<>(first.size() + second.size());
result.addAll(first);
result.addAll(second);
return result;
}
public static Map<String, String> newConstStringMap(String... parameters) { public static Map<String, String> newConstStringMap(String... parameters) {
int len = parameters.length; int len = parameters.length;
if (len == 0) { if (len == 0) {
@@ -338,6 +353,14 @@ public class Utils {
return list.get(0); return list.get(0);
} }
@Nullable
public static <T> T getOne(@Nullable Collection<T> collection) {
if (collection == null || collection.size() != 1) {
return null;
}
return collection.iterator().next();
}
@Nullable @Nullable
public static <T> T first(List<T> list) { public static <T> T first(List<T> list) {
if (list.isEmpty()) { if (list.isEmpty()) {
@@ -24,6 +24,8 @@ import java.io.OutputStream;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.jetbrains.annotations.Nullable;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
/** /**
@@ -31,16 +33,19 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
*/ */
public class Res9patchStreamDecoder { public class Res9patchStreamDecoder {
public void decode(InputStream in, OutputStream out) { public boolean decode(InputStream in, OutputStream out) {
try { try {
BufferedImage im = ImageIO.read(in); BufferedImage im = ImageIO.read(in);
NinePatch np = getNinePatch(in);
if (np == null) {
return false;
}
int w = im.getWidth(); int w = im.getWidth();
int h = im.getHeight(); int h = im.getHeight();
BufferedImage im2 = new BufferedImage(w + 2, h + 2, BufferedImage.TYPE_INT_ARGB); BufferedImage im2 = new BufferedImage(w + 2, h + 2, BufferedImage.TYPE_INT_ARGB);
im2.createGraphics().drawImage(im, 1, 1, w, h, null); im2.createGraphics().drawImage(im, 1, 1, w, h, null);
NinePatch np = getNinePatch(in);
drawHLine(im2, h + 1, np.padLeft + 1, w - np.padRight); drawHLine(im2, h + 1, np.padLeft + 1, w - np.padRight);
drawVLine(im2, w + 1, np.padTop + 1, h - np.padBottom); drawVLine(im2, w + 1, np.padTop + 1, h - np.padBottom);
@@ -55,28 +60,32 @@ public class Res9patchStreamDecoder {
} }
ImageIO.write(im2, "png", out); ImageIO.write(im2, "png", out);
return true;
} catch (Exception e) { } catch (Exception e) {
throw new JadxRuntimeException("9patch image decode error", e); throw new JadxRuntimeException("9patch image decode error", e);
} }
} }
@Nullable
private NinePatch getNinePatch(InputStream in) throws IOException { private NinePatch getNinePatch(InputStream in) throws IOException {
ExtDataInput di = new ExtDataInput(in); ExtDataInput di = new ExtDataInput(in);
find9patchChunk(di); if (!find9patchChunk(di)) {
return null;
}
return NinePatch.decode(di); return NinePatch.decode(di);
} }
private void find9patchChunk(DataInput di) throws IOException { private boolean find9patchChunk(DataInput di) throws IOException {
di.skipBytes(8); di.skipBytes(8);
while (true) { while (true) {
int size; int size;
try { try {
size = di.readInt(); size = di.readInt();
} catch (IOException ex) { } catch (IOException ex) {
throw new JadxRuntimeException("Cant find nine patch chunk", ex); return false;
} }
if (di.readInt() == NP_CHUNK_TYPE) { if (di.readInt() == NP_CHUNK_TYPE) {
return; return true;
} }
di.skipBytes(size + 4); di.skipBytes(size + 4);
} }

Some files were not shown because too many files have changed in this diff Show More