Compare commits

...

103 Commits

Author SHA1 Message Date
SiderealArt 1ec127c3cb fix(gui): update Traditional Chinese translation (PR #1452) 2022-04-19 14:05:55 +01:00
Skylot 7a3b7c55c9 build: run tests in parallel 2022-04-18 16:31:25 +01:00
Skylot b66293a2f7 fix: handle wildcard in invoke type resolver (#1238) 2022-04-18 16:27:35 +01:00
Skylot abcaafa89a chore: update gradle and dependencies 2022-04-17 19:28:18 +01:00
Skylot cf25cc4faa fix: prevent null type in code variables 2022-04-17 19:18:33 +01:00
Skylot b57001d4a7 fix: use correct reference for replaced bridge constructor (#1441) 2022-04-10 15:48:36 +01:00
Skylot 83decc2473 fix(gui): rename class while rename constructor (#1441) 2022-04-08 13:45:27 +01:00
root-intruder 92faa569be build: allow unsigned local lib builds (#1438)(PR #1439) 2022-04-06 13:41:33 +01:00
Skylot c5b731169d build: disable jitpack 2022-04-06 13:34:08 +01:00
Skylot f0a8ef81d3 fix: replace fixed memory limit with -XX:MaxRAMPercentage=70.0 (#1437) 2022-04-05 20:08:39 +01:00
Skylot 994973ac01 fix(gui): check free memory after GC attempt 2022-04-05 19:46:10 +01:00
Skylot c9622c0771 chore: update class set to Android 32 2022-04-05 20:21:41 +03:00
Jan S 8551c6c903 fix(res): ignore resource chunk entries that are located after the resource chunk end (#751)(PR #1436) 2022-04-04 18:05:07 +01:00
Bruno Oberle 9a9ac4308e fix(cli): use correct converter for "--decompilation-mode" option (#1434)(PR #1435) 2022-04-04 13:47:30 +01:00
Matt e784cbdd09 fix(deobf): fix writing method mappings as fields entries (#1432)(PR #1433) 2022-04-02 12:30:25 +01:00
Elias 2744c4bfb6 build: fix appdata.xml (#1427)(PR #1430) 2022-03-31 17:30:06 +01:00
Skylot e4f4c1b84a fix(gui): don't highlight whitespaces and special symbols (#1429) 2022-03-28 19:21:05 +01:00
Elias e5fa818b5c build: remove unsupported tags from appdata.xml (#1427)(PR #1428)
* remove <code> tags from appdata.xml because flatpak doesn't like it
* appdata.xml: remove <a> tag because flatpak doesn't like it
2022-03-27 17:28:01 +01:00
Elias b22b554a69 build: add appdata.xml for flatpak package (PR #1426) 2022-03-27 14:06:18 +01:00
Skylot e9b8060889 refactor(gui): improve node action in code area 2022-03-26 15:31:29 +00:00
Skylot 1c2b2c072c fix(gui): restore open tabs on project load (regression fix) 2022-03-25 13:40:00 +00:00
Skylot 3d451912ee fix: handle inlined classes while collecting override related methods (#1422) 2022-03-25 12:56:18 +00:00
Skylot fe91d774fa feat(gui): add split view for different decompilation modes 2022-03-23 18:16:57 +03:00
Skylot d8306cb1c0 feat: add 'simple' decompilation mode 2022-03-23 18:16:54 +03:00
Jan S 909cf0a576 fix: various minor improvements (PR #1418)
* chore: better variable naming for getInstance calls
* chore: rebalance preferences window and fix empty plugins section directly after jadx-gui start
* chore: do not ask for project save if nothing had been changed
* use parallel mode for gradle
* minor improvements for app debugging
* apply CodeQL suggestion to prevent log injection
* handle IntelliJ Idea warnings
* replace not-ASCII chars in LogUtils.escape

Co-authored-by: Skylot <skylot@gmail.com>
2022-03-23 15:13:53 +00:00
Jan S 8fe1ee11e4 fix(debugger): resolve IO read problems, proper socket closing (PR #1414)
* fix(debugger): several IO read problems fixed
* merged latest changes
* fixed read loop
* Update jadx-gui/src/main/java/jadx/gui/device/protocol/ADB.java

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
2022-03-20 17:01:01 +00:00
Skylot d2bef108f5 chore: update dependencies 2022-03-19 18:52:44 +00:00
Skylot ba8ba504b1 fix(debugger): small improve for jdwp handshake (#1412) 2022-03-19 18:43:34 +00:00
Skylot 481b5abf85 fix(debugger): handle stream end and partial reads (#1412) 2022-03-18 14:19:08 +00:00
Skylot c4e1d9445a fix(gui): reduce threads count on low memory, other tweaks (#1410) 2022-03-17 17:50:28 +00:00
Skylot cb03532b76 fix: allow implicit type cast for array operations (#1407) 2022-03-14 18:47:55 +00:00
Skylot c93e9eea14 fix: improve class names collision detection (#1406) 2022-03-13 12:08:03 +00:00
Skylot 9a67b19973 feat(gui): add zoom in/out actions (#1403) 2022-03-11 13:59:00 +00:00
Skylot 95c75bed1e chore: update gradle and dependencies 2022-03-11 11:34:51 +00:00
Skylot b008568a5c doc: add missing options to readme 2022-03-05 17:15:08 +00:00
Skylot 94fb91cec6 feat: add options for java-convert plugin 2022-03-02 15:40:32 +00:00
Skylot c54dd77f35 fix(gui): resolve NPE and fix code style in BreakpointManager 2022-03-02 12:10:14 +00:00
Jan S 17fbc99f29 feat(gui): dialog for showing exception details and creating an GitHub issue (PR #1399)
* chore(gui): Dialog for showing exception details and creating an GitHub issue
* directly throw test exception
* checkstyle
* minor
* log exception before the dialog is shown
2022-03-01 15:00:22 +00:00
Skylot 21dd17290b fix(gui): download only latest version info for jadx update (#1397) 2022-02-28 18:51:13 +00:00
Skylot dc73fc92be fix(gui): don't use hardcoded color for link component (#1398) 2022-02-28 18:39:51 +00:00
Skylot 592215db66 fix(gui): handle package version in update check (#1397) 2022-02-28 18:39:51 +00:00
Skylot fb318e3bd9 fix(gui): revert contextual keywords to identifiers (#1394) 2022-02-27 15:22:41 +00:00
Skylot 5f3c8816a3 fix: allow zero skips for restore new filled array 2022-02-26 17:29:00 +00:00
Skylot 6016b902c7 test: fix usage of Eclipse compiler 2022-02-26 17:29:00 +00:00
Skylot 5852da1e3d feat: support MethodParameters attribute (#1260) 2022-02-26 10:28:21 +00:00
Skylot 502fd069be test: for source auto check use compiled classes instead runtime 2022-02-26 10:28:20 +00:00
Jan S fad9e7b827 fix(gui): initialize project name with loaded files (shown in Jadx title) (#1386)(PR #1393) 2022-02-26 09:20:58 +00:00
Skylot 35116d0b1a fix: load files also by extension (#1391) 2022-02-25 11:38:44 +00:00
Skylot 3b781e41ad test: allow to pass additional compiler options 2022-02-24 20:52:34 +00:00
Skylot a3e9744364 chore(cli): additional debug messages for java-convert plugin 2022-02-24 20:51:31 +00:00
Skylot 7030daeccd fix(cli): resolve regression in applying '-v' and '-q' options 2022-02-24 19:52:58 +00:00
Jan S e7151ad7b2 fix(gui): IllegalArgumentException when saving project to a different directory than the APK file (#1387)(PR #1388) 2022-02-23 09:27:04 +00:00
Skylot ed2a3c8458 fix: prevent NPE on 'ignore' deobf map file mode 2022-02-22 18:06:01 +00:00
Skylot 779f75cd52 fix(gui): prevent NPE on open preferences without loaded files (#1385) 2022-02-22 18:05:51 +00:00
Skylot 54683e3198 feat: plugin options, add verify checksum option for dex input (#1385) 2022-02-21 19:44:00 +00:00
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
281 changed files with 8462 additions and 2625 deletions
+5
View File
@@ -0,0 +1,5 @@
jdk:
- openjdk11
install:
- echo "Jitpack is not supported. Use artifacts from Maven Central (https://search.maven.org/search?q=jadx), check usage help at https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library"
- ./gradlew intentional-fail
+29 -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
@@ -76,10 +79,16 @@ 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
-m, --decompilation-mode - code output mode:
'auto' - trying best options (default)
'restructure' - restore code structure (normal java code)
'simple' - simplified instructions (linear, with goto's)
'fallback' - raw instructions without modifications
--show-bad-code - show inconsistent code (incorrectly decompiled) --show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name --no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info --no-debug-info - disable debug info
@@ -93,7 +102,12 @@ options:
--deobf-min - min length of name, renamed if shorter, default: 3 --deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64 --deobf-max - max length of name, renamed if longer, default: 64
--deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension --deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension
--deobf-rewrite-cfg - force to ignore and overwrite deobfuscation map file --deobf-cfg-file-mode - set mode for handle deobfuscation map file:
'read' - read if found, don't save (default)
'read-or-save' - read if found, save otherwise (don't overwrite)
'overwrite' - don't read, always save
'ignore' - don't read and don't save
--deobf-rewrite-cfg - set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)
--deobf-use-sourcename - use source file name as class name alias --deobf-use-sourcename - use source file name as class name alias
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names --deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply --use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
@@ -106,7 +120,7 @@ options:
--fs-case-sensitive - treat filesystem as case sensitive, false by default --fs-case-sensitive - treat filesystem as case sensitive, false by default
--cfg - save methods control flow graph to dot file --cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions) --raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc) -f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
--use-dx - use dx/d8 to convert java bytecode --use-dx - use dx/d8 to convert java bytecode
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info --comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress --log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
@@ -114,11 +128,20 @@ options:
-q, --quiet - turn off output (set --log-level to QUIET) -q, --quiet - turn off output (set --log-level to QUIET)
--version - print jadx version --version - print jadx version
-h, --help - print this help -h, --help - print this help
Plugin options (-P<name>=<value>):
1) dex-input (Load .dex and .apk files)
-Pdex-input.verify-checksum - Verify dex file checksum before load, values: [yes, no], default: yes
2) java-convert (Convert .jar and .class files to dex)
-Pjava-convert.mode - Convert mode, values: [dx, d8, both], default: both
-Pjava-convert.d8-desugar - Use desugar in d8, values: [yes, no], default: no
Examples: Examples:
jadx -d out classes.dex jadx -d out classes.dex
jadx --rename-flags "none" classes.dex jadx --rename-flags "none" classes.dex
jadx --rename-flags "valid, printable" classes.dex jadx --rename-flags "valid, printable" classes.dex
jadx --log-level ERROR app.apk jadx --log-level ERROR app.apk
jadx -Pdex-input.verify-checksum=no app.apk
``` ```
These options also worked on jadx-gui running from command line and override options from preferences dialog These options also worked on jadx-gui running from command line and override options from preferences dialog
+33
View File
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
<id>com.github.skylot.jadx</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>Apache-2.0</project_license>
<name>JADX</name>
<summary>Dex to Java decompiler</summary>
<description>
<p>Command line and GUI tools for producing Java source code from Android Dex and Apk files</p>
<ul>
<li>decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files</li>
<li>decode AndroidManifest.xml and other resources from resources.arsc</li>
<li>deobfuscator included</li>
<li>view decompiled code with highlighted syntax</li>
<li>jump to declaration</li>
<li>find usage</li>
<li>full text search</li>
<li>smali debugger</li>
</ul>
</description>
<screenshots>
<screenshot type="default">
<image>https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png</image>
</screenshot>
</screenshots>
<content_rating type="oars-1.1" />
<launchable type="desktop-id">com.github.skylot.jadx.desktop</launchable>
<url type="homepage">https://github.com/skylot/jadx</url>
<url type="bugtracker">https://github.com/skylot/jadx/issues</url>
<releases>
<release version="1.3.4" date="2022-03-20" />
</releases>
</component>
+9 -8
View File
@@ -1,6 +1,6 @@
plugins { plugins {
id 'com.github.ben-manes.versions' version '0.41.0' id 'com.github.ben-manes.versions' version '0.42.0'
id 'com.diffplug.spotless' version '6.2.0' id 'com.diffplug.spotless' version '6.4.2'
} }
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev" ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -27,23 +27,23 @@ allprojects {
} }
dependencies { dependencies {
implementation 'org.slf4j:slf4j-api:1.7.33' implementation 'org.slf4j:slf4j-api:1.7.36'
compileOnly 'org.jetbrains:annotations:23.0.0' compileOnly 'org.jetbrains:annotations:23.0.0'
testImplementation 'ch.qos.logback:logback-classic:1.2.10' testImplementation 'ch.qos.logback:logback-classic:1.2.11'
testImplementation 'org.hamcrest:hamcrest-library:2.2' testImplementation 'org.hamcrest:hamcrest-library:2.2'
testImplementation 'org.mockito:mockito-core:4.2.0' testImplementation 'org.mockito:mockito-core:4.4.0'
testImplementation 'org.assertj:assertj-core:3.22.0' testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
testCompileOnly 'org.jetbrains:annotations:23.0.0' testCompileOnly 'org.jetbrains:annotations:23.0.0'
} }
test { test {
useJUnitPlatform() useJUnitPlatform()
maxParallelForks = Runtime.runtime.availableProcessors()
} }
repositories { repositories {
@@ -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)
@@ -66,6 +66,7 @@ publishing {
} }
signing { signing {
required { gradle.taskGraph.hasTask("publish") }
sign publishing.publications.mavenJava sign publishing.publications.mavenJava
} }
+1
View File
@@ -1 +1,2 @@
org.gradle.warning.mode=all org.gradle.warning.mode=all
org.gradle.parallel=true
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=b586e04868a22fd817c8971330fec37e298f3242eb85c374181b12d637f80302 distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
+2 -2
View File
@@ -11,13 +11,13 @@ dependencies {
runtimeOnly(project(':jadx-plugins:jadx-smali-input')) runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
implementation 'com.beust:jcommander:1.82' implementation 'com.beust:jcommander:1.82'
implementation 'ch.qos.logback:logback-classic:1.2.10' implementation 'ch.qos.logback:logback-classic:1.2.11'
} }
application { application {
applicationName = 'jadx' applicationName = 'jadx'
mainClass.set('jadx.cli.JadxCLI') mainClass.set('jadx.cli.JadxCLI')
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC'] applicationDefaultJvmArgs = ['-Xms128M', '-XX:MaxRAMPercentage=70.0', '-XX:+UseG1GC']
} }
applicationDistribution.with { applicationDistribution.with {
@@ -17,6 +17,11 @@ import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameterized; import com.beust.jcommander.Parameterized;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.options.OptionDescription;
public class JCommanderWrapper<T> { public class JCommanderWrapper<T> {
private final JCommander jc; private final JCommander jc;
@@ -70,40 +75,44 @@ public class JCommanderWrapper<T> {
maxNamesLen = len; maxNamesLen = len;
} }
} }
maxNamesLen += 3;
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0); JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
for (Field f : getFields(args.getClass())) { for (Field f : getFields(args.getClass())) {
String name = f.getName(); String name = f.getName();
ParameterDescription p = paramsMap.get(name); ParameterDescription p = paramsMap.get(name);
if (p == null) { if (p == null || p.getParameter().hidden()) {
continue; continue;
} }
StringBuilder opt = new StringBuilder(); StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames()); opt.append(" ").append(p.getNames());
String description = p.getDescription(); String description = p.getDescription();
addSpaces(opt, maxNamesLen - opt.length() + 3); addSpaces(opt, maxNamesLen - opt.length());
if (description.contains("\n")) { if (description.contains("\n")) {
String[] lines = description.split("\n"); String[] lines = description.split("\n");
opt.append("- ").append(lines[0]); opt.append("- ").append(lines[0]);
for (int i = 1; i < lines.length; i++) { for (int i = 1; i < lines.length; i++) {
opt.append('\n'); opt.append('\n');
addSpaces(opt, maxNamesLen + 5); addSpaces(opt, maxNamesLen + 2);
opt.append(lines[i]); opt.append(lines[i]);
} }
} else { } else {
opt.append("- ").append(description); opt.append("- ").append(description);
} }
String defaultValue = getDefaultValue(args, f, opt); String defaultValue = getDefaultValue(args, f, opt);
if (defaultValue != null) { if (defaultValue != null && !description.contains("(default)")) {
opt.append(", default: ").append(defaultValue); opt.append(", default: ").append(defaultValue);
} }
out.println(opt); out.println(opt);
} }
out.println(appendPluginOptions(maxNamesLen));
out.println();
out.println("Examples:"); out.println("Examples:");
out.println(" jadx -d out classes.dex"); out.println(" jadx -d out classes.dex");
out.println(" jadx --rename-flags \"none\" classes.dex"); out.println(" jadx --rename-flags \"none\" classes.dex");
out.println(" jadx --rename-flags \"valid, printable\" classes.dex"); out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
out.println(" jadx --log-level ERROR app.apk"); out.println(" jadx --log-level ERROR app.apk");
out.println(" jadx -Pdex-input.verify-checksum=no app.apk");
} }
/** /**
@@ -145,4 +154,46 @@ public class JCommanderWrapper<T> {
str.append(' '); str.append(' ');
} }
} }
private String appendPluginOptions(int maxNamesLen) {
StringBuilder sb = new StringBuilder();
JadxPluginManager pluginManager = new JadxPluginManager();
pluginManager.load();
int k = 1;
for (JadxPlugin plugin : pluginManager.getAllPlugins()) {
if (plugin instanceof JadxPluginOptions) {
if (appendPlugin(((JadxPluginOptions) plugin), sb, maxNamesLen, k)) {
k++;
}
}
}
if (sb.length() == 0) {
return "";
}
return "\nPlugin options (-P<name>=<value>):" + sb;
}
private boolean appendPlugin(JadxPluginOptions plugin, StringBuilder out, int maxNamesLen, int k) {
List<OptionDescription> descs = plugin.getOptionsDescriptions();
if (descs.isEmpty()) {
return false;
}
JadxPluginInfo pluginInfo = plugin.getPluginInfo();
out.append("\n ").append(k).append(") ");
out.append(pluginInfo.getPluginId()).append(" (").append(pluginInfo.getDescription()).append(") ");
for (OptionDescription desc : descs) {
StringBuilder opt = new StringBuilder();
opt.append(" -P").append(desc.name());
addSpaces(opt, maxNamesLen - opt.length());
opt.append("- ").append(desc.description());
if (!desc.values().isEmpty()) {
opt.append(", values: ").append(desc.values());
}
if (desc.defaultValue() != null) {
opt.append(", default: ").append(desc.defaultValue());
}
out.append("\n").append(opt);
}
return true;
}
} }
+39 -10
View File
@@ -7,6 +7,7 @@ import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.impl.NoOpCodeCache; import jadx.api.impl.NoOpCodeCache;
import jadx.api.impl.SimpleCodeWriter; import jadx.api.impl.SimpleCodeWriter;
import jadx.cli.LogHelper.LogLevelEnum;
import jadx.core.utils.exceptions.JadxArgsValidateException; import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
@@ -21,7 +22,7 @@ public class JadxCLI {
LOG.error("Incorrect arguments: {}", e.getMessage()); LOG.error("Incorrect arguments: {}", e.getMessage());
result = 1; result = 1;
} catch (Exception e) { } catch (Exception e) {
LOG.error("jadx error: {}", e.getMessage(), e); LOG.error("Process error:", e);
result = 1; result = 1;
} finally { } finally {
FileUtils.deleteTempRootDir(); FileUtils.deleteTempRootDir();
@@ -32,23 +33,25 @@ public class JadxCLI {
public static int execute(String[] args) { public static int execute(String[] args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs(); JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) { if (jadxArgs.processArgs(args)) {
return processAndSave(jadxArgs.toJadxArgs()); return processAndSave(jadxArgs);
} }
return 0; return 0;
} }
private static int processAndSave(JadxArgs jadxArgs) { private static int processAndSave(JadxCLIArgs cliArgs) {
LogHelper.initLogLevel(cliArgs);
LogHelper.setLogLevelsForLoadingStage();
JadxArgs jadxArgs = cliArgs.toJadxArgs();
jadxArgs.setCodeCache(new NoOpCodeCache()); jadxArgs.setCodeCache(new NoOpCodeCache());
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new); jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) { try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load(); jadx.load();
if (LogHelper.getLogLevel() == LogHelper.LogLevelEnum.QUIET) { if (checkForErrors(jadx)) {
jadx.save(); return 1;
} else { }
jadx.save(500, (done, total) -> { LogHelper.setLogLevelsForDecompileStage();
int progress = (int) (done * 100.0 / total); if (!SingleClassMode.process(jadx, cliArgs)) {
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress); save(jadx);
});
} }
int errorsCount = jadx.getErrorsCount(); int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) { if (errorsCount != 0) {
@@ -60,4 +63,30 @@ public class JadxCLI {
} }
return 0; return 0;
} }
private static boolean checkForErrors(JadxDecompiler jadx) {
if (jadx.getRoot().getClasses().isEmpty()) {
LOG.error("Load failed! No classes for decompile!");
return true;
}
if (jadx.getErrorsCount() > 0) {
LOG.error("Load with errors! Check log for details");
// continue processing
return false;
}
return false;
}
private static void save(JadxDecompiler jadx) {
if (LogHelper.getLogLevel() == LogLevelEnum.QUIET) {
jadx.save();
} else {
jadx.save(500, (done, total) -> {
int progress = (int) (done * 100.0 / total);
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
});
// dumb line clear :)
System.out.print(" \r");
}
}
} }
@@ -2,20 +2,25 @@ package jadx.cli;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.beust.jcommander.DynamicParameter;
import com.beust.jcommander.IStringConverter; import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import jadx.api.CommentsLevel; import jadx.api.CommentsLevel;
import jadx.api.DecompilationMode;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RenameEnum; import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames; import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
@@ -39,9 +44,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";
@@ -51,6 +59,17 @@ public class JadxCLIArgs {
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count") @Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT; protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
@Parameter(
names = { "-m", "--decompilation-mode" },
description = "code output mode:"
+ "\n 'auto' - trying best options (default)"
+ "\n 'restructure' - restore code structure (normal java code)"
+ "\n 'simple' - simplified instructions (linear, with goto's)"
+ "\n 'fallback' - raw instructions without modifications",
converter = DecompilationModeConverter.class
)
protected DecompilationMode decompilationMode = DecompilationMode.AUTO;
@Parameter(names = { "--show-bad-code" }, description = "show inconsistent code (incorrectly decompiled)") @Parameter(names = { "--show-bad-code" }, description = "show inconsistent code (incorrectly decompiled)")
protected boolean showInconsistentCode = false; protected boolean showInconsistentCode = false;
@@ -93,7 +112,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")
@@ -130,7 +160,7 @@ public class JadxCLIArgs {
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)") @Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
protected boolean rawCfgOutput = false; protected boolean rawCfgOutput = false;
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)") @Parameter(names = { "-f", "--fallback" }, description = "set '--decompilation-mode' to 'fallback' (deprecated)")
protected boolean fallbackMode = false; protected boolean fallbackMode = false;
@Parameter(names = { "--use-dx" }, description = "use dx/d8 to convert java bytecode") @Parameter(names = { "--use-dx" }, description = "use dx/d8 to convert java bytecode")
@@ -162,6 +192,9 @@ public class JadxCLIArgs {
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true) @Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
protected boolean printHelp = false; protected boolean printHelp = false;
@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
protected Map<String, String> pluginOptions = new HashMap<>();
public boolean processArgs(String[] args) { public boolean processArgs(String[] args) {
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this); JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
return jcw.parse(args) && process(jcw); return jcw.parse(args) && process(jcw);
@@ -197,7 +230,6 @@ public class JadxCLIArgs {
if (threadsCount <= 0) { if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive, got: " + threadsCount); throw new JadxException("Threads count must be positive, got: " + threadsCount);
} }
LogHelper.setLogLevelFromArgs(this);
} catch (JadxException e) { } catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage()); System.err.println("ERROR: " + e.getMessage());
jcw.printUsage(); jcw.printUsage();
@@ -215,18 +247,23 @@ 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); if (fallbackMode) {
args.setDecompilationMode(DecompilationMode.FALLBACK);
} else {
args.setDecompilationMode(decompilationMode);
}
args.setShowInconsistentCode(showInconsistentCode); args.setShowInconsistentCode(showInconsistentCode);
args.setCfgOutput(cfgOutput); args.setCfgOutput(cfgOutput);
args.setRawCFGOutput(rawCfgOutput); args.setRawCFGOutput(rawCfgOutput);
args.setReplaceConsts(replaceConsts); args.setReplaceConsts(replaceConsts);
args.setDeobfuscationOn(deobfuscationOn); args.setDeobfuscationOn(deobfuscationOn);
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile)); args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
args.setDeobfuscationForceSave(deobfuscationForceSave); if (deobfuscationForceSave) {
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
} else {
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
}
args.setDeobfuscationMinLength(deobfuscationMinLength); args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength); args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias); args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
@@ -244,6 +281,7 @@ public class JadxCLIArgs {
args.setFsCaseSensitive(fsCaseSensitive); args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel); args.setCommentsLevel(commentsLevel);
args.setUseDxInput(useDx); args.setUseDxInput(useDx);
args.setPluginOptions(pluginOptions);
return args; return args;
} }
@@ -263,6 +301,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;
} }
@@ -283,6 +329,10 @@ public class JadxCLIArgs {
return useDx; return useDx;
} }
public DecompilationMode getDecompilationMode() {
return decompilationMode;
}
public boolean isShowInconsistentCode() { public boolean isShowInconsistentCode() {
return showInconsistentCode; return showInconsistentCode;
} }
@@ -323,6 +373,10 @@ public class JadxCLIArgs {
return deobfuscationMapFile; return deobfuscationMapFile;
} }
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
return deobfuscationMapFileMode;
}
public boolean isDeobfuscationForceSave() { public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave; return deobfuscationForceSave;
} }
@@ -383,6 +437,14 @@ public class JadxCLIArgs {
return commentsLevel; return commentsLevel;
} }
public LogHelper.LogLevelEnum getLogLevel() {
return logLevel;
}
public Map<String, String> getPluginOptions() {
return pluginOptions;
}
static class RenameConverter implements IStringConverter<Set<RenameEnum>> { static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
private final String paramName; private final String paramName;
@@ -438,6 +500,32 @@ public class JadxCLIArgs {
} }
} }
public static class DeobfuscationMapFileModeConverter implements IStringConverter<DeobfuscationMapFileMode> {
@Override
public DeobfuscationMapFileMode convert(String value) {
try {
return DeobfuscationMapFileMode.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: "
+ JadxCLIArgs.enumValuesString(DeobfuscationMapFileMode.values()));
}
}
}
public static class DecompilationModeConverter implements IStringConverter<DecompilationMode> {
@Override
public DecompilationMode convert(String value) {
try {
return DecompilationMode.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: "
+ JadxCLIArgs.enumValuesString(DecompilationMode.values()));
}
}
}
public static String enumValuesString(Enum<?>[] values) { public static String enumValuesString(Enum<?>[] values) {
return Stream.of(values) return Stream.of(values)
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT)) .map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
+49 -20
View File
@@ -1,5 +1,6 @@
package jadx.cli; package jadx.cli;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -32,33 +33,61 @@ public class LogHelper {
} }
} }
@Nullable("For disable log level control")
private static LogLevelEnum logLevelValue; private static LogLevelEnum logLevelValue;
public static void setLogLevelFromArgs(JadxCLIArgs args) { public static void initLogLevel(JadxCLIArgs args) {
if (isCustomLogConfig()) { logLevelValue = getLogLevelFromArgs(args);
return;
}
LogLevelEnum logLevel = args.logLevel;
if (args.quiet) {
logLevel = LogLevelEnum.QUIET;
} else if (args.verbose) {
logLevel = LogLevelEnum.DEBUG;
}
applyLogLevel(logLevel);
} }
public static void applyLogLevel(LogLevelEnum logLevel) { private static LogLevelEnum getLogLevelFromArgs(JadxCLIArgs args) {
logLevelValue = logLevel; if (isCustomLogConfig()) {
return null;
}
if (args.quiet) {
return LogLevelEnum.QUIET;
}
if (args.verbose) {
return LogLevelEnum.DEBUG;
}
return args.logLevel;
}
public static void setLogLevelsForLoadingStage() {
if (logLevelValue == null) {
return;
}
if (logLevelValue == LogLevelEnum.PROGRESS) {
// show load errors
LogHelper.applyLogLevel(LogLevelEnum.ERROR);
fixForShowProgress();
return;
}
applyLogLevel(logLevelValue);
}
public static void setLogLevelsForDecompileStage() {
if (logLevelValue == null) {
return;
}
applyLogLevel(logLevelValue);
if (logLevelValue == LogLevelEnum.PROGRESS) {
fixForShowProgress();
}
}
/**
* Show progress: change to 'INFO' for control classes
*/
private static void fixForShowProgress() {
setLevelForClass(JadxCLI.class, Level.INFO);
setLevelForClass(JadxDecompiler.class, Level.INFO);
setLevelForClass(SingleClassMode.class, Level.INFO);
}
private static void applyLogLevel(@NotNull LogLevelEnum logLevel) {
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(logLevel.getLevel()); rootLogger.setLevel(logLevel.getLevel());
if (logLevel != LogLevelEnum.QUIET) {
// show progress for all levels except quiet
setLevelForClass(JadxCLI.class, Level.INFO);
setLevelForClass(JadxDecompiler.class, Level.INFO);
}
} }
@Nullable @Nullable
@@ -0,0 +1,87 @@
package jadx.cli;
import java.io.File;
import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.api.JadxDecompiler;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
public class SingleClassMode {
private static final Logger LOG = LoggerFactory.getLogger(SingleClassMode.class);
public static boolean process(JadxDecompiler jadx, JadxCLIArgs cliArgs) {
String singleClass = cliArgs.getSingleClass();
String singleClassOutput = cliArgs.getSingleClassOutput();
if (singleClass == null && singleClassOutput == null) {
return false;
}
ClassNode clsForProcess;
if (singleClass != null) {
clsForProcess = jadx.getRoot().resolveClass(singleClass);
if (clsForProcess == null) {
clsForProcess = jadx.getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(singleClass))
.findFirst().orElse(null);
}
if (clsForProcess == null) {
throw new JadxRuntimeException("Input class not found: " + singleClass);
}
if (clsForProcess.contains(AFlag.DONT_GENERATE)) {
throw new JadxRuntimeException("Input class can't be saved by currect jadx settings (marked as DONT_GENERATE)");
}
if (clsForProcess.isInner()) {
clsForProcess = clsForProcess.getTopParentClass();
LOG.warn("Input class is inner, parent class will be saved: {}", clsForProcess.getFullName());
}
} else {
// singleClassOutput is set
// expect only one class to be loaded
List<ClassNode> classes = jadx.getRoot().getClasses().stream()
.filter(c -> !c.isInner() && !c.contains(AFlag.DONT_GENERATE))
.collect(Collectors.toList());
int size = classes.size();
if (size == 1) {
clsForProcess = classes.get(0);
} else {
throw new JadxRuntimeException("Found " + size + " classes, single class output can't be used");
}
}
ICodeInfo codeInfo;
try {
codeInfo = clsForProcess.decompile();
} catch (Exception e) {
throw new JadxRuntimeException("Class decompilation failed", e);
}
String fileExt = SaveCode.getFileExtension(jadx.getRoot());
File out;
if (singleClassOutput == null) {
out = new File(jadx.getArgs().getOutDirSrc(), clsForProcess.getClassInfo().getAliasFullPath() + fileExt);
} else {
if (singleClassOutput.endsWith(fileExt)) {
// treat as file name
out = new File(singleClassOutput);
} else {
// treat as directory
out = new File(singleClassOutput, clsForProcess.getShortName() + fileExt);
}
}
File resultOut = FileUtils.prepareFile(out);
if (clsForProcess.getClassInfo().hasAlias()) {
LOG.info("Saving class '{}' (alias: '{}') to file '{}'",
clsForProcess.getClassInfo().getFullName(), clsForProcess.getFullName(), resultOut.getAbsolutePath());
} else {
LOG.info("Saving class '{}' to file '{}'", clsForProcess.getFullName(), resultOut.getAbsolutePath());
}
SaveCode.save(codeInfo.getCodeStr(), resultOut);
return true;
}
}
+3 -2
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
@@ -20,7 +20,8 @@ dependencies {
testRuntimeOnly(project(':jadx-plugins:jadx-java-input')) testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input')) testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
testImplementation('tools.profiler:async-profiler:1.8.3') testImplementation 'org.eclipse.jdt:ecj:3.29.0'
testImplementation 'tools.profiler:async-profiler:1.8.3'
} }
test { test {
@@ -0,0 +1,23 @@
package jadx.api;
public enum DecompilationMode {
/**
* Trying best options (default)
*/
AUTO,
/**
* Restore code structure (normal java code)
*/
RESTRUCTURE,
/**
* Simplified instructions (linear with goto's)
*/
SIMPLE,
/**
* Raw instructions without modifications
*/
FALLBACK
}
+65 -10
View File
@@ -4,11 +4,14 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.data.ICodeData; import jadx.api.data.ICodeData;
import jadx.api.impl.AnnotatedCodeWriter; import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache; import jadx.api.impl.InMemoryCodeCache;
@@ -35,7 +38,6 @@ public class JadxArgs {
private boolean cfgOutput = false; private boolean cfgOutput = false;
private boolean rawCFGOutput = false; private boolean rawCFGOutput = false;
private boolean fallbackMode = false;
private boolean showInconsistentCode = false; private boolean showInconsistentCode = false;
private boolean useImports = true; private boolean useImports = true;
@@ -54,11 +56,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;
@@ -81,6 +84,8 @@ public class JadxArgs {
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA; private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
private DecompilationMode decompilationMode = DecompilationMode.AUTO;
private ICodeData codeData; private ICodeData codeData;
private CommentsLevel commentsLevel = CommentsLevel.INFO; private CommentsLevel commentsLevel = CommentsLevel.INFO;
@@ -93,6 +98,13 @@ public class JadxArgs {
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY; private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
/**
* Don't save files (can be using for performance testing)
*/
private boolean skipFilesSave = false;
private Map<String, String> pluginOptions = new HashMap<>();
public JadxArgs() { public JadxArgs() {
// use default options // use default options
} }
@@ -164,11 +176,17 @@ public class JadxArgs {
} }
public boolean isFallbackMode() { public boolean isFallbackMode() {
return fallbackMode; return decompilationMode == DecompilationMode.FALLBACK;
} }
/**
* Deprecated: use 'decompilation mode' property
*/
@Deprecated
public void setFallbackMode(boolean fallbackMode) { public void setFallbackMode(boolean fallbackMode) {
this.fallbackMode = fallbackMode; if (fallbackMode) {
this.decompilationMode = DecompilationMode.FALLBACK;
}
} }
public boolean isShowInconsistentCode() { public boolean isShowInconsistentCode() {
@@ -259,12 +277,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() {
@@ -399,6 +429,14 @@ public class JadxArgs {
this.outputFormat = outputFormat; this.outputFormat = outputFormat;
} }
public DecompilationMode getDecompilationMode() {
return decompilationMode;
}
public void setDecompilationMode(DecompilationMode decompilationMode) {
this.decompilationMode = decompilationMode;
}
public ICodeCache getCodeCache() { public ICodeCache getCodeCache() {
return codeCache; return codeCache;
} }
@@ -447,6 +485,22 @@ public class JadxArgs {
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames; this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
} }
public boolean isSkipFilesSave() {
return skipFilesSave;
}
public void setSkipFilesSave(boolean skipFilesSave) {
this.skipFilesSave = skipFilesSave;
}
public Map<String, String> getPluginOptions() {
return pluginOptions;
}
public void setPluginOptions(Map<String, String> pluginOptions) {
this.pluginOptions = pluginOptions;
}
@Override @Override
public String toString() { public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -454,16 +508,14 @@ public class JadxArgs {
+ ", outDirSrc=" + outDirSrc + ", outDirSrc=" + outDirSrc
+ ", outDirRes=" + outDirRes + ", outDirRes=" + outDirRes
+ ", threadsCount=" + threadsCount + ", threadsCount=" + threadsCount
+ ", cfgOutput=" + cfgOutput + ", decompilationMode=" + decompilationMode
+ ", rawCFGOutput=" + rawCFGOutput
+ ", fallbackMode=" + fallbackMode
+ ", showInconsistentCode=" + showInconsistentCode + ", showInconsistentCode=" + showInconsistentCode
+ ", useImports=" + useImports + ", useImports=" + useImports
+ ", skipResources=" + skipResources + ", skipResources=" + skipResources
+ ", skipSources=" + skipSources + ", skipSources=" + skipSources
+ ", deobfuscationOn=" + deobfuscationOn + ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationMapFile=" + deobfuscationMapFile + ", deobfuscationMapFile=" + deobfuscationMapFile
+ ", deobfuscationForceSave=" + deobfuscationForceSave + ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias + ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata + ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames + ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
@@ -480,6 +532,9 @@ public class JadxArgs {
+ ", codeCache=" + codeCache + ", codeCache=" + codeCache
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName() + ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
+ ", useDxInput=" + useDxInput + ", useDxInput=" + useDxInput
+ ", pluginOptions=" + pluginOptions
+ ", cfgOutput=" + cfgOutput
+ ", rawCFGOutput=" + rawCFGOutput
+ '}'; + '}';
} }
} }
@@ -30,8 +30,11 @@ import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginManager; import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin; import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult; import jadx.api.plugins.input.data.ILoadResult;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.core.Jadx; import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.InlinedAttr;
import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
@@ -122,12 +125,16 @@ public final class JadxDecompiler implements Closeable {
loadedInputs.clear(); loadedInputs.clear();
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath); List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
List<Path> inputFiles = FileUtils.expandDirs(inputPaths); List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
long start = System.currentTimeMillis();
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) { for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
ILoadResult loadResult = inputPlugin.loadFiles(inputFiles); ILoadResult loadResult = inputPlugin.loadFiles(inputFiles);
if (loadResult != null && !loadResult.isEmpty()) { if (loadResult != null && !loadResult.isEmpty()) {
loadedInputs.add(loadResult); loadedInputs.add(loadResult);
} }
} }
if (LOG.isDebugEnabled()) {
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
}
} }
private void reset() { private void reset() {
@@ -167,6 +174,18 @@ public final class JadxDecompiler implements Closeable {
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(), LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
p -> p.getPluginInfo().getPluginId())); p -> p.getPluginInfo().getPluginId()));
} }
Map<String, String> pluginOptions = args.getPluginOptions();
if (!pluginOptions.isEmpty()) {
LOG.debug("Applying plugin options: {}", pluginOptions);
for (JadxPluginOptions plugin : pluginManager.getPluginsWithOptions()) {
try {
plugin.setOptions(pluginOptions);
} catch (Exception e) {
String pluginId = plugin.getPluginInfo().getPluginId();
throw new JadxRuntimeException("Failed to apply options for plugin: " + pluginId, e);
}
}
}
} }
public void registerPlugin(JadxPlugin plugin) { public void registerPlugin(JadxPlugin plugin) {
@@ -282,6 +301,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 +328,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 {
@@ -457,12 +485,12 @@ public final class JadxDecompiler implements Closeable {
if (parentClass.contains(AFlag.DONT_GENERATE)) { if (parentClass.contains(AFlag.DONT_GENERATE)) {
return null; return null;
} }
if (parentClass != cls) { JavaClass parentJavaClass = classesMap.get(parentClass);
JavaClass parentJavaClass = classesMap.get(parentClass); if (parentJavaClass == null) {
if (parentJavaClass == null) { getClasses();
getClasses(); parentJavaClass = classesMap.get(parentClass);
parentJavaClass = classesMap.get(parentClass); }
} if (parentJavaClass != null) {
loadJavaClass(parentJavaClass); loadJavaClass(parentJavaClass);
javaClass = classesMap.get(cls); javaClass = classesMap.get(cls);
if (javaClass != null) { if (javaClass != null) {
@@ -486,7 +514,9 @@ public final class JadxDecompiler implements Closeable {
return null; return null;
} }
// parent class not loaded yet // parent class not loaded yet
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass()); ClassNode parentClass = mth.getParentClass();
ClassNode codeCls = getCodeParentClass(parentClass);
JavaClass javaClass = getJavaClassByNode(codeCls);
if (javaClass == null) { if (javaClass == null) {
return null; return null;
} }
@@ -495,12 +525,26 @@ public final class JadxDecompiler implements Closeable {
if (javaMethod != null) { if (javaMethod != null) {
return javaMethod; return javaMethod;
} }
if (mth.getParentClass().hasNotGeneratedParent()) { if (parentClass.hasNotGeneratedParent()) {
return null; return null;
} }
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth); throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
} }
private ClassNode getCodeParentClass(ClassNode cls) {
ClassNode codeCls;
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
if (inlinedAttr != null) {
codeCls = inlinedAttr.getInlineCls().getTopParentClass();
} else {
codeCls = cls.getTopParentClass();
}
if (codeCls == cls) {
return codeCls;
}
return getCodeParentClass(codeCls);
}
@Nullable @Nullable
private JavaField getJavaFieldByNode(FieldNode fld) { private JavaField getJavaFieldByNode(FieldNode fld) {
JavaField javaField = fieldsMap.get(fld); JavaField javaField = fieldsMap.get(fld);
+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;
@@ -2,9 +2,12 @@ package jadx.api;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr; import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
@@ -14,6 +17,7 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
public final class JavaMethod implements JavaNode { public final class JavaMethod implements JavaNode {
private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class);
private final MethodNode mth; private final MethodNode mth;
private final JavaClass parent; private final JavaClass parent;
@@ -73,7 +77,14 @@ public final class JavaMethod implements JavaNode {
} }
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler(); JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
return ovrdAttr.getRelatedMthNodes().stream() return ovrdAttr.getRelatedMthNodes().stream()
.map(m -> ((JavaMethod) decompiler.convertNode(m))) .map(m -> {
JavaMethod javaMth = (JavaMethod) decompiler.convertNode(m);
if (javaMth == null) {
LOG.warn("Failed convert to java method: {}", m);
}
return javaMth;
})
.filter(Objects::nonNull)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@@ -126,8 +126,9 @@ public final class ResourcesLoader {
if (name.endsWith(".9.png")) { if (name.endsWith(".9.png")) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder(); Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
decoder.decode(inputStream, os); if (decoder.decode(inputStream, os)) {
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray()); return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
}
} catch (Exception e) { } catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e); LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
} }
@@ -0,0 +1,32 @@
package jadx.api.args;
public enum DeobfuscationMapFileMode {
/**
* Load if found, don't save (default)
*/
READ,
/**
* Load if found, save only if new (don't overwrite)
*/
READ_OR_SAVE,
/**
* Don't load, always save
*/
OVERWRITE,
/**
* Don't load and don't save
*/
IGNORE;
public boolean shouldRead() {
return this == READ || this == READ_OR_SAVE;
}
public boolean shouldWrite() {
return this == READ_OR_SAVE || this == OVERWRITE;
}
}
+81 -18
View File
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel; import jadx.api.CommentsLevel;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.visitors.AnonymousClassVisitor; import jadx.core.dex.visitors.AnonymousClassVisitor;
import jadx.core.dex.visitors.AttachCommentsVisitor; import jadx.core.dex.visitors.AttachCommentsVisitor;
import jadx.core.dex.visitors.AttachMethodDetails; import jadx.core.dex.visitors.AttachMethodDetails;
@@ -32,6 +33,7 @@ import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.InlineMethods; import jadx.core.dex.visitors.InlineMethods;
import jadx.core.dex.visitors.MarkMethodsForInline; import jadx.core.dex.visitors.MarkMethodsForInline;
import jadx.core.dex.visitors.MethodInvokeVisitor; import jadx.core.dex.visitors.MethodInvokeVisitor;
import jadx.core.dex.visitors.MethodVisitor;
import jadx.core.dex.visitors.ModVisitor; import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.MoveInlineVisitor; import jadx.core.dex.visitors.MoveInlineVisitor;
import jadx.core.dex.visitors.OverrideMethodVisitor; import jadx.core.dex.visitors.OverrideMethodVisitor;
@@ -60,8 +62,10 @@ import jadx.core.dex.visitors.rename.CodeRenameVisitor;
import jadx.core.dex.visitors.rename.RenameVisitor; import jadx.core.dex.visitors.rename.RenameVisitor;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.dex.visitors.usage.UsageInfoVisitor; import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class Jadx { public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class); private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
@@ -69,21 +73,20 @@ public class Jadx {
private Jadx() { private Jadx() {
} }
static { public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
if (Consts.DEBUG) { switch (args.getDecompilationMode()) {
LOG.info("debug enabled"); case AUTO:
case RESTRUCTURE:
return getRegionsModePasses(args);
case SIMPLE:
return getSimpleModePasses(args);
case FALLBACK:
return getFallbackPassesList();
default:
throw new JadxRuntimeException("Unknown decompilation mode: " + args.getDecompilationMode());
} }
} }
public static List<IDexTreeVisitor> getFallbackPassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new AttachTryCatchVisitor());
passes.add(new AttachCommentsVisitor());
passes.add(new ProcessInstructionsVisitor());
passes.add(new FallbackModeVisitor());
return passes;
}
public static List<IDexTreeVisitor> getPreDecompilePassesList() { public static List<IDexTreeVisitor> getPreDecompilePassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>(); List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new SignatureProcessor()); passes.add(new SignatureProcessor());
@@ -95,12 +98,8 @@ public class Jadx {
return passes; return passes;
} }
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) { public static List<IDexTreeVisitor> getRegionsModePasses(JadxArgs args) {
if (args.isFallbackMode()) {
return getFallbackPassesList();
}
List<IDexTreeVisitor> passes = new ArrayList<>(); List<IDexTreeVisitor> passes = new ArrayList<>();
// instructions IR // instructions IR
passes.add(new CheckCode()); passes.add(new CheckCode());
if (args.isDebugInfo()) { if (args.isDebugInfo()) {
@@ -132,6 +131,7 @@ public class Jadx {
if (args.isDebugInfo()) { if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor()); passes.add(new DebugInfoApplyVisitor());
} }
passes.add(new FinishTypeInference());
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) { if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
passes.add(new ProcessKotlinInternals()); passes.add(new ProcessKotlinInternals());
} }
@@ -178,7 +178,69 @@ public class Jadx {
return passes; return passes;
} }
public static List<IDexTreeVisitor> getSimpleModePasses(JadxArgs args) {
List<IDexTreeVisitor> passes = new ArrayList<>();
if (args.isDebugInfo()) {
passes.add(new DebugInfoAttachVisitor());
}
passes.add(new AttachTryCatchVisitor());
if (args.getCommentsLevel() != CommentsLevel.NONE) {
passes.add(new AttachCommentsVisitor());
}
passes.add(new AttachMethodDetails());
passes.add(new ProcessInstructionsVisitor());
passes.add(new BlockSplitter());
passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK)));
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK)));
passes.add(new BlockProcessor());
passes.add(new SSATransform());
passes.add(new MoveInlineVisitor());
passes.add(new ConstructorVisitor());
passes.add(new InitCodeVariables());
passes.add(new ConstInlineVisitor());
passes.add(new TypeInferenceVisitor());
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new FinishTypeInference());
passes.add(new CodeRenameVisitor());
passes.add(new DeboxingVisitor());
passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode());
passes.add(new CodeShrinkVisitor());
passes.add(new SimplifyVisitor());
passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE)));
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump());
}
return passes;
}
public static List<IDexTreeVisitor> getFallbackPassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new AttachTryCatchVisitor());
passes.add(new AttachCommentsVisitor());
passes.add(new ProcessInstructionsVisitor());
passes.add(new FallbackModeVisitor());
return passes;
}
public static final String VERSION_DEV = "dev";
private static String version;
public static String getVersion() { public static String getVersion() {
if (version != null) {
return version;
}
try { try {
ClassLoader classLoader = Jadx.class.getClassLoader(); ClassLoader classLoader = Jadx.class.getClassLoader();
if (classLoader != null) { if (classLoader != null) {
@@ -188,6 +250,7 @@ public class Jadx {
Manifest manifest = new Manifest(is); Manifest manifest = new Manifest(is);
String ver = manifest.getMainAttributes().getValue("jadx-version"); String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null) { if (ver != null) {
version = ver;
return ver; return ver;
} }
} }
@@ -196,6 +259,6 @@ public class Jadx {
} catch (Exception e) { } catch (Exception e) {
LOG.error("Can't get manifest file", e); LOG.error("Can't get manifest file", e);
} }
return "dev"; return VERSION_DEV;
} }
} }
@@ -1,13 +1,19 @@
package jadx.core; package jadx.core;
import java.util.List;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo; import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.core.codegen.CodeGen; import jadx.core.codegen.CodeGen;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.LoadStage; import jadx.core.dex.nodes.LoadStage;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.DepthTraversal; import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -18,13 +24,17 @@ import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE; import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED; import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
public final class ProcessClass { public class ProcessClass {
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
private ProcessClass() { private final List<IDexTreeVisitor> passes;
public ProcessClass(JadxArgs args) {
this.passes = Jadx.getPassesList(args);
} }
@Nullable @Nullable
private static ICodeInfo process(ClassNode cls, boolean codegen) { private ICodeInfo process(ClassNode cls, boolean codegen) {
if (!codegen && cls.getState() == PROCESS_COMPLETE) { if (!codegen && cls.getState() == PROCESS_COMPLETE) {
// nothing to do // nothing to do
return null; return null;
@@ -34,17 +44,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);
@@ -58,7 +68,7 @@ public final class ProcessClass {
} }
if (cls.getState() == LOADED) { if (cls.getState() == LOADED) {
cls.setState(PROCESS_STARTED); cls.setState(PROCESS_STARTED);
for (IDexTreeVisitor visitor : cls.root().getPasses()) { for (IDexTreeVisitor visitor : passes) {
DepthTraversal.visit(visitor, cls); DepthTraversal.visit(visitor, cls);
} }
cls.setState(PROCESS_COMPLETE); cls.setState(PROCESS_COMPLETE);
@@ -83,7 +93,7 @@ public final class ProcessClass {
} }
@NotNull @NotNull
public static ICodeInfo generateCode(ClassNode cls) { public ICodeInfo generateCode(ClassNode cls) {
ClassNode topParentClass = cls.getTopParentClass(); ClassNode topParentClass = cls.getTopParentClass();
if (topParentClass != cls) { if (topParentClass != cls) {
return generateCode(topParentClass); return generateCode(topParentClass);
@@ -107,4 +117,19 @@ public final class ProcessClass {
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e); throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
} }
} }
public void initPasses(RootNode root) {
for (IDexTreeVisitor pass : passes) {
try {
pass.init(root);
} catch (Exception e) {
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
}
}
}
// TODO: make passes list private and not visible
public List<IDexTreeVisitor> getPasses() {
return passes;
}
} }
@@ -13,8 +13,8 @@ import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation; import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.data.attributes.JadxAttrType; import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr; import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr;
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr; import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.dex.attributes.IAttributeNode; import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
@@ -48,7 +48,7 @@ public class AnnotationGen {
add(field, code); add(field, code);
} }
public void addForParameter(ICodeWriter code, MethodParamsAttr paramsAnnotations, int n) { public void addForParameter(ICodeWriter code, AnnotationMethodParamsAttr paramsAnnotations, int n) {
List<AnnotationsAttr> paramList = paramsAnnotations.getParamList(); List<AnnotationsAttr> paramList = paramsAnnotations.getParamList();
if (n >= paramList.size()) { if (n >= paramList.size()) {
return; return;
@@ -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;
} }
} }
@@ -356,7 +356,7 @@ public class ClassGen {
badCode = false; badCode = false;
} }
MethodGen mthGen; MethodGen mthGen;
if (badCode || fallback || mth.contains(AType.JADX_ERROR) || mth.getRegion() == null) { if (badCode || fallback || mth.contains(AType.JADX_ERROR)) {
mthGen = MethodGen.getFallbackMethodGen(mth); mthGen = MethodGen.getFallbackMethodGen(mth);
} else { } else {
mthGen = new MethodGen(this, mth); mthGen = new MethodGen(this, mth);
@@ -459,7 +459,7 @@ public class ClassGen {
} }
if (f.getCls() != null) { if (f.getCls() != null) {
code.add(' '); code.add(' ');
new ClassGen(f.getCls(), this).addClassBody(code); new ClassGen(f.getCls(), this).addClassBody(code, true);
} }
if (it.hasNext()) { if (it.hasNext()) {
code.add(','); code.add(',');
@@ -526,12 +526,42 @@ public class ClassGen {
if (outerType != null) { if (outerType != null) {
useClass(code, outerType); useClass(code, outerType);
code.add('.'); code.add('.');
// import not needed, force use short name addInnerType(code, type);
useClassShortName(code, type.getObject());
return; return;
} }
useClass(code, ClassInfo.fromType(cls.root(), type)); useClass(code, ClassInfo.fromType(cls.root(), type));
addGenerics(code, type);
}
private void addInnerType(ICodeWriter code, ArgType baseType) {
ArgType innerType = baseType.getInnerType();
ArgType outerType = innerType.getOuterType();
if (outerType != null) {
useClassWithShortName(code, baseType, outerType);
code.add('.');
addInnerType(code, innerType);
return;
}
useClassWithShortName(code, baseType, innerType);
}
private void useClassWithShortName(ICodeWriter code, ArgType baseType, ArgType type) {
String fullNameObj;
if (type.getObject().contains(".")) {
fullNameObj = type.getObject();
} else {
fullNameObj = baseType.getObject();
}
ClassInfo classInfo = ClassInfo.fromName(cls.root(), fullNameObj);
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
code.add(classInfo.getAliasShortName());
addGenerics(code, type);
}
private void addGenerics(ICodeWriter code, ArgType type) {
List<ArgType> generics = type.getGenericTypes(); List<ArgType> generics = type.getGenericTypes();
if (generics != null) { if (generics != null) {
code.add('<'); code.add('<');
@@ -556,15 +586,6 @@ public class ClassGen {
} }
} }
private void useClassShortName(ICodeWriter code, String object) {
ClassInfo classInfo = ClassInfo.fromName(cls.root(), object);
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
code.add(classInfo.getAliasShortName());
}
public void useClass(ICodeWriter code, ClassInfo classInfo) { public void useClass(ICodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.root().resolveClass(classInfo); ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) { if (classNode != null) {
@@ -590,6 +611,9 @@ public class ClassGen {
return fullName; return fullName;
} }
String shortName = extClsInfo.getAliasShortName(); String shortName = extClsInfo.getAliasShortName();
if (useCls.equals(extClsInfo)) {
return shortName;
}
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) { if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
return shortName; return shortName;
} }
@@ -599,6 +623,9 @@ public class ClassGen {
if (extClsInfo.isInner()) { if (extClsInfo.isInner()) {
return expandInnerClassName(useCls, extClsInfo); return expandInnerClassName(useCls, extClsInfo);
} }
if (searchCollision(cls.root(), useCls, extClsInfo)) {
return fullName;
}
if (isBothClassesInOneTopClass(useCls, extClsInfo)) { if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
return shortName; return shortName;
} }
@@ -606,9 +633,6 @@ public class ClassGen {
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) { if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
return shortName; return shortName;
} }
if (searchCollision(cls.root(), useCls, extClsInfo)) {
return fullName;
}
// ignore classes from default package // ignore classes from default package
if (extClsInfo.isDefaultPackage()) { if (extClsInfo.isDefaultPackage()) {
return shortName; return shortName;
@@ -20,6 +20,7 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.GenericInfoAttr; import jadx.core.dex.attributes.nodes.GenericInfoAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
@@ -50,6 +51,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.instructions.mods.TernaryInsn; import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
@@ -172,7 +174,7 @@ public class InsnGen {
private void instanceField(ICodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException { private void instanceField(ICodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
ClassNode pCls = mth.getParentClass(); ClassNode pCls = mth.getParentClass();
FieldNode fieldNode = pCls.root().deepResolveField(field); FieldNode fieldNode = pCls.root().resolveField(field);
if (fieldNode != null) { if (fieldNode != null) {
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE); FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
if (replace != null) { if (replace != null) {
@@ -210,7 +212,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);
} }
@@ -517,7 +519,7 @@ public class InsnGen {
code.add(' '); code.add(' ');
code.add(ifInsn.getOp().getSymbol()).add(' '); code.add(ifInsn.getOp().getSymbol()).add(' ');
addArg(code, insn.getArg(1)); addArg(code, insn.getArg(1));
code.add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget())); code.add(") goto ").add(MethodGen.getLabelName(ifInsn));
break; break;
case GOTO: case GOTO:
@@ -538,13 +540,24 @@ public class InsnGen {
code.add(") {"); code.add(") {");
code.incIndent(); code.incIndent();
int[] keys = sw.getKeys(); int[] keys = sw.getKeys();
int[] targets = sw.getTargets(); int size = keys.length;
for (int i = 0; i < keys.length; i++) { BlockNode[] targetBlocks = sw.getTargetBlocks();
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto "); if (targetBlocks != null) {
code.add(MethodGen.getLabelName(targets[i])).add(';'); for (int i = 0; i < size; i++) {
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
code.add(MethodGen.getLabelName(targetBlocks[i])).add(';');
}
code.startLine("default: goto ");
code.add(MethodGen.getLabelName(sw.getDefTargetBlock())).add(';');
} else {
int[] targets = sw.getTargets();
for (int i = 0; i < size; i++) {
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
code.add(MethodGen.getLabelName(targets[i])).add(';');
}
code.startLine("default: goto ");
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
} }
code.startLine("default: goto ");
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
code.decIndent(); code.decIndent();
code.startLine('}'); code.startLine('}');
break; break;
@@ -682,19 +695,27 @@ public class InsnGen {
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!"); throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
} }
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth()); MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
MethodNode refMth = callMth;
if (callMth != null) {
MethodReplaceAttr replaceAttr = callMth.get(AType.METHOD_REPLACE);
if (replaceAttr != null) {
refMth = replaceAttr.getReplaceMth();
}
}
if (insn.isSuper()) { if (insn.isSuper()) {
code.attachAnnotation(callMth); code.attachAnnotation(refMth);
code.add("super"); code.add("super");
} else if (insn.isThis()) { } else if (insn.isThis()) {
code.attachAnnotation(callMth); code.attachAnnotation(refMth);
code.add("this"); code.add("this");
} else { } else {
code.add("new "); code.add("new ");
if (callMth == null || callMth.contains(AFlag.DONT_GENERATE)) { if (refMth == null || refMth.contains(AFlag.DONT_GENERATE)) {
// use class reference if constructor method is missing (default constructor) // use class reference if constructor method is missing (default constructor)
code.attachAnnotation(mth.root().resolveClass(insn.getCallMth().getDeclClass())); code.attachAnnotation(mth.root().resolveClass(insn.getCallMth().getDeclClass()));
} else { } else {
code.attachAnnotation(callMth); code.attachAnnotation(refMth);
} }
mgen.getClassGen().addClsName(code, insn.getClassType()); mgen.getClassGen().addClsName(code, insn.getClassType());
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO); GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
@@ -719,13 +740,13 @@ public class InsnGen {
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException { private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
if (this.mth.getParentClass() == cls) { if (this.mth.getParentClass() == cls) {
cls.remove(AFlag.ANONYMOUS_CLASS); cls.remove(AType.ANONYMOUS_CLASS);
cls.remove(AFlag.DONT_GENERATE); cls.remove(AFlag.DONT_GENERATE);
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN); mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
throw new CodegenException("Anonymous inner class unlimited recursion detected." throw new CodegenException("Anonymous inner class unlimited recursion detected."
+ " Convert class to inner: " + cls.getClassInfo().getFullName()); + " Convert class to inner: " + cls.getClassInfo().getFullName());
} }
ArgType parent = cls.get(AType.ANONYMOUS_CLASS_BASE).getBaseType(); ArgType parent = cls.get(AType.ANONYMOUS_CLASS).getBaseType();
// hide empty anonymous constructors // hide empty anonymous constructors
for (MethodNode ctor : cls.getMethods()) { for (MethodNode ctor : cls.getMethods()) {
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR) if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
@@ -764,7 +785,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) {
@@ -6,17 +6,19 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel; import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter; import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.data.annotations.InsnCodeOffset; import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef; import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.plugins.input.data.AccessFlags; import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType; import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr; import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.Jadx; import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
@@ -32,13 +34,14 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar; import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.visitors.DepthTraversal; import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.CodeGenUtils; import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxOverflowException; import jadx.core.utils.exceptions.JadxOverflowException;
@@ -195,7 +198,7 @@ public class MethodGen {
} }
private void addMethodArguments(ICodeWriter code, List<RegisterArg> args) { private void addMethodArguments(ICodeWriter code, List<RegisterArg> args) {
MethodParamsAttr paramsAnnotation = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS); AnnotationMethodParamsAttr paramsAnnotation = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS);
int i = 0; int i = 0;
Iterator<RegisterArg> it = args.iterator(); Iterator<RegisterArg> it = args.iterator();
while (it.hasNext()) { while (it.hasNext()) {
@@ -238,10 +241,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()) {
@@ -251,12 +255,28 @@ public class MethodGen {
} }
public void addInstructions(ICodeWriter code) throws CodegenException { public void addInstructions(ICodeWriter code) throws CodegenException {
if (mth.root().getArgs().isFallbackMode()) { JadxArgs args = mth.root().getArgs();
addFallbackMethodCode(code, FALLBACK_MODE); switch (args.getDecompilationMode()) {
} else if (classGen.isFallbackMode()) { case AUTO:
dumpInstructions(code); if (classGen.isFallbackMode()) {
} else { // TODO: try simple mode first
addRegionInsns(code); dumpInstructions(code);
} else {
addRegionInsns(code);
}
break;
case RESTRUCTURE:
addRegionInsns(code);
break;
case SIMPLE:
addSimpleMethodCode(code);
break;
case FALLBACK:
addFallbackMethodCode(code, FALLBACK_MODE);
break;
} }
} }
@@ -278,6 +298,59 @@ public class MethodGen {
} }
} }
private void addSimpleMethodCode(ICodeWriter code) {
if (mth.getBasicBlocks() == null) {
code.startLine("// Blocks not ready for simple mode, using fallback");
addFallbackMethodCode(code, FALLBACK_MODE);
return;
}
JadxArgs args = mth.root().getArgs();
ICodeWriter tmpCode = args.getCodeWriterProvider().apply(args);
try {
tmpCode.setIndent(code.getIndent());
generateSimpleCode(tmpCode);
code.add(tmpCode);
} catch (Exception e) {
mth.addError("Simple mode code generation failed", e);
CodeGenUtils.addError(code, "Simple mode code generation failed", e);
dumpInstructions(code);
}
}
private void generateSimpleCode(ICodeWriter code) throws CodegenException {
SimpleModeHelper helper = new SimpleModeHelper(mth);
List<BlockNode> blocks = helper.prepareBlocks();
InsnGen insnGen = new InsnGen(this, true);
for (BlockNode block : blocks) {
if (block.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (helper.isNeedStartLabel(block)) {
code.decIndent();
code.startLine(getLabelName(block)).add(':');
code.incIndent();
}
for (InsnNode insn : block.getInstructions()) {
if (!insn.contains(AFlag.DONT_GENERATE)) {
if (insn.getResult() != null) {
CodeVar codeVar = insn.getResult().getSVar().getCodeVar();
if (!codeVar.isDeclared()) {
insn.add(AFlag.DECLARE_VAR);
codeVar.setDeclared(true);
}
}
InsnCodeOffset.attach(code, insn);
insnGen.makeInsn(insn, code);
addCatchComment(code, insn, false);
CodeGenUtils.addCodeComments(code, mth, insn);
}
}
if (helper.isNeedEndGoto(block)) {
code.startLine("goto ").add(getLabelName(block.getSuccessors().get(0)));
}
}
}
public void dumpInstructions(ICodeWriter code) { public void dumpInstructions(ICodeWriter code) {
if (mth.checkCommentsLevel(CommentsLevel.ERROR)) { if (mth.checkCommentsLevel(CommentsLevel.ERROR)) {
code.startLine("/*"); code.startLine("/*");
@@ -352,60 +425,81 @@ public class MethodGen {
public static void addFallbackInsns(ICodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) { public static void addFallbackInsns(ICodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
int startIndent = code.getIndent(); int startIndent = code.getIndent();
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true); MethodGen methodGen = getFallbackMethodGen(mth);
InsnGen insnGen = new InsnGen(methodGen, true);
InsnNode prevInsn = null; InsnNode prevInsn = null;
for (InsnNode insn : insnArr) { for (InsnNode insn : insnArr) {
if (insn == null) { if (insn == null) {
continue; continue;
} }
if (insn.contains(AType.JADX_ERROR)) { methodGen.dumpInsn(code, insnGen, option, startIndent, prevInsn, insn);
for (JadxError error : insn.getAll(AType.JADX_ERROR)) { prevInsn = insn;
code.startLine("// ").add(error.getError()); }
} }
continue;
private boolean dumpInsn(ICodeWriter code, InsnGen insnGen, FallbackOption option, int startIndent,
@Nullable InsnNode prevInsn, InsnNode insn) {
if (insn.contains(AType.JADX_ERROR)) {
for (JadxError error : insn.getAll(AType.JADX_ERROR)) {
code.startLine("// ").add(error.getError());
} }
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) { return true;
}
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ':');
code.incIndent();
}
if (insn.getType() == InsnType.NOP) {
return true;
}
try {
boolean escapeComment = isCommentEscapeNeeded(insn, option);
if (escapeComment) {
code.decIndent(); code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ':'); code.startLine("*/");
code.startLine("// ");
} else {
code.startLineWithNum(insn.getSourceLine());
}
InsnCodeOffset.attach(code, insn);
RegisterArg resArg = insn.getResult();
if (resArg != null) {
ArgType varType = resArg.getInitType();
if (varType.isTypeKnown()) {
code.add(varType.toString()).add(' ');
}
}
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
if (escapeComment) {
code.startLine("/*");
code.incIndent(); code.incIndent();
} }
if (insn.getType() == InsnType.NOP) { addCatchComment(code, insn, true);
continue; CodeGenUtils.addCodeComments(code, mth, insn);
} } catch (Exception e) {
try { LOG.debug("Error generate fallback instruction: ", e.getCause());
boolean escapeComment = isCommentEscapeNeeded(insn, option); code.setIndent(startIndent);
if (escapeComment) { code.startLine("// error: " + insn);
code.decIndent(); }
code.startLine("*/"); return false;
code.startLine("// "); }
} else {
code.startLineWithNum(insn.getSourceLine());
}
InsnCodeOffset.attach(code, insn);
RegisterArg resArg = insn.getResult();
if (resArg != null) {
ArgType varType = resArg.getInitType();
if (varType.isTypeKnown()) {
code.add(varType.toString()).add(' ');
}
}
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
if (escapeComment) {
code.startLine("/*");
code.incIndent();
}
CatchAttr catchAttr = insn.get(AType.EXC_CATCH); private void addCatchComment(ICodeWriter code, InsnNode insn, boolean raw) {
if (catchAttr != null) { CatchAttr catchAttr = insn.get(AType.EXC_CATCH);
code.add(" // " + catchAttr); if (catchAttr == null) {
} return;
CodeGenUtils.addCodeComments(code, mth, insn); }
} catch (Exception e) { code.add(" // Catch:");
LOG.debug("Error generate fallback instruction: ", e.getCause()); for (ExceptionHandler handler : catchAttr.getHandlers()) {
code.setIndent(startIndent); code.add(' ');
code.startLine("// error: " + insn); classGen.useClass(code, handler.getArgType());
code.add(" -> ");
if (raw) {
code.add(getLabelName(handler.getHandlerOffset()));
} else {
code.add(getLabelName(handler.getHandlerBlock()));
} }
prevInsn = insn;
} }
} }
@@ -448,7 +542,22 @@ public class MethodGen {
return new MethodGen(clsGen, mth); return new MethodGen(clsGen, mth);
} }
public static String getLabelName(BlockNode block) {
return String.format("L%d", block.getId());
}
public static String getLabelName(IfNode insn) {
BlockNode thenBlock = insn.getThenBlock();
if (thenBlock != null) {
return getLabelName(thenBlock);
}
return getLabelName(insn.getTarget());
}
public static String getLabelName(int offset) { public static String getLabelName(int offset) {
return "L_" + InsnUtils.formatOffset(offset); if (offset < 0) {
return String.format("LB_%x", -offset);
}
return String.format("L%x", offset);
} }
} }
@@ -268,13 +268,17 @@ public class NameGen {
private String makeNameFromInvoke(MethodInfo callMth) { private String makeNameFromInvoke(MethodInfo callMth) {
String name = callMth.getName(); String name = callMth.getName();
ArgType declType = callMth.getDeclClass().getType();
if ("getInstance".equals(name)) {
// e.g. Cipher.getInstance
return makeNameForType(declType);
}
if (name.startsWith("get") || name.startsWith("set")) { if (name.startsWith("get") || name.startsWith("set")) {
return fromName(name.substring(3)); return fromName(name.substring(3));
} }
if ("iterator".equals(name)) { if ("iterator".equals(name)) {
return "it"; return "it";
} }
ArgType declType = callMth.getDeclClass().getType();
if ("toString".equals(name)) { if ("toString".equals(name)) {
return makeNameForType(declType); return makeNameForType(declType);
} }
@@ -0,0 +1,149 @@
package jadx.core.codegen;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.TargetInsnNode;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.visitors.blocks.BlockProcessor;
import jadx.core.dex.visitors.blocks.BlockSplitter;
import jadx.core.utils.BlockUtils;
public class SimpleModeHelper {
private final MethodNode mth;
private final BitSet startLabel;
private final BitSet endGoto;
public SimpleModeHelper(MethodNode mth) {
this.mth = mth;
this.startLabel = BlockUtils.newBlocksBitSet(mth);
this.endGoto = BlockUtils.newBlocksBitSet(mth);
}
public List<BlockNode> prepareBlocks() {
removeEmptyBlocks();
List<BlockNode> blocksList = getSortedBlocks();
blocksList.removeIf(b -> b.equals(mth.getEnterBlock()) || b.equals(mth.getExitBlock()));
unbindExceptionHandlers();
if (blocksList.isEmpty()) {
return Collections.emptyList();
}
@Nullable
BlockNode prev = null;
int blocksCount = blocksList.size();
for (int i = 0; i < blocksCount; i++) {
BlockNode block = blocksList.get(i);
BlockNode nextBlock = i + 1 == blocksCount ? null : blocksList.get(i + 1);
List<BlockNode> preds = block.getPredecessors();
int predsCount = preds.size();
if (predsCount > 1) {
startLabel.set(block.getId());
} else if (predsCount == 1 && prev != null) {
if (!prev.equals(preds.get(0))) {
startLabel.set(block.getId());
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) {
endGoto.set(prev.getId());
}
}
}
InsnNode lastInsn = BlockUtils.getLastInsn(block);
if (lastInsn instanceof TargetInsnNode) {
processTargetInsn(block, lastInsn, nextBlock);
}
if (block.contains(AType.EXC_HANDLER)) {
startLabel.set(block.getId());
}
if (nextBlock == null && !mth.isPreExitBlocks(block)) {
endGoto.set(block.getId());
}
prev = block;
}
if (mth.isVoidReturn()) {
int last = blocksList.size() - 1;
if (blocksList.get(last).contains(AFlag.RETURN)) {
// remove trailing return
blocksList.remove(last);
}
}
return blocksList;
}
private void removeEmptyBlocks() {
for (BlockNode block : mth.getBasicBlocks()) {
if (block.getInstructions().isEmpty()
&& block.getPredecessors().size() > 0
&& block.getSuccessors().size() == 1) {
BlockNode successor = block.getSuccessors().get(0);
List<BlockNode> predecessors = block.getPredecessors();
BlockSplitter.removeConnection(block, successor);
if (predecessors.size() == 1) {
BlockSplitter.replaceConnection(predecessors.get(0), block, successor);
} else {
for (BlockNode pred : new ArrayList<>(predecessors)) {
BlockSplitter.replaceConnection(pred, block, successor);
}
}
block.add(AFlag.REMOVE);
}
}
BlockProcessor.removeMarkedBlocks(mth);
}
private void unbindExceptionHandlers() {
if (mth.isNoExceptionHandlers()) {
return;
}
for (ExceptionHandler handler : mth.getExceptionHandlers()) {
BlockNode handlerBlock = handler.getHandlerBlock();
if (handlerBlock != null) {
BlockSplitter.removePredecessors(handlerBlock);
}
}
}
private void processTargetInsn(BlockNode block, InsnNode lastInsn, @Nullable BlockNode next) {
if (lastInsn instanceof IfNode) {
IfNode ifInsn = (IfNode) lastInsn;
BlockNode thenBlock = ifInsn.getThenBlock();
if (Objects.equals(next, thenBlock)) {
ifInsn.invertCondition();
startLabel.set(ifInsn.getThenBlock().getId());
} else {
startLabel.set(thenBlock.getId());
}
ifInsn.normalize();
} else {
for (BlockNode successor : block.getSuccessors()) {
startLabel.set(successor.getId());
}
}
}
public boolean isNeedStartLabel(BlockNode block) {
return startLabel.get(block.getId());
}
public boolean isNeedEndGoto(BlockNode block) {
return endGoto.get(block.getId());
}
// DFS sort blocks to reduce goto count
private List<BlockNode> getSortedBlocks() {
List<BlockNode> list = new ArrayList<>(mth.getBasicBlocks().size());
BlockUtils.dfsVisit(mth, list::add);
return list;
}
}
@@ -0,0 +1,24 @@
package jadx.core.deobf;
public class ClsAliasPair {
private final String pkg;
private final String name;
public ClsAliasPair(String pkg, String name) {
this.pkg = pkg;
this.name = name;
}
public String getPkg() {
return pkg;
}
public String getName() {
return name;
}
@Override
public String toString() {
return pkg + '.' + name;
}
}
@@ -12,11 +12,11 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
@@ -37,28 +37,21 @@ public class DeobfPresets {
private final Map<String, String> fldPresetMap = new HashMap<>(); private final Map<String, String> fldPresetMap = new HashMap<>();
private final Map<String, String> mthPresetMap = new HashMap<>(); private final Map<String, String> mthPresetMap = new HashMap<>();
@Nullable
public static DeobfPresets build(RootNode root) { public static DeobfPresets build(RootNode root) {
Path deobfMapPath = getPathDeobfMapPath(root); Path deobfMapPath = getPathDeobfMapPath(root);
if (deobfMapPath == null) { if (root.getArgs().getDeobfuscationMapFileMode() != DeobfuscationMapFileMode.IGNORE) {
return null; LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
} }
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
return new DeobfPresets(deobfMapPath); return new DeobfPresets(deobfMapPath);
} }
@Nullable
private static Path getPathDeobfMapPath(RootNode root) { private static Path getPathDeobfMapPath(RootNode root) {
JadxArgs jadxArgs = root.getArgs(); JadxArgs jadxArgs = root.getArgs();
File deobfMapFile = jadxArgs.getDeobfuscationMapFile(); File deobfMapFile = jadxArgs.getDeobfuscationMapFile();
if (deobfMapFile != null) { if (deobfMapFile != null) {
return deobfMapFile.toPath(); return deobfMapFile.toPath();
} }
List<File> inputFiles = jadxArgs.getInputFiles(); Path inputFilePath = jadxArgs.getInputFiles().get(0).toPath().toAbsolutePath();
if (inputFiles.isEmpty()) {
return null;
}
Path inputFilePath = inputFiles.get(0).toPath().toAbsolutePath();
String baseName = FileUtils.getPathBaseName(inputFilePath); String baseName = FileUtils.getPathBaseName(inputFilePath);
return inputFilePath.getParent().resolve(baseName + ".jobf"); return inputFilePath.getParent().resolve(baseName + ".jobf");
} }
@@ -70,9 +63,9 @@ public class DeobfPresets {
/** /**
* Loads deobfuscator presets * Loads deobfuscator presets
*/ */
public void load() { public boolean load() {
if (!Files.exists(deobfMapFile)) { if (!Files.exists(deobfMapFile)) {
return; return false;
} }
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath()); LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
try { try {
@@ -106,8 +99,10 @@ public class DeobfPresets {
break; break;
} }
} }
return true;
} catch (Exception e) { } catch (Exception e) {
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e); LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
return false;
} }
} }
@@ -142,9 +137,7 @@ public class DeobfPresets {
} }
Files.write(deobfMapFile, list, MAP_FILE_CHARSET, Files.write(deobfMapFile, list, MAP_FILE_CHARSET,
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
if (LOG.isDebugEnabled()) { LOG.info("Deobfuscation map file saved as: {}", deobfMapFile);
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
}
} }
public String getForCls(ClassInfo cls) { public String getForCls(ClassInfo cls) {
@@ -16,6 +16,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.plugins.input.data.attributes.JadxAttrType; import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr; import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
@@ -76,22 +77,25 @@ public class Deobfuscator {
} }
public void execute() { public void execute() {
if (!args.isDeobfuscationForceSave()) { if (args.getDeobfuscationMapFileMode().shouldRead()) {
deobfPresets.load(); if (deobfPresets.load()) {
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) { for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue()); addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
}
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
initIndexes();
} }
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
initIndexes();
} }
process(); process();
} }
public void savePresets() { public void savePresets() {
DeobfuscationMapFileMode mode = args.getDeobfuscationMapFileMode();
if (!mode.shouldWrite()) {
return;
}
Path deobfMapFile = deobfPresets.getDeobfMapFile(); Path deobfMapFile = deobfPresets.getDeobfMapFile();
if (Files.exists(deobfMapFile) && !args.isDeobfuscationForceSave()) { if (mode == DeobfuscationMapFileMode.READ_OR_SAVE && Files.exists(deobfMapFile)) {
LOG.info("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
deobfMapFile.toAbsolutePath());
return; return;
} }
try { try {
@@ -112,16 +116,25 @@ public class Deobfuscator {
deobfPresets.getPkgPresetMap().put(p.getName(), p.getAlias()); deobfPresets.getPkgPresetMap().put(p.getName(), p.getAlias());
} }
} }
for (DeobfClsInfo deobfClsInfo : clsMap.values()) { for (ClassNode cls : root.getClasses()) {
if (deobfClsInfo.getAlias() != null) { ClassInfo classInfo = cls.getClassInfo();
deobfPresets.getClsPresetMap().put(deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias()); if (classInfo.hasAlias()) {
deobfPresets.getClsPresetMap().put(classInfo.makeRawFullName(), classInfo.getAliasShortName());
}
for (FieldNode fld : cls.getFields()) {
FieldInfo fieldInfo = fld.getFieldInfo();
if (fieldInfo.hasAlias()) {
deobfPresets.getFldPresetMap().put(fieldInfo.getRawFullId(), fld.getAlias());
}
}
for (MethodNode mth : cls.getMethods()) {
MethodInfo methodInfo = mth.getMethodInfo();
if (methodInfo.hasAlias()) {
deobfPresets.getMthPresetMap().put(methodInfo.getRawFullId(), methodInfo.getAlias());
}
} }
}
for (FieldInfo fld : fldMap.keySet()) {
deobfPresets.getFldPresetMap().put(fld.getRawFullId(), fld.getAlias());
}
for (MethodInfo mth : mthMap.keySet()) {
deobfPresets.getMthPresetMap().put(mth.getRawFullId(), mth.getAlias());
} }
} }
@@ -377,10 +390,10 @@ public class Deobfuscator {
String alias = null; String alias = null;
String pkgName = null; String pkgName = null;
if (this.parseKotlinMetadata) { if (this.parseKotlinMetadata) {
ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls); ClsAliasPair kotlinCls = KotlinMetadataUtils.getClassAlias(cls);
if (kotlinCls != null) { if (kotlinCls != null) {
alias = prepareNameFull(kotlinCls.getShortName(), "C"); alias = kotlinCls.getName();
pkgName = kotlinCls.getPackage(); pkgName = kotlinCls.getPkg();
} }
} }
if (alias == null && this.useSourceNameAsAlias) { if (alias == null && this.useSourceNameAsAlias) {
@@ -572,6 +585,7 @@ public class Deobfuscator {
if (!pkg.hasAlias()) { if (!pkg.hasAlias()) {
String pkgName = pkg.getName(); String pkgName = pkg.getName();
if ((args.isDeobfuscationOn() && shouldRename(pkgName)) if ((args.isDeobfuscationOn() && shouldRename(pkgName))
&& (pkg.getParentPackage() != rootPackage || !TldHelper.contains(pkgName)) // check if first level is a valid tld
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName)) || (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName))
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) { || (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName())); String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
@@ -592,20 +606,6 @@ public class Deobfuscator {
return NameMapper.removeInvalidCharsMiddle(name); return NameMapper.removeInvalidCharsMiddle(name);
} }
private String prepareNameFull(String name, String prefix) {
if (name.length() > maxLength) {
return makeHashName(name, prefix);
}
String result = NameMapper.removeInvalidChars(name, prefix);
if (result.isEmpty()) {
return makeHashName(name, prefix);
}
if (NameMapper.isReserved(result)) {
return prefix + result;
}
return result;
}
private static String makeHashName(String name, String invalidPrefix) { private static String makeHashName(String name, String invalidPrefix) {
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode()); return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
} }
@@ -0,0 +1,38 @@
package jadx.core.deobf;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Provides a list of all top level domains with 3 characters and less,
* so we can exclude them from deobfuscation.
*/
public class TldHelper {
private static final Set<String> TLD_SET = loadTldFile();
private static Set<String> loadTldFile() {
Set<String> tldNames = new HashSet<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(Deobfuscator.class.getResourceAsStream("tld_3.txt")))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (!line.startsWith("#") && !line.isEmpty()) {
tldNames.add(line);
}
}
return tldNames;
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load top level domain list tld_3.txt", e);
}
}
public static boolean contains(String name) {
return TLD_SET.contains(name);
}
}
@@ -35,7 +35,6 @@ public enum AFlag {
SKIP_ARG, // skip argument in invoke call SKIP_ARG, // skip argument in invoke call
NO_SKIP_ARGS, NO_SKIP_ARGS,
ANONYMOUS_CONSTRUCTOR, ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS,
THIS, THIS,
SUPER, SUPER,
@@ -77,10 +76,13 @@ public enum AFlag {
INCONSISTENT_CODE, // warning about incorrect decompilation INCONSISTENT_CODE, // warning about incorrect decompilation
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
REQUEST_CODE_SHRINK,
RERUN_SSA_TRANSFORM, RERUN_SSA_TRANSFORM,
METHOD_CANDIDATE_FOR_INLINE, METHOD_CANDIDATE_FOR_INLINE,
DISABLE_BLOCKS_LOCK,
// Class processing flags // Class processing flags
RESTART_CODEGEN, // codegen must be executed again RESTART_CODEGEN, // codegen must be executed again
RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage
@@ -2,7 +2,7 @@ package jadx.core.dex.attributes;
import jadx.api.plugins.input.data.attributes.IJadxAttrType; import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute; import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.nodes.AnonymousClassBaseAttr; import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr; import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EdgeInsnAttr; import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
@@ -11,6 +11,7 @@ import jadx.core.dex.attributes.nodes.EnumMapAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr; import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.GenericInfoAttr; import jadx.core.dex.attributes.nodes.GenericInfoAttr;
import jadx.core.dex.attributes.nodes.InlinedAttr;
import jadx.core.dex.attributes.nodes.JadxCommentsAttr; import jadx.core.dex.attributes.nodes.JadxCommentsAttr;
import jadx.core.dex.attributes.nodes.JadxError; import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JumpInfo; import jadx.core.dex.attributes.nodes.JumpInfo;
@@ -20,11 +21,13 @@ import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodBridgeAttr; import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr; import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr; import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr; import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr; import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr; import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr; import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.attributes.nodes.SpecialEdgeAttr;
import jadx.core.dex.attributes.nodes.TmpEdgeAttr; import jadx.core.dex.attributes.nodes.TmpEdgeAttr;
import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.CatchAttr;
@@ -53,7 +56,8 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>(); public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>(); public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>(); public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
public static final AType<AnonymousClassBaseAttr> ANONYMOUS_CLASS_BASE = new AType<>(); public static final AType<AnonymousClassAttr> ANONYMOUS_CLASS = new AType<>();
public static final AType<InlinedAttr> INLINED = new AType<>();
// field // field
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>(); public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
@@ -62,11 +66,12 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
// method // method
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>(); public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>(); public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
public static final AType<MethodReplaceAttr> METHOD_REPLACE = new AType<>();
public static final AType<MethodBridgeAttr> BRIDGED_BY = new AType<>();
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>(); public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>(); public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>(); public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>(); public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>();
public static final AType<MethodBridgeAttr> BRIDGED_BY = new AType<>();
// region // region
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>(); public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
@@ -76,6 +81,7 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>(); public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>(); public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>(); public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
public static final AType<AttrList<SpecialEdgeAttr>> SPECIAL_EDGE = new AType<>();
public static final AType<TmpEdgeAttr> TMP_EDGE = new AType<>(); public static final AType<TmpEdgeAttr> TMP_EDGE = new AType<>();
public static final AType<TryCatchBlockAttr> TRY_BLOCK = new AType<>(); public static final AType<TryCatchBlockAttr> TRY_BLOCK = new AType<>();
@@ -0,0 +1,35 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
public class AnonymousClassAttr extends PinnedAttribute {
private final ClassNode outerCls;
private final ArgType baseType;
public AnonymousClassAttr(ClassNode outerCls, ArgType baseType) {
this.outerCls = outerCls;
this.baseType = baseType;
}
public ClassNode getOuterCls() {
return outerCls;
}
public ArgType getBaseType() {
return baseType;
}
@Override
public AType<AnonymousClassAttr> getAttrType() {
return AType.ANONYMOUS_CLASS;
}
@Override
public String toString() {
return "AnonymousClass{" + outerCls + ", base: " + baseType + '}';
}
}
@@ -1,28 +0,0 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.args.ArgType;
public class AnonymousClassBaseAttr extends PinnedAttribute {
private final ArgType baseType;
public AnonymousClassBaseAttr(ArgType baseType) {
this.baseType = baseType;
}
public ArgType getBaseType() {
return baseType;
}
@Override
public AType<AnonymousClassBaseAttr> getAttrType() {
return AType.ANONYMOUS_CLASS_BASE;
}
@Override
public String toString() {
return "AnonymousClassBaseAttr{" + baseType + '}';
}
}
@@ -0,0 +1,29 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.ClassNode;
public class InlinedAttr implements IJadxAttribute {
private final ClassNode inlineCls;
public InlinedAttr(ClassNode inlineCls) {
this.inlineCls = inlineCls;
}
public ClassNode getInlineCls() {
return inlineCls;
}
@Override
public IJadxAttrType<InlinedAttr> getAttrType() {
return AType.INLINED;
}
@Override
public String toString() {
return "INLINED: " + inlineCls;
}
}
@@ -50,11 +50,7 @@ public class JadxError implements Comparable<JadxError> {
@Override @Override
public String toString() { public String toString() {
StringBuilder str = new StringBuilder(); StringBuilder str = new StringBuilder();
str.append("JadxError: "); str.append("JadxError: ").append(error).append(' ');
if (error != null) {
str.append(error);
str.append(' ');
}
if (cause != null) { if (cause != null) {
str.append(cause.getClass()); str.append(cause.getClass());
str.append(':'); str.append(':');
@@ -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,31 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.MethodNode;
/**
* Calls of method should be replaced by provided method (used for synthetic methods redirect)
*/
public class MethodReplaceAttr extends PinnedAttribute {
private final MethodNode replaceMth;
public MethodReplaceAttr(MethodNode replaceMth) {
this.replaceMth = replaceMth;
}
public MethodNode getReplaceMth() {
return replaceMth;
}
@Override
public AType<MethodReplaceAttr> getAttrType() {
return AType.METHOD_REPLACE;
}
@Override
public String toString() {
return "REPLACED_BY: " + replaceMth;
}
}
@@ -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;
@@ -5,6 +5,7 @@ import java.util.List;
import jadx.api.plugins.input.insns.InsnData; import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
@@ -70,6 +71,15 @@ public class IfNode extends GotoNode {
elseBlock = tmp; elseBlock = tmp;
} }
/**
* Change 'a != false' to 'a == true'
*/
public void normalize() {
if (getOp() == IfOp.NE && getArg(1).isFalse()) {
changeCondition(IfOp.EQ, getArg(0), LiteralArg.litTrue());
}
}
public void changeCondition(IfOp op, InsnArg arg1, InsnArg arg2) { public void changeCondition(IfOp op, InsnArg arg1, InsnArg arg2) {
this.op = op; this.op = op;
setArg(0, arg1); setArg(0, arg1);
@@ -389,11 +389,11 @@ public class InsnDecoder {
return arrLenInsn; return arrLenInsn;
case AGET: case AGET:
return arrayGet(insn, ArgType.INT_FLOAT); return arrayGet(insn, ArgType.INT_FLOAT, ArgType.NARROW_NUMBERS_NO_BOOL);
case AGET_BOOLEAN: case AGET_BOOLEAN:
return arrayGet(insn, ArgType.BOOLEAN); return arrayGet(insn, ArgType.BOOLEAN);
case AGET_BYTE: case AGET_BYTE:
return arrayGet(insn, ArgType.BYTE); return arrayGet(insn, ArgType.BYTE, ArgType.NARROW_INTEGRAL);
case AGET_BYTE_BOOLEAN: case AGET_BYTE_BOOLEAN:
return arrayGet(insn, ArgType.BYTE_BOOLEAN); return arrayGet(insn, ArgType.BYTE_BOOLEAN);
case AGET_CHAR: case AGET_CHAR:
@@ -406,7 +406,7 @@ public class InsnDecoder {
return arrayGet(insn, ArgType.UNKNOWN_OBJECT); return arrayGet(insn, ArgType.UNKNOWN_OBJECT);
case APUT: case APUT:
return arrayPut(insn, ArgType.INT_FLOAT); return arrayPut(insn, ArgType.INT_FLOAT, ArgType.NARROW_NUMBERS_NO_BOOL);
case APUT_BOOLEAN: case APUT_BOOLEAN:
return arrayPut(insn, ArgType.BOOLEAN); return arrayPut(insn, ArgType.BOOLEAN);
case APUT_BYTE: case APUT_BYTE:
@@ -607,16 +607,24 @@ public class InsnDecoder {
} }
private InsnNode arrayGet(InsnData insn, ArgType argType) { private InsnNode arrayGet(InsnData insn, ArgType argType) {
return arrayGet(insn, argType, argType);
}
private InsnNode arrayGet(InsnData insn, ArgType arrElemType, ArgType resType) {
InsnNode inode = new InsnNode(InsnType.AGET, 2); InsnNode inode = new InsnNode(InsnType.AGET, 2);
inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, argType)); inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, resType));
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType))); inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(arrElemType)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL)); inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
return inode; return inode;
} }
private InsnNode arrayPut(InsnData insn, ArgType argType) { private InsnNode arrayPut(InsnData insn, ArgType argType) {
return arrayPut(insn, argType, argType);
}
private InsnNode arrayPut(InsnData insn, ArgType arrElemType, ArgType argType) {
InsnNode inode = new InsnNode(InsnType.APUT, 3); InsnNode inode = new InsnNode(InsnType.APUT, 3);
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType))); inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(arrElemType)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL)); inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 0, argType)); inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
return inode; return inode;
@@ -192,19 +192,13 @@ public class SSAVar {
return usedInPhi; return usedInPhi;
} }
public boolean isUsedInPhi() { public boolean isAssignInPhi() {
return usedInPhi != null && !usedInPhi.isEmpty(); InsnNode assignInsn = getAssignInsn();
return assignInsn != null && assignInsn.getType() == InsnType.PHI;
} }
public int getVariableUseCount() { public boolean isUsedInPhi() {
int count = useList.size(); return usedInPhi != null && !usedInPhi.isEmpty();
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) {
@@ -16,9 +16,11 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.DecompilationMode;
import jadx.api.ICodeCache; import jadx.api.ICodeCache;
import jadx.api.ICodeInfo; import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter; import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.plugins.input.data.IClassData; import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.IFieldData; import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodData; import jadx.api.plugins.input.data.IMethodData;
@@ -33,6 +35,8 @@ 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.InlinedAttr;
import jadx.core.dex.attributes.nodes.NotificationAttrNode; import jadx.core.dex.attributes.nodes.NotificationAttrNode;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType; import jadx.core.dex.info.AccessInfo.AFType;
@@ -42,6 +46,7 @@ import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.nodes.utils.TypeUtils; import jadx.core.dex.nodes.utils.TypeUtils;
import jadx.core.utils.ListUtils;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -302,6 +307,26 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return decompile(true); return decompile(true);
} }
/**
* WARNING: Slow operation! Use with caution!
*/
public ICodeInfo decompileWithMode(DecompilationMode mode) {
DecompilationMode baseMode = root.getArgs().getDecompilationMode();
if (mode == baseMode) {
return decompile(true);
}
JadxArgs args = root.getArgs();
try {
unload();
args.setDecompilationMode(mode);
ProcessClass process = new ProcessClass(args);
process.initPasses(root);
return process.generateCode(this);
} finally {
args.setDecompilationMode(baseMode);
}
}
public ICodeInfo getCode() { public ICodeInfo getCode() {
return decompile(true); return decompile(true);
} }
@@ -353,11 +378,22 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return code; return code;
} }
} }
ICodeInfo codeInfo = ProcessClass.generateCode(this); ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
codeCache.add(clsRawName, codeInfo); codeCache.add(clsRawName, codeInfo);
return codeInfo; return codeInfo;
} }
@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()) {
@@ -598,6 +634,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
if (inlinedClasses.isEmpty()) { if (inlinedClasses.isEmpty()) {
inlinedClasses = new ArrayList<>(5); inlinedClasses = new ArrayList<>(5);
} }
cls.addAttr(new InlinedAttr(this));
inlinedClasses.add(cls); inlinedClasses.add(cls);
} }
@@ -608,7 +645,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
} }
public boolean isAnonymous() { public boolean isAnonymous() {
return contains(AFlag.ANONYMOUS_CLASS); return contains(AType.ANONYMOUS_CLASS);
} }
public boolean isInner() { public boolean isInner() {
@@ -739,6 +776,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
this.dependencies = dependencies; this.dependencies = dependencies;
} }
public void removeDependency(ClassNode dep) {
this.dependencies = ListUtils.safeRemoveAndTrim(this.dependencies, dep);
}
public List<ClassNode> getCodegenDeps() { public List<ClassNode> getCodegenDeps() {
return codegenDeps; return codegenDeps;
} }
@@ -747,6 +788,14 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
this.codegenDeps = codegenDeps; this.codegenDeps = codegenDeps;
} }
public void addCodegenDep(ClassNode dep) {
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
}
public int getTotalDepsCount() {
return dependencies.size() + codegenDeps.size();
}
public List<ClassNode> getUseIn() { public List<ClassNode> getUseIn() {
return useIn; return useIn;
} }
@@ -9,6 +9,7 @@ import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType; import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.ListUtils;
public class FieldNode extends NotificationAttrNode implements ICodeNode { public class FieldNode extends NotificationAttrNode implements ICodeNode {
@@ -80,6 +81,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
this.useIn = useIn; this.useIn = useIn;
} }
public synchronized void addUseIn(MethodNode mth) {
useIn = ListUtils.safeAdd(useIn, mth);
}
@Override @Override
public String typeName() { public String typeName() {
return "field"; return "field";
@@ -300,20 +300,6 @@ public class InsnNode extends LineAttrNode {
} }
} }
/**
* Visit all args recursively (including inner instructions),
* but excluding wrapped args
*/
public void visitArgs(Consumer<InsnArg> visitor) {
for (InsnArg arg : getArguments()) {
if (arg.isInsnWrap()) {
((InsnWrapArg) arg).getWrapInsn().visitArgs(visitor);
} else {
visitor.accept(arg);
}
}
}
/** /**
* Visit this instruction and all inner (wrapped) instructions * Visit this instruction and all inner (wrapped) instructions
* To terminate visiting return non-null value * To terminate visiting return non-null value
@@ -336,6 +322,40 @@ public class InsnNode extends LineAttrNode {
return null; return null;
} }
/**
* Visit all args recursively (including inner instructions), but excluding wrapped args
*/
public void visitArgs(Consumer<InsnArg> visitor) {
for (InsnArg arg : getArguments()) {
if (arg.isInsnWrap()) {
((InsnWrapArg) arg).getWrapInsn().visitArgs(visitor);
} else {
visitor.accept(arg);
}
}
}
/**
* Visit all args recursively (including inner instructions), but excluding wrapped args.
* To terminate visiting return non-null value
*/
@Nullable
public <R> R visitArgs(Function<InsnArg, R> visitor) {
for (InsnArg arg : getArguments()) {
R result;
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
result = wrapInsn.visitArgs(visitor);
} else {
result = visitor.apply(arg);
}
if (result != null) {
return result;
}
}
return null;
}
/** /**
* 'Soft' equals, don't compare arguments, only instruction specific parameters. * 'Soft' equals, don't compare arguments, only instruction specific parameters.
*/ */
@@ -99,9 +99,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
@Override @Override
public void unload() { public void unload() {
loaded = false; loaded = false;
if (noCode) {
return;
}
// don't unload retType, argTypes, typeParameters // don't unload retType, argTypes, typeParameters
thisArg = null; thisArg = null;
argsList = null; argsList = null;
@@ -339,6 +336,14 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return exitBlock.getPredecessors(); return exitBlock.getPredecessors();
} }
public boolean isPreExitBlocks(BlockNode block) {
List<BlockNode> successors = block.getSuccessors();
if (successors.size() == 1) {
return successors.get(0).equals(exitBlock);
}
return exitBlock.getPredecessors().contains(block);
}
public void registerLoop(LoopInfo loop) { public void registerLoop(LoopInfo loop) {
if (loops.isEmpty()) { if (loops.isEmpty()) {
loops = new ArrayList<>(5); loops = new ArrayList<>(5);
@@ -22,6 +22,7 @@ import jadx.api.data.ICodeData;
import jadx.api.plugins.input.data.IClassData; import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.ILoadResult; import jadx.api.plugins.input.data.ILoadResult;
import jadx.core.Jadx; import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.clsp.ClspGraph; import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage; import jadx.core.dex.info.ConstStorage;
@@ -51,9 +52,9 @@ public class RootNode {
private final JadxArgs args; private final JadxArgs args;
private final List<IDexTreeVisitor> preDecompilePasses; private final List<IDexTreeVisitor> preDecompilePasses;
private final List<IDexTreeVisitor> passes;
private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>(); private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>();
private final ProcessClass processClasses;
private final ErrorsCounter errorsCounter = new ErrorsCounter(); private final ErrorsCounter errorsCounter = new ErrorsCounter();
private final StringUtils stringUtils; private final StringUtils stringUtils;
private final ConstStorage constValues; private final ConstStorage constValues;
@@ -76,7 +77,7 @@ public class RootNode {
public RootNode(JadxArgs args) { public RootNode(JadxArgs args) {
this.args = args; this.args = args;
this.preDecompilePasses = Jadx.getPreDecompilePassesList(); this.preDecompilePasses = Jadx.getPreDecompilePassesList();
this.passes = Jadx.getPassesList(args); this.processClasses = new ProcessClass(this.getArgs());
this.stringUtils = new StringUtils(args); this.stringUtils = new StringUtils(args);
this.constValues = new ConstStorage(args); this.constValues = new ConstStorage(args);
this.typeUpdate = new TypeUpdate(this); this.typeUpdate = new TypeUpdate(this);
@@ -378,15 +379,6 @@ public class RootNode {
@Nullable @Nullable
public MethodNode resolveMethod(@NotNull MethodInfo mth) { public MethodNode resolveMethod(@NotNull MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass());
if (cls != null) {
return cls.searchMethod(mth);
}
return null;
}
@Nullable
public MethodNode deepResolveMethod(@NotNull MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass()); ClassNode cls = resolveClass(mth.getDeclClass());
if (cls == null) { if (cls == null) {
return null; return null;
@@ -430,19 +422,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);
} }
@@ -474,18 +461,16 @@ public class RootNode {
return null; return null;
} }
public ProcessClass getProcessClasses() {
return processClasses;
}
public List<IDexTreeVisitor> getPasses() { public List<IDexTreeVisitor> getPasses() {
return passes; return processClasses.getPasses();
} }
public void initPasses() { public void initPasses() {
for (IDexTreeVisitor pass : passes) { processClasses.initPasses(this);
try {
pass.init(this);
} catch (Exception e) {
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
}
}
} }
public ICodeWriter makeCodeWriter() { public ICodeWriter makeCodeWriter() {
@@ -2,7 +2,6 @@ package jadx.core.dex.nodes.parser;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -197,6 +196,8 @@ public class SignatureParser {
String obj = slice(); String obj = slice();
if (!innerType) { if (!innerType) {
obj += ';'; obj += ';';
} else {
obj = obj.replace('/', '.');
} }
List<ArgType> typeVars = consumeGenericArgs(); List<ArgType> typeVars = consumeGenericArgs();
consume('>'); consume('>');
@@ -227,7 +228,7 @@ public class SignatureParser {
} }
private List<ArgType> consumeGenericArgs() { private List<ArgType> consumeGenericArgs() {
List<ArgType> list = new LinkedList<>(); List<ArgType> list = new ArrayList<>();
ArgType type; ArgType type;
do { do {
if (lookAhead('*')) { if (lookAhead('*')) {
@@ -38,7 +38,7 @@ public class MethodUtils {
@Nullable @Nullable
public IMethodDetails getMethodDetails(MethodInfo callMth) { public IMethodDetails getMethodDetails(MethodInfo callMth) {
MethodNode mthNode = root.deepResolveMethod(callMth); MethodNode mthNode = root.resolveMethod(callMth);
if (mthNode != null) { if (mthNode != null) {
return mthNode; return mthNode;
} }
@@ -63,7 +63,7 @@ public class TypeUtils {
public ArgType expandTypeVariables(ClassNode cls, ArgType type) { public ArgType expandTypeVariables(ClassNode cls, ArgType type) {
if (type.containsTypeVariable()) { if (type.containsTypeVariable()) {
expandTypeVar(cls, type, cls.getGenericTypeParameters()); expandTypeVar(cls, type, getKnownTypeVarsAtClass(cls));
} }
return type; return type;
} }
@@ -115,11 +115,18 @@ public class TypeUtils {
return varsAttr.getTypeVars(); return varsAttr.getTypeVars();
} }
private static Set<ArgType> collectKnownTypeVarsAtMethod(MethodNode mth) { private static Collection<ArgType> getKnownTypeVarsAtClass(ClassNode cls) {
ClassNode declCls = mth.getParentClass(); if (cls.isInner()) {
Set<ArgType> typeVars = new HashSet<>(declCls.getGenericTypeParameters()); Set<ArgType> typeVars = new HashSet<>(cls.getGenericTypeParameters());
declCls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters())); cls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters()));
return typeVars;
}
return cls.getGenericTypeParameters();
}
private static Set<ArgType> collectKnownTypeVarsAtMethod(MethodNode mth) {
Set<ArgType> typeVars = new HashSet<>();
typeVars.addAll(getKnownTypeVarsAtClass(mth.getParentClass()));
typeVars.addAll(mth.getTypeParameters()); typeVars.addAll(mth.getTypeParameters());
return typeVars.isEmpty() ? Collections.emptySet() : typeVars; return typeVars.isEmpty() ? Collections.emptySet() : typeVars;
} }
@@ -4,7 +4,6 @@ import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IfOp; import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
public final class Compare { public final class Compare {
private final IfNode insn; private final IfNode insn;
@@ -35,13 +34,8 @@ public final class Compare {
return this; return this;
} }
/**
* Change 'a != false' to 'a == true'
*/
public void normalize() { public void normalize() {
if (getOp() == IfOp.NE && getB().isFalse()) { insn.normalize();
insn.changeCondition(IfOp.EQ, getA(), LiteralArg.litTrue());
}
} }
@Override @Override
@@ -1,5 +1,6 @@
package jadx.core.dex.trycatch; package jadx.core.dex.trycatch;
import java.util.Comparator;
import java.util.List; import java.util.List;
import jadx.api.plugins.input.data.attributes.IJadxAttribute; import jadx.api.plugins.input.data.attributes.IJadxAttribute;
@@ -8,9 +9,14 @@ import jadx.core.utils.Utils;
public class CatchAttr implements IJadxAttribute { public class CatchAttr implements IJadxAttribute {
public static CatchAttr build(List<ExceptionHandler> handlers) {
handlers.sort(Comparator.comparingInt(ExceptionHandler::getHandlerOffset));
return new CatchAttr(handlers);
}
private final List<ExceptionHandler> handlers; private final List<ExceptionHandler> handlers;
public CatchAttr(List<ExceptionHandler> handlers) { private CatchAttr(List<ExceptionHandler> handlers) {
this.handlers = handlers; this.handlers = handlers;
} }
@@ -7,6 +7,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
@@ -36,7 +37,7 @@ public class AnonymousClassVisitor extends AbstractVisitor {
@Override @Override
public boolean visit(ClassNode cls) throws JadxException { public boolean visit(ClassNode cls) throws JadxException {
if (cls.contains(AFlag.ANONYMOUS_CLASS)) { if (cls.contains(AType.ANONYMOUS_CLASS)) {
for (MethodNode mth : cls.getMethods()) { for (MethodNode mth : cls.getMethods()) {
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) { if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
processAnonymousConstructor(mth); processAnonymousConstructor(mth);
@@ -1,6 +1,7 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -15,12 +16,16 @@ import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.visitors.typeinference.TypeCompare;
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset; import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset;
@@ -51,11 +56,11 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
tries.forEach(tryData -> LOG.debug(" - {}", tryData)); tries.forEach(tryData -> LOG.debug(" - {}", tryData));
} }
for (ITry tryData : tries) { for (ITry tryData : tries) {
List<ExceptionHandler> handlers = attachHandlers(mth, tryData.getCatch(), insnByOffset); List<ExceptionHandler> handlers = convertToHandlers(mth, tryData.getCatch(), insnByOffset);
if (handlers.isEmpty()) { if (handlers.isEmpty()) {
continue; continue;
} }
markTryBounds(insnByOffset, tryData, new CatchAttr(handlers)); markTryBounds(insnByOffset, tryData, CatchAttr.build(handlers));
} }
} }
@@ -96,13 +101,13 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
if (existAttr != null) { if (existAttr != null) {
// merge handlers // merge handlers
List<ExceptionHandler> handlers = Utils.concat(existAttr.getHandlers(), catchAttr.getHandlers()); List<ExceptionHandler> handlers = Utils.concat(existAttr.getHandlers(), catchAttr.getHandlers());
insn.addAttr(new CatchAttr(handlers)); insn.addAttr(CatchAttr.build(handlers));
} else { } else {
insn.addAttr(catchAttr); insn.addAttr(catchAttr);
} }
} }
private static List<ExceptionHandler> attachHandlers(MethodNode mth, ICatch catchBlock, InsnNode[] insnByOffset) { private static List<ExceptionHandler> convertToHandlers(MethodNode mth, ICatch catchBlock, InsnNode[] insnByOffset) {
int[] handlerOffsetArr = catchBlock.getHandlers(); int[] handlerOffsetArr = catchBlock.getHandlers();
String[] handlerTypes = catchBlock.getTypes(); String[] handlerTypes = catchBlock.getTypes();
@@ -117,6 +122,7 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
if (allHandlerOffset >= 0) { if (allHandlerOffset >= 0) {
Utils.addToList(list, createHandler(mth, insnByOffset, allHandlerOffset, null)); Utils.addToList(list, createHandler(mth, insnByOffset, allHandlerOffset, null));
} }
checkAndFilterHandlers(mth, list);
return list; return list;
} }
@@ -143,6 +149,45 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
return handler; return handler;
} }
private static void checkAndFilterHandlers(MethodNode mth, List<ExceptionHandler> list) {
if (list.size() <= 1) {
return;
}
// Remove shadowed handlers (with same or narrow type compared to previous)
TypeCompare typeCompare = mth.root().getTypeCompare();
Iterator<ExceptionHandler> it = list.iterator();
ArgType maxType = null;
while (it.hasNext()) {
ExceptionHandler handler = it.next();
ArgType maxCatch = maxCatchFromHandler(handler, typeCompare);
if (maxType == null) {
maxType = maxCatch;
} else {
TypeCompareEnum result = typeCompare.compareObjects(maxType, maxCatch);
if (result.isWiderOrEqual()) {
if (Consts.DEBUG_EXC_HANDLERS) {
LOG.debug("Removed shadowed catch handler: {}, from list: {}", handler, list);
}
it.remove();
}
}
}
}
private static ArgType maxCatchFromHandler(ExceptionHandler handler, TypeCompare typeCompare) {
List<ClassInfo> catchTypes = handler.getCatchTypes();
if (catchTypes.isEmpty()) {
return ArgType.THROWABLE;
}
if (catchTypes.size() == 1) {
return catchTypes.get(0).getType();
}
return catchTypes.stream()
.map(ClassInfo::getType)
.max(typeCompare.getComparator())
.orElseThrow(() -> new JadxRuntimeException("Failed to get max type from catch list: " + catchTypes));
}
private static InsnNode insertNOP(InsnNode[] insnByOffset, int offset) { private static InsnNode insertNOP(InsnNode[] insnByOffset, int offset) {
InsnNode nop = new InsnNode(InsnType.NOP, 0); InsnNode nop = new InsnNode(InsnType.NOP, 0);
nop.setOffset(offset); nop.setOffset(offset);
@@ -11,6 +11,7 @@ import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
@@ -31,6 +32,7 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnRemover; import jadx.core.utils.InsnRemover;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
@@ -155,7 +157,7 @@ public class ClassModifier extends AbstractVisitor {
return; return;
} }
// remove synthetic constructor for inner classes // remove synthetic constructor for inner classes
if (af.isConstructor()) { if (mth.isConstructor() && mth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE)) {
InsnNode insn = BlockUtils.getOnlyOneInsnFromMth(mth); InsnNode insn = BlockUtils.getOnlyOneInsnFromMth(mth);
if (insn != null) { if (insn != null) {
List<RegisterArg> args = mth.getArgRegs(); List<RegisterArg> args = mth.getArgRegs();
@@ -210,7 +212,14 @@ public class ClassModifier extends AbstractVisitor {
SkipMethodArgsAttr.skipArg(mth, i); SkipMethodArgsAttr.skipArg(mth, i);
} }
} }
mth.add(AFlag.DONT_GENERATE); MethodInfo callMth = constr.getCallMth();
MethodNode callMthNode = cls.root().resolveMethod(callMth);
if (callMthNode != null) {
mth.addAttr(new MethodReplaceAttr(callMthNode));
mth.add(AFlag.DONT_GENERATE);
// code generation order should be already fixed for marked methods
UsageInfoVisitor.replaceMethodUsage(callMthNode, mth);
}
} }
} }
} }
@@ -244,7 +253,7 @@ public class ClassModifier extends AbstractVisitor {
return false; return false;
} }
MethodInfo callMth = invokeInsn.getCallMth(); MethodInfo callMth = invokeInsn.getCallMth();
MethodNode wrappedMth = mth.root().deepResolveMethod(callMth); MethodNode wrappedMth = mth.root().resolveMethod(callMth);
if (wrappedMth == null) { if (wrappedMth == null) {
return false; return false;
} }
@@ -65,6 +65,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
SSAVar sVar = insn.getResult().getSVar(); SSAVar sVar = insn.getResult().getSVar();
InsnArg constArg; InsnArg constArg;
Runnable onSuccess = null;
InsnType insnType = insn.getType(); InsnType insnType = insn.getType();
if (insnType == InsnType.CONST || insnType == InsnType.MOVE) { if (insnType == InsnType.CONST || insnType == InsnType.MOVE) {
@@ -90,6 +91,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
constArg = InsnArg.wrapArg(constGet); constArg = InsnArg.wrapArg(constGet);
constArg.setType(ArgType.STRING); constArg.setType(ArgType.STRING);
onSuccess = () -> f.addUseIn(mth);
} }
} else if (insnType == InsnType.CONST_CLASS) { } else if (insnType == InsnType.CONST_CLASS) {
if (sVar.isUsedInPhi()) { if (sVar.isUsedInPhi()) {
@@ -104,6 +106,9 @@ public class ConstInlineVisitor extends AbstractVisitor {
// all check passed, run replace // all check passed, run replace
if (replaceConst(mth, insn, constArg)) { if (replaceConst(mth, insn, constArg)) {
toRemove.add(insn); toRemove.add(insn);
if (onSuccess != null) {
onSuccess.run();
}
} }
} }
@@ -235,7 +240,10 @@ public class ConstInlineVisitor extends AbstractVisitor {
fieldNode = mth.getParentClass().getConstField((int) literal, false); fieldNode = mth.getParentClass().getConstField((int) literal, false);
} }
if (fieldNode != null) { if (fieldNode != null) {
litArg.wrapInstruction(mth, new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0)); IndexInsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0);
if (litArg.wrapInstruction(mth, sgetInsn) != null) {
fieldNode.addUseIn(mth);
}
} else { } else {
if (needExplicitCast(useInsn, litArg)) { if (needExplicitCast(useInsn, litArg)) {
litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE); litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
@@ -15,6 +15,7 @@ import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.codegen.TypeGen; import jadx.core.codegen.TypeGen;
import jadx.core.deobf.NameMapper; import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField; import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
@@ -71,7 +72,14 @@ public class EnumVisitor extends AbstractVisitor {
@Override @Override
public boolean visit(ClassNode cls) throws JadxException { public boolean visit(ClassNode cls) throws JadxException {
if (!convertToEnum(cls)) { boolean converted;
try {
converted = convertToEnum(cls);
} catch (Exception e) {
cls.addWarnComment("Enum visitor error", e);
converted = false;
}
if (!converted) {
AccessInfo accessFlags = cls.getAccessFlags(); AccessInfo accessFlags = cls.getAccessFlags();
if (accessFlags.isEnum()) { if (accessFlags.isEnum()) {
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM)); cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
@@ -179,8 +187,7 @@ public class EnumVisitor extends AbstractVisitor {
if (!enumClsInfo.equals(cls.getClassInfo())) { if (!enumClsInfo.equals(cls.getClassInfo())) {
ClassNode enumCls = cls.root().resolveClass(enumClsInfo); ClassNode enumCls = cls.root().resolveClass(enumClsInfo);
if (enumCls != null) { if (enumCls != null) {
processEnumCls(enumField, enumCls); processEnumCls(cls, enumField, enumCls);
cls.addInlinedClass(enumCls);
} }
} }
List<RegisterArg> regs = new ArrayList<>(); List<RegisterArg> regs = new ArrayList<>();
@@ -381,7 +388,11 @@ public class EnumVisitor extends AbstractVisitor {
if (constrCls == null) { if (constrCls == null) {
return null; return null;
} }
if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum()) { if (constrCls.equals(cls)) {
// allow same class
} else if (constrCls.contains(AType.ANONYMOUS_CLASS)) {
// allow external class already marked as anonymous
} else {
return null; return null;
} }
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth()); MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
@@ -466,7 +477,7 @@ public class EnumVisitor extends AbstractVisitor {
return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null; return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null;
} }
private static void processEnumCls(EnumField field, ClassNode innerCls) { private static void processEnumCls(ClassNode cls, EnumField field, ClassNode innerCls) {
// remove constructor, because it is anonymous class // remove constructor, because it is anonymous class
for (MethodNode innerMth : innerCls.getMethods()) { for (MethodNode innerMth : innerCls.getMethods()) {
if (innerMth.getAccessFlags().isConstructor()) { if (innerMth.getAccessFlags().isConstructor()) {
@@ -474,7 +485,11 @@ public class EnumVisitor extends AbstractVisitor {
} }
} }
field.setCls(innerCls); field.setCls(innerCls);
innerCls.add(AFlag.DONT_GENERATE); if (!innerCls.getParentClass().equals(cls)) {
// not inner
cls.addInlinedClass(innerCls);
innerCls.add(AFlag.DONT_GENERATE);
}
} }
private ConstructorInsn getConstructorInsn(InsnNode insn) { private ConstructorInsn getConstructorInsn(InsnNode insn) {
@@ -26,9 +26,6 @@ public class InitCodeVariables extends AbstractVisitor {
@Override @Override
public void visit(MethodNode mth) throws JadxException { public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode()) {
return;
}
initCodeVars(mth); initCodeVars(mth);
} }
@@ -42,16 +39,24 @@ public class InitCodeVariables extends AbstractVisitor {
private static void initCodeVars(MethodNode mth) { private static void initCodeVars(MethodNode mth) {
RegisterArg thisArg = mth.getThisArg(); RegisterArg thisArg = mth.getThisArg();
if (thisArg != null) { if (thisArg != null) {
initCodeVar(thisArg.getSVar()); initCodeVar(mth, thisArg);
} }
for (RegisterArg mthArg : mth.getArgRegs()) { for (RegisterArg mthArg : mth.getArgRegs()) {
initCodeVar(mthArg.getSVar()); initCodeVar(mth, mthArg);
} }
for (SSAVar ssaVar : mth.getSVars()) { for (SSAVar ssaVar : mth.getSVars()) {
initCodeVar(ssaVar); initCodeVar(ssaVar);
} }
} }
public static void initCodeVar(MethodNode mth, RegisterArg regArg) {
SSAVar ssaVar = regArg.getSVar();
if (ssaVar == null) {
ssaVar = mth.makeNewSVar(regArg);
}
initCodeVar(ssaVar);
}
public static void initCodeVar(SSAVar ssaVar) { public static void initCodeVar(SSAVar ssaVar) {
if (ssaVar.isCodeVarSet()) { if (ssaVar.isCodeVarSet()) {
return; return;
@@ -106,7 +106,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
InsnType insnType = insn.getType(); InsnType insnType = insn.getType();
if (insnType == InsnType.INVOKE) { if (insnType == InsnType.INVOKE) {
InvokeNode invoke = (InvokeNode) insn; InvokeNode invoke = (InvokeNode) insn;
MethodNode callMthNode = mth.root().deepResolveMethod(invoke.getCallMth()); MethodNode callMthNode = mth.root().resolveMethod(invoke.getCallMth());
if (callMthNode != null) { if (callMthNode != null) {
FixAccessModifiers.changeVisibility(callMthNode, newVisFlag); FixAccessModifiers.changeVisibility(callMthNode, newVisFlag);
} }
@@ -0,0 +1,31 @@
package jadx.core.dex.visitors;
import java.util.function.Consumer;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxException;
public class MethodVisitor implements IDexTreeVisitor {
private final Consumer<MethodNode> visitor;
public MethodVisitor(Consumer<MethodNode> visitor) {
this.visitor = visitor;
}
@Override
public void visit(MethodNode mth) throws JadxException {
visitor.accept(mth);
}
@Override
public void init(RootNode root) throws JadxException {
}
@Override
public boolean visit(ClassNode cls) throws JadxException {
return true;
}
}
@@ -114,7 +114,7 @@ public class ModVisitor extends AbstractVisitor {
break; break;
case SWITCH: case SWITCH:
replaceConstKeys(parentClass, (SwitchInsn) insn); replaceConstKeys(mth, parentClass, (SwitchInsn) insn);
break; break;
case NEW_ARRAY: case NEW_ARRAY:
@@ -228,13 +228,14 @@ public class ModVisitor extends AbstractVisitor {
return result == TypeCompareEnum.NARROW; // true if use class is subclass of field class return result == TypeCompareEnum.NARROW; // true if use class is subclass of field class
} }
private static void replaceConstKeys(ClassNode parentClass, SwitchInsn insn) { private static void replaceConstKeys(MethodNode mth, ClassNode parentClass, SwitchInsn insn) {
int[] keys = insn.getKeys(); int[] keys = insn.getKeys();
int len = keys.length; int len = keys.length;
for (int k = 0; k < len; k++) { for (int k = 0; k < len; k++) {
FieldNode f = parentClass.getConstField(keys[k]); FieldNode f = parentClass.getConstField(keys[k]);
if (f != null) { if (f != null) {
insn.modifyKey(k, f); insn.modifyKey(k, f);
f.addUseIn(mth);
} }
} }
} }
@@ -291,6 +292,13 @@ public class ModVisitor extends AbstractVisitor {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private EncodedValue replaceConstValue(ClassNode parentCls, EncodedValue encodedValue) { private EncodedValue replaceConstValue(ClassNode parentCls, EncodedValue encodedValue) {
if (encodedValue.getType() == EncodedType.ENCODED_ANNOTATION) {
IAnnotation annotation = (IAnnotation) encodedValue.getValue();
for (Map.Entry<String, EncodedValue> entry : annotation.getValues().entrySet()) {
entry.setValue(replaceConstValue(parentCls, entry.getValue()));
}
return encodedValue;
}
if (encodedValue.getType() == EncodedType.ENCODED_ARRAY) { if (encodedValue.getType() == EncodedType.ENCODED_ARRAY) {
List<EncodedValue> listVal = (List<EncodedValue>) encodedValue.getValue(); List<EncodedValue> listVal = (List<EncodedValue>) encodedValue.getValue();
if (!listVal.isEmpty()) { if (!listVal.isEmpty()) {
@@ -320,6 +328,7 @@ public class ModVisitor extends AbstractVisitor {
InsnNode inode = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); InsnNode inode = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
inode.setResult(insn.getResult()); inode.setResult(insn.getResult());
replaceInsn(mth, block, i, inode); replaceInsn(mth, block, i, inode);
f.addUseIn(mth);
} }
} }
@@ -332,7 +341,9 @@ public class ModVisitor extends AbstractVisitor {
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg); FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg);
if (f != null) { if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet)); if (arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet))) {
f.addUseIn(mth);
}
} }
} }
} }
@@ -446,7 +457,7 @@ public class ModVisitor extends AbstractVisitor {
} }
SkipMethodArgsAttr attr = callMth.get(AType.SKIP_MTH_ARGS); SkipMethodArgsAttr attr = callMth.get(AType.SKIP_MTH_ARGS);
if (attr != null) { if (attr != null) {
int argsCount = Math.min(callMth.getArgRegs().size(), co.getArgsCount()); int argsCount = Math.min(callMth.getMethodInfo().getArgsCount(), co.getArgsCount());
for (int i = 0; i < argsCount; i++) { for (int i = 0; i < argsCount; i++) {
if (attr.isSkip(i)) { if (attr.isSkip(i)) {
anonymousCallArgMod(co.getArg(i)); anonymousCallArgMod(co.getArg(i));
@@ -516,6 +527,7 @@ public class ModVisitor extends AbstractVisitor {
if (f != null) { if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
filledArr.addArg(InsnArg.wrapArg(fGet)); filledArr.addArg(InsnArg.wrapArg(fGet));
f.addUseIn(mth);
} else { } else {
filledArr.addArg(arg); filledArr.addArg(arg);
} }
@@ -52,6 +52,13 @@ public class MoveInlineVisitor extends AbstractVisitor {
if (resultArg.sameRegAndSVar(moveArg)) { if (resultArg.sameRegAndSVar(moveArg)) {
return true; return true;
} }
if (moveArg.isRegister()) {
RegisterArg moveReg = (RegisterArg) moveArg;
if (moveReg.getSVar().isAssignInPhi()) {
// don't mix already merged variables
return false;
}
}
SSAVar ssaVar = resultArg.getSVar(); SSAVar ssaVar = resultArg.getSVar();
if (ssaVar.isUsedInPhi()) { if (ssaVar.isUsedInPhi()) {
return deleteMove(mth, move); return deleteMove(mth, move);
@@ -5,15 +5,24 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.IFieldRef;
import jadx.api.plugins.input.data.annotations.AnnotationVisibility;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp; import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
@@ -25,6 +34,7 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.instructions.mods.TernaryInsn; import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnContainer; import jadx.core.dex.nodes.InsnContainer;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
@@ -54,6 +64,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
if (cls.root().getArgs().isDebugInfo()) { if (cls.root().getArgs().isDebugInfo()) {
setClassSourceLine(cls); setClassSourceLine(cls);
} }
collectFieldsUsageInAnnotations(cls);
return true; return true;
} }
@@ -73,6 +84,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
checkConstUsage(block); checkConstUsage(block);
} }
moveConstructorInConstructor(mth); moveConstructorInConstructor(mth);
collectFieldsUsageInAnnotations(mth, mth);
} }
private static void removeInstructions(BlockNode block) { private static void removeInstructions(BlockNode block) {
@@ -310,4 +322,61 @@ public class PrepareForCodeGen extends AbstractVisitor {
cls.setSourceLine(minLine - 1); cls.setSourceLine(minLine - 1);
} }
} }
private void collectFieldsUsageInAnnotations(ClassNode cls) {
MethodNode useMth = cls.getDefaultConstructor();
if (useMth == null && !cls.getMethods().isEmpty()) {
useMth = cls.getMethods().get(0);
}
if (useMth == null) {
return;
}
collectFieldsUsageInAnnotations(useMth, cls);
MethodNode finalUseMth = useMth;
cls.getFields().forEach(f -> collectFieldsUsageInAnnotations(finalUseMth, f));
}
private void collectFieldsUsageInAnnotations(MethodNode mth, AttrNode attrNode) {
AnnotationsAttr annotationsList = attrNode.get(JadxAttrType.ANNOTATION_LIST);
if (annotationsList == null) {
return;
}
for (IAnnotation annotation : annotationsList.getAll()) {
if (annotation.getVisibility() == AnnotationVisibility.SYSTEM) {
continue;
}
for (Map.Entry<String, EncodedValue> entry : annotation.getValues().entrySet()) {
checkEncodedValue(mth, entry.getValue());
}
}
}
@SuppressWarnings("unchecked")
private void checkEncodedValue(MethodNode mth, EncodedValue encodedValue) {
switch (encodedValue.getType()) {
case ENCODED_FIELD:
Object fieldData = encodedValue.getValue();
FieldInfo fieldInfo;
if (fieldData instanceof IFieldRef) {
fieldInfo = FieldInfo.fromRef(mth.root(), (IFieldRef) fieldData);
} else {
fieldInfo = (FieldInfo) fieldData;
}
FieldNode fieldNode = mth.root().resolveField(fieldInfo);
if (fieldNode != null) {
fieldNode.addUseIn(mth);
}
break;
case ENCODED_ANNOTATION:
IAnnotation annotation = (IAnnotation) encodedValue.getValue();
annotation.getValues().forEach((k, v) -> checkEncodedValue(mth, v));
break;
case ENCODED_ARRAY:
List<EncodedValue> valueList = (List<EncodedValue>) encodedValue.getValue();
valueList.forEach(v -> checkEncodedValue(mth, v));
break;
}
}
} }
@@ -1,11 +1,18 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.AnonymousClassBaseAttr; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
@@ -25,27 +32,36 @@ import jadx.core.utils.exceptions.JadxException;
) )
public class ProcessAnonymous extends AbstractVisitor { public class ProcessAnonymous extends AbstractVisitor {
private boolean inlineAnonymous; private boolean inlineAnonymousClasses;
@Override @Override
public void init(RootNode root) { public void init(RootNode root) {
inlineAnonymous = root.getArgs().isInlineAnonymousClasses(); inlineAnonymousClasses = root.getArgs().isInlineAnonymousClasses();
if (!inlineAnonymousClasses) {
return;
}
for (ClassNode cls : root.getClasses()) {
markAnonymousClass(cls);
}
mergeAnonymousDeps(root);
} }
@Override @Override
public boolean visit(ClassNode cls) throws JadxException { public boolean visit(ClassNode cls) throws JadxException {
if (!inlineAnonymous) { if (inlineAnonymousClasses && cls.contains(AFlag.CLASS_UNLOADED)) {
return false; // enter only on class reload
visitClassAndInners(cls);
} }
return false;
}
private void visitClassAndInners(ClassNode cls) {
markAnonymousClass(cls); markAnonymousClass(cls);
return true; cls.getInnerClasses().forEach(this::visitClassAndInners);
} }
private static void markAnonymousClass(ClassNode cls) { private static void markAnonymousClass(ClassNode cls) {
boolean synthetic = cls.getAccessFlags().isSynthetic() if (!canBeAnonymous(cls)) {
|| cls.getClassInfo().getShortName().contains("$")
|| Character.isDigit(cls.getClassInfo().getShortName().charAt(0));
if (!synthetic) {
return; return;
} }
MethodNode anonymousConstructor = checkUsage(cls); MethodNode anonymousConstructor = checkUsage(cls);
@@ -56,27 +72,132 @@ public class ProcessAnonymous extends AbstractVisitor {
if (baseType == null) { if (baseType == null) {
return; return;
} }
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
cls.add(AFlag.ANONYMOUS_CLASS); outerCls.addInlinedClass(cls);
cls.addAttr(new AnonymousClassBaseAttr(baseType)); cls.addAttr(new AnonymousClassAttr(outerCls, baseType));
cls.add(AFlag.DONT_GENERATE); cls.add(AFlag.DONT_GENERATE);
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR); anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
// force anonymous class to be processed before outer class, // force anonymous class to be processed before outer class,
// actual usage of outer class will be removed at anonymous class process, // actual usage of outer class will be removed at anonymous class process,
// see ModVisitor.processAnonymousConstructor method // see ModVisitor.processAnonymousConstructor method
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
ClassNode topOuterCls = outerCls.getTopParentClass(); ClassNode topOuterCls = outerCls.getTopParentClass();
ListUtils.safeRemove(cls.getDependencies(), topOuterCls); cls.removeDependency(topOuterCls);
ListUtils.safeRemove(outerCls.getUseIn(), cls); ListUtils.safeRemove(outerCls.getUseIn(), cls);
// move dependency to codegen stage // move dependency to codegen stage
if (cls.isTopClass()) { if (cls.isTopClass()) {
topOuterCls.setDependencies(ListUtils.safeRemoveAndTrim(topOuterCls.getDependencies(), cls)); topOuterCls.removeDependency(cls);
topOuterCls.setCodegenDeps(ListUtils.safeAdd(topOuterCls.getCodegenDeps(), cls)); topOuterCls.addCodegenDep(cls);
} }
} }
private static void undoAnonymousMark(ClassNode cls) {
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
ClassNode outerCls = attr.getOuterCls();
cls.setDependencies(ListUtils.safeAdd(cls.getDependencies(), outerCls.getTopParentClass()));
outerCls.setUseIn(ListUtils.safeAdd(outerCls.getUseIn(), cls));
cls.remove(AType.ANONYMOUS_CLASS);
cls.remove(AFlag.DONT_GENERATE);
for (MethodNode mth : cls.getMethods()) {
if (mth.isConstructor()) {
mth.remove(AFlag.ANONYMOUS_CONSTRUCTOR);
}
}
cls.addDebugComment("Anonymous mark cleared");
}
private void mergeAnonymousDeps(RootNode root) {
// Collect edges to build bidirectional tree:
// inline edge: anonymous -> outer (one-to-one)
// use edges: outer -> *anonymous (one-to-many)
Map<ClassNode, ClassNode> inlineMap = new HashMap<>();
Map<ClassNode, List<ClassNode>> useMap = new HashMap<>();
for (ClassNode anonymousCls : root.getClasses()) {
AnonymousClassAttr attr = anonymousCls.get(AType.ANONYMOUS_CLASS);
if (attr != null) {
ClassNode outerCls = attr.getOuterCls();
List<ClassNode> list = useMap.get(outerCls);
if (list == null || list.isEmpty()) {
list = new ArrayList<>(2);
useMap.put(outerCls, list);
}
list.add(anonymousCls);
useMap.putIfAbsent(anonymousCls, Collections.emptyList()); // put leaf explicitly
inlineMap.put(anonymousCls, outerCls);
}
}
if (inlineMap.isEmpty()) {
return;
}
// starting from leaf process deps in nodes up to root
Set<ClassNode> added = new HashSet<>();
useMap.forEach((key, list) -> {
if (list.isEmpty()) {
added.clear();
updateDeps(key, inlineMap, added);
}
});
for (ClassNode cls : root.getClasses()) {
List<ClassNode> deps = cls.getCodegenDeps();
if (deps.size() > 1) {
// distinct sorted dep, reusing collections to reduce memory allocations :)
added.clear();
added.addAll(deps);
deps.clear();
deps.addAll(added);
Collections.sort(deps);
}
}
}
private void updateDeps(ClassNode leafCls, Map<ClassNode, ClassNode> inlineMap, Set<ClassNode> added) {
ClassNode topNode;
ClassNode current = leafCls;
while (true) {
if (!added.add(current)) {
current.addWarnComment("Loop in anonymous inline: " + current + ", path: " + added);
added.forEach(ProcessAnonymous::undoAnonymousMark);
return;
}
ClassNode next = inlineMap.get(current);
if (next == null) {
topNode = current.getTopParentClass();
break;
}
current = next;
}
if (added.size() <= 2) {
// first level deps already processed
return;
}
List<ClassNode> deps = topNode.getCodegenDeps();
if (deps.isEmpty()) {
deps = new ArrayList<>(added.size());
topNode.setCodegenDeps(deps);
}
for (ClassNode add : added) {
deps.add(add.getTopParentClass());
}
}
private static boolean canBeAnonymous(ClassNode cls) {
if (cls.getAccessFlags().isSynthetic()) {
return true;
}
String shortName = cls.getClassInfo().getShortName();
if (shortName.contains("$") || Character.isDigit(shortName.charAt(0))) {
return true;
}
if (cls.getUseIn().size() == 1 && cls.getUseInMth().size() == 1) {
MethodNode useMth = cls.getUseInMth().get(0);
// allow use in enum class init
return useMth.getMethodInfo().isClassInit() && useMth.getParentClass().isEnum();
}
return false;
}
/** /**
* Checks: * Checks:
* - class have only one constructor which used only once (allow common code for field init) * - class have only one constructor which used only once (allow common code for field init)
@@ -45,16 +45,17 @@ public class ProcessMethodsForInline extends AbstractVisitor {
} }
AccessInfo accessFlags = mth.getAccessFlags(); AccessInfo accessFlags = mth.getAccessFlags();
boolean isSynthetic = accessFlags.isSynthetic() || mth.getName().contains("$"); boolean isSynthetic = accessFlags.isSynthetic() || mth.getName().contains("$");
return isSynthetic && accessFlags.isStatic(); return isSynthetic && (accessFlags.isStatic() || mth.isConstructor());
} }
private static void fixClassDependencies(MethodNode mth) { private static void fixClassDependencies(MethodNode mth) {
ClassNode parentClass = mth.getTopParentClass(); ClassNode parentClass = mth.getTopParentClass();
for (MethodNode useInMth : mth.getUseIn()) { for (MethodNode useInMth : mth.getUseIn()) {
// remove possible cross dependency to force class with inline method to be processed before its // remove possible cross dependency
// usage // to force class with inline method to be processed before its usage
ClassNode useTopCls = useInMth.getTopParentClass(); ClassNode useTopCls = useInMth.getTopParentClass();
parentClass.setDependencies(ListUtils.safeRemoveAndTrim(parentClass.getDependencies(), useTopCls)); parentClass.setDependencies(ListUtils.safeRemoveAndTrim(parentClass.getDependencies(), useTopCls));
useTopCls.addCodegenDep(parentClass);
} }
} }
} }
@@ -1,10 +1,10 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Map;
import java.util.stream.Collectors; import java.util.SortedMap;
import java.util.TreeMap;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -34,7 +34,6 @@ import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.utils.InsnList; import jadx.core.utils.InsnList;
import jadx.core.utils.InsnRemover; import jadx.core.utils.InsnRemover;
import jadx.core.utils.InsnUtils; import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
@JadxVisitor( @JadxVisitor(
@@ -87,7 +86,7 @@ public class ReSugarCode extends AbstractVisitor {
} }
switch (insn.getType()) { switch (insn.getType()) {
case NEW_ARRAY: case NEW_ARRAY:
return processNewArray(mth, (NewArrayNode) insn, instructions, i, remover); return processNewArray(mth, (NewArrayNode) insn, instructions, remover);
case SWITCH: case SWITCH:
return processEnumSwitch(mth, (SwitchInsn) insn); return processEnumSwitch(mth, (SwitchInsn) insn);
@@ -100,8 +99,7 @@ public class ReSugarCode extends AbstractVisitor {
/** /**
* Replace new-array and sequence of array-put to new filled-array instruction. * Replace new-array and sequence of array-put to new filled-array instruction.
*/ */
private static boolean processNewArray(MethodNode mth, NewArrayNode newArrayInsn, private static boolean processNewArray(MethodNode mth, NewArrayNode newArrayInsn, List<InsnNode> instructions, InsnRemover remover) {
List<InsnNode> instructions, int i, InsnRemover remover) {
Object arrayLenConst = InsnUtils.getConstValueByArg(mth.root(), newArrayInsn.getArg(0)); Object arrayLenConst = InsnUtils.getConstValueByArg(mth.root(), newArrayInsn.getArg(0));
if (!(arrayLenConst instanceof LiteralArg)) { if (!(arrayLenConst instanceof LiteralArg)) {
return false; return false;
@@ -110,50 +108,81 @@ public class ReSugarCode extends AbstractVisitor {
if (len == 0) { if (len == 0) {
return false; return false;
} }
ArgType arrType = newArrayInsn.getArrayType();
ArgType elemType = arrType.getArrayElement();
boolean allowMissingKeys = arrType.getArrayDimension() == 1 && elemType.isPrimitive();
int minLen = allowMissingKeys ? len / 2 : len;
RegisterArg arrArg = newArrayInsn.getResult(); RegisterArg arrArg = newArrayInsn.getResult();
List<RegisterArg> useList = arrArg.getSVar().getUseList(); List<RegisterArg> useList = arrArg.getSVar().getUseList();
if (useList.size() < len) { if (useList.size() < minLen) {
return false; return false;
} }
List<InsnNode> arrPuts = useList.stream() // quick check if APUT is used
.map(InsnArg::getParentInsn) boolean foundPut = false;
.filter(Objects::nonNull) for (RegisterArg registerArg : useList) {
.filter(insn -> insn.getType() == InsnType.APUT) InsnNode parentInsn = registerArg.getParentInsn();
.sorted(Comparator.comparingLong(insn -> { if (parentInsn != null && parentInsn.getType() == InsnType.APUT) {
Object constVal = InsnUtils.getConstValueByArg(mth.root(), insn.getArg(1)); foundPut = true;
if (constVal instanceof LiteralArg) { break;
return ((LiteralArg) constVal).getLiteral(); }
} }
return -1; // bad value, put at top to fail fast next check if (!foundPut) {
})) return false;
.collect(Collectors.toList()); }
if (arrPuts.size() != len) { // collect put instructions sorted by array index
SortedMap<Long, InsnNode> arrPuts = new TreeMap<>();
for (RegisterArg registerArg : useList) {
InsnNode parentInsn = registerArg.getParentInsn();
if (parentInsn == null || parentInsn.getType() != InsnType.APUT) {
continue;
}
if (!arrArg.sameRegAndSVar(parentInsn.getArg(0))) {
return false;
}
Object constVal = InsnUtils.getConstValueByArg(mth.root(), parentInsn.getArg(1));
if (!(constVal instanceof LiteralArg)) {
return false;
}
long index = ((LiteralArg) constVal).getLiteral();
if (index >= len) {
return false;
}
if (arrPuts.containsKey(index)) {
// stop on index rewrite
break;
}
arrPuts.put(index, parentInsn);
}
if (arrPuts.size() < minLen) {
return false; return false;
} }
// expect all puts to be in same block // expect all puts to be in same block
if (!new HashSet<>(instructions).containsAll(arrPuts)) { if (!new HashSet<>(instructions).containsAll(arrPuts.values())) {
return false; return false;
} }
for (int j = 0; j < len; j++) {
InsnNode insn = arrPuts.get(j);
if (!checkPutInsn(mth, insn, arrArg, j)) {
return false;
}
}
// checks complete, apply // checks complete, apply
ArgType arrType = newArrayInsn.getArrayType(); InsnNode filledArr = new FilledNewArrayNode(elemType, len);
InsnNode filledArr = new FilledNewArrayNode(arrType.getArrayElement(), len);
filledArr.setResult(arrArg.duplicate()); filledArr.setResult(arrArg.duplicate());
for (InsnNode put : arrPuts) { long prevIndex = -1;
for (Map.Entry<Long, InsnNode> entry : arrPuts.entrySet()) {
long index = entry.getKey();
if (index != prevIndex) {
// use zero for missing keys
for (long i = prevIndex + 1; i < index; i++) {
filledArr.addArg(InsnArg.lit(0, elemType));
}
}
InsnNode put = entry.getValue();
filledArr.addArg(replaceConstInArg(mth, put.getArg(2))); filledArr.addArg(replaceConstInArg(mth, put.getArg(2)));
remover.addAndUnbind(put); remover.addAndUnbind(put);
prevIndex = index;
} }
remover.addAndUnbind(newArrayInsn); remover.addAndUnbind(newArrayInsn);
InsnNode lastPut = Utils.last(arrPuts); InsnNode lastPut = arrPuts.get(arrPuts.lastKey());
int replaceIndex = InsnList.getIndex(instructions, lastPut); int replaceIndex = InsnList.getIndex(instructions, lastPut);
instructions.set(replaceIndex, filledArr); instructions.set(replaceIndex, filledArr);
return true; return true;
@@ -164,28 +193,14 @@ public class ReSugarCode extends AbstractVisitor {
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg((LiteralArg) valueArg); FieldNode f = mth.getParentClass().getConstFieldByLiteralArg((LiteralArg) valueArg);
if (f != null) { if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
return InsnArg.wrapArg(fGet); InsnArg arg = InsnArg.wrapArg(fGet);
f.addUseIn(mth);
return arg;
} }
} }
return valueArg.duplicate(); return valueArg.duplicate();
} }
private static boolean checkPutInsn(MethodNode mth, InsnNode insn, RegisterArg arrArg, int putIndex) {
if (insn == null || insn.getType() != InsnType.APUT) {
return false;
}
if (!arrArg.sameRegAndSVar(insn.getArg(0))) {
return false;
}
InsnArg indexArg = insn.getArg(1);
Object value = InsnUtils.getConstValueByArg(mth.root(), indexArg);
if (value instanceof LiteralArg) {
int index = (int) ((LiteralArg) value).getLiteral();
return index == putIndex;
}
return false;
}
private static boolean processEnumSwitch(MethodNode mth, SwitchInsn insn) { private static boolean processEnumSwitch(MethodNode mth, SwitchInsn insn) {
InsnArg arg = insn.getArg(0); InsnArg arg = insn.getArg(0);
if (!arg.isInsnWrap()) { if (!arg.isInsnWrap()) {
@@ -11,6 +11,7 @@ import jadx.api.JadxArgs;
import jadx.api.plugins.utils.ZipSecurity; import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
@@ -34,7 +35,10 @@ public class SaveCode {
if (codeStr.isEmpty()) { if (codeStr.isEmpty()) {
return; return;
} }
String fileName = cls.getClassInfo().getAliasFullPath() + getFileExtension(cls); if (cls.root().getArgs().isSkipFilesSave()) {
return;
}
String fileName = cls.getClassInfo().getAliasFullPath() + getFileExtension(cls.root());
save(codeStr, dir, fileName); save(codeStr, dir, fileName);
} }
@@ -58,8 +62,8 @@ public class SaveCode {
} }
} }
private static String getFileExtension(ClassNode cls) { public static String getFileExtension(RootNode root) {
JadxArgs.OutputFormatEnum outputFormat = cls.root().getArgs().getOutputFormat(); JadxArgs.OutputFormatEnum outputFormat = root.getArgs().getOutputFormat();
switch (outputFormat) { switch (outputFormat) {
case JAVA: case JAVA:
return ".java"; return ".java";
@@ -80,11 +80,15 @@ public class SignatureProcessor extends AbstractVisitor {
} }
ClassNode cls = field.getParentClass(); ClassNode cls = field.getParentClass();
try { try {
ArgType gType = sp.consumeType(); ArgType signatureType = sp.consumeType();
if (gType == null) { if (signatureType == null) {
return; return;
} }
ArgType type = root.getTypeUtils().expandTypeVariables(cls, gType); if (!validateInnerType(signatureType)) {
field.addWarnComment("Incorrect inner types in field signature: " + sp.getSignature());
return;
}
ArgType type = root.getTypeUtils().expandTypeVariables(cls, signatureType);
if (!validateParsedType(type, field.getType())) { if (!validateParsedType(type, field.getType())) {
cls.addWarnComment("Incorrect field signature: " + sp.getSignature()); cls.addWarnComment("Incorrect field signature: " + sp.getSignature());
return; return;
@@ -105,6 +109,11 @@ public class SignatureProcessor extends AbstractVisitor {
List<ArgType> parsedArgTypes = sp.consumeMethodArgs(mth.getMethodInfo().getArgsCount()); List<ArgType> parsedArgTypes = sp.consumeMethodArgs(mth.getMethodInfo().getArgsCount());
ArgType parsedRetType = sp.consumeType(); ArgType parsedRetType = sp.consumeType();
if (!validateInnerType(parsedRetType) || !validateInnerType(parsedArgTypes)) {
mth.addWarnComment("Incorrect inner types in method signature: " + sp.getSignature());
return;
}
mth.updateTypeParameters(typeParameters); // apply before expand args mth.updateTypeParameters(typeParameters); // apply before expand args
TypeUtils typeUtils = root.getTypeUtils(); TypeUtils typeUtils = root.getTypeUtils();
ArgType retType = typeUtils.expandTypeVariables(mth, parsedRetType); ArgType retType = typeUtils.expandTypeVariables(mth, parsedRetType);
@@ -172,4 +181,54 @@ public class SignatureProcessor extends AbstractVisitor {
TypeCompareEnum result = root.getTypeCompare().compareTypes(parsedType, currentType); TypeCompareEnum result = root.getTypeCompare().compareTypes(parsedType, currentType);
return result != TypeCompareEnum.CONFLICT; return result != TypeCompareEnum.CONFLICT;
} }
private boolean validateInnerType(List<ArgType> types) {
for (ArgType type : types) {
if (!validateInnerType(type)) {
return false;
}
}
return true;
}
private boolean validateInnerType(ArgType type) {
ArgType innerType = type.getInnerType();
if (innerType == null) {
return true;
}
// check in outer type has inner type as inner class
ArgType outerType = type.getOuterType();
ClassNode outerCls = root.resolveClass(outerType);
if (outerCls == null) {
// can't check class not found
return true;
}
String innerObj;
if (innerType.getOuterType() != null) {
innerObj = innerType.getOuterType().getObject();
// "next" inner type will be processed at end of method
} else {
innerObj = innerType.getObject();
}
if (!innerObj.contains(".")) {
// short reference
for (ClassNode innerClass : outerCls.getInnerClasses()) {
if (innerClass.getShortName().equals(innerObj)) {
return true;
}
}
return false;
}
// full name
ClassNode innerCls = root.resolveClass(innerObj);
if (innerCls == null) {
return false;
}
if (!innerCls.getParentClass().equals(outerCls)) {
// not inner => fixing
outerCls.addInnerClass(innerCls);
innerCls.getClassInfo().convertToInner(outerCls);
}
return validateInnerType(innerType);
}
} }
@@ -126,7 +126,7 @@ public class BlockExceptionHandler {
commonCatchAttr = catchAttr; commonCatchAttr = catchAttr;
continue; continue;
} }
if (commonCatchAttr != catchAttr) { if (!commonCatchAttr.equals(catchAttr)) {
return null; return null;
} }
} }
@@ -149,12 +149,7 @@ public class BlockExceptionHandler {
continue; continue;
} }
firstInsn.remove(AType.EXC_HANDLER); firstInsn.remove(AType.EXC_HANDLER);
TmpEdgeAttr tmpEdgeAttr = block.get(AType.TMP_EDGE); removeTmpConnection(block);
if (tmpEdgeAttr != null) {
// remove temp connection
BlockSplitter.removeConnection(tmpEdgeAttr.getBlock(), block);
block.remove(AType.TMP_EDGE);
}
ExceptionHandler excHandler = excHandlerAttr.getHandler(); ExceptionHandler excHandler = excHandlerAttr.getHandler();
if (block.getPredecessors().isEmpty()) { if (block.getPredecessors().isEmpty()) {
@@ -176,6 +171,19 @@ public class BlockExceptionHandler {
} }
} }
protected static void removeTmpConnections(MethodNode mth) {
mth.getBasicBlocks().forEach(BlockExceptionHandler::removeTmpConnection);
}
private static void removeTmpConnection(BlockNode block) {
TmpEdgeAttr tmpEdgeAttr = block.get(AType.TMP_EDGE);
if (tmpEdgeAttr != null) {
// remove temp connection
BlockSplitter.removeConnection(tmpEdgeAttr.getBlock(), block);
block.remove(AType.TMP_EDGE);
}
}
private static List<TryCatchBlockAttr> prepareTryBlocks(MethodNode mth) { private static List<TryCatchBlockAttr> prepareTryBlocks(MethodNode mth) {
Map<ExceptionHandler, List<BlockNode>> blocksByHandler = new HashMap<>(); Map<ExceptionHandler, List<BlockNode>> blocksByHandler = new HashMap<>();
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
@@ -390,15 +398,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;
@@ -73,7 +77,9 @@ public class BlockProcessor extends AbstractVisitor {
processNestedLoops(mth); processNestedLoops(mth);
updateCleanSuccessors(mth); updateCleanSuccessors(mth);
mth.finishBasicBlocks(); if (!mth.contains(AFlag.DISABLE_BLOCKS_LOCK)) {
mth.finishBasicBlocks();
}
} }
static void updateCleanSuccessors(MethodNode mth) { static void updateCleanSuccessors(MethodNode mth) {
@@ -347,7 +353,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);
} }
@@ -681,7 +688,7 @@ public class BlockProcessor extends AbstractVisitor {
return false; return false;
} }
static void removeMarkedBlocks(MethodNode mth) { public static void removeMarkedBlocks(MethodNode mth) {
mth.getBasicBlocks().removeIf(block -> { mth.getBasicBlocks().removeIf(block -> {
if (block.contains(AFlag.REMOVE)) { if (block.contains(AFlag.REMOVE)) {
if (!block.getPredecessors().isEmpty() || !block.getSuccessors().isEmpty()) { if (!block.getPredecessors().isEmpty() || !block.getSuccessors().isEmpty()) {
@@ -142,7 +142,7 @@ public class BlockSplitter extends AbstractVisitor {
return block; return block;
} }
static void connect(BlockNode from, BlockNode to) { public static void connect(BlockNode from, BlockNode to) {
if (!from.getSuccessors().contains(to)) { if (!from.getSuccessors().contains(to)) {
from.getSuccessors().add(to); from.getSuccessors().add(to);
} }
@@ -151,19 +151,19 @@ public class BlockSplitter extends AbstractVisitor {
} }
} }
static void removeConnection(BlockNode from, BlockNode to) { public static void removeConnection(BlockNode from, BlockNode to) {
from.getSuccessors().remove(to); from.getSuccessors().remove(to);
to.getPredecessors().remove(from); to.getPredecessors().remove(from);
} }
static void removePredecessors(BlockNode block) { public static void removePredecessors(BlockNode block) {
for (BlockNode pred : block.getPredecessors()) { for (BlockNode pred : block.getPredecessors()) {
pred.getSuccessors().remove(block); pred.getSuccessors().remove(block);
} }
block.getPredecessors().clear(); block.getPredecessors().clear();
} }
static void replaceConnection(BlockNode source, BlockNode oldDest, BlockNode newDest) { public static void replaceConnection(BlockNode source, BlockNode oldDest, BlockNode newDest) {
removeConnection(source, oldDest); removeConnection(source, oldDest);
connect(source, newDest); connect(source, newDest);
replaceTarget(source, oldDest, newDest); replaceTarget(source, oldDest, newDest);
@@ -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,13 +3,16 @@ package jadx.core.dex.visitors.debuginfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.OptionalInt;
import java.util.Set; import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.ILocalVar; import jadx.api.plugins.input.data.ILocalVar;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.MethodParametersAttr;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.deobf.NameMapper; import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
@@ -18,6 +21,7 @@ import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr; import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.instructions.PhiInsn; import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.Named; import jadx.core.dex.instructions.args.Named;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
@@ -51,53 +55,32 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
applyDebugInfo(mth); applyDebugInfo(mth);
mth.remove(AType.LOCAL_VARS_DEBUG_INFO); mth.remove(AType.LOCAL_VARS_DEBUG_INFO);
} }
checkTypes(mth); processMethodParametersAttribute(mth);
} catch (Exception e) { } catch (Exception e) {
mth.addWarnComment("Failed to apply debug info", e); mth.addWarnComment("Failed to apply debug info", e);
} }
} }
private static void checkTypes(MethodNode mth) {
if (mth.isNoCode() || mth.getSVars().isEmpty()) {
return;
}
mth.getSVars().forEach(var -> {
ArgType type = var.getTypeInfo().getType();
if (!type.isTypeKnown()) {
mth.addWarnComment("Type inference failed for: " + var.getDetailedVarInfo(mth));
}
});
}
private static void applyDebugInfo(MethodNode mth) { private static void applyDebugInfo(MethodNode mth) {
if (Consts.DEBUG_TYPE_INFERENCE) { if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.info("Apply debug info for method: {}", mth); LOG.info("Apply debug info for method: {}", mth);
} }
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 +88,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 +125,26 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
return -1; return -1;
} }
public static void applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType type, String varName) { public static boolean applyDebugInfo(MethodNode mth, SSAVar ssaVar, RegisterArg arg) {
TypeUpdateResult result = mth.root().getTypeUpdate().applyWithWiderAllow(mth, ssaVar, type); RegDebugInfoAttr debugInfoAttr = arg.get(AType.REG_DEBUG_INFO);
if (debugInfoAttr == null) {
return false;
}
return applyDebugInfo(mth, ssaVar, debugInfoAttr.getRegType(), debugInfoAttr.getName());
}
public static boolean applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType type, String varName) {
TypeUpdateResult result = mth.root().getTypeUpdate().applyWithWiderIgnoreUnknown(mth, ssaVar, type);
if (result == TypeUpdateResult.REJECT) { if (result == TypeUpdateResult.REJECT) {
if (Consts.DEBUG_TYPE_INFERENCE) { if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth); LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth);
} }
} else { return false;
if (NameMapper.isValidAndPrintable(varName)) {
ssaVar.setName(varName);
}
} }
} if (NameMapper.isValidAndPrintable(varName)) {
ssaVar.setName(varName);
private static void addRegDbdInfo(Set<RegDebugInfoAttr> debugInfo, RegisterArg reg) {
RegDebugInfoAttr debugInfoAttr = reg.get(AType.REG_DEBUG_INFO);
if (debugInfoAttr != null) {
debugInfo.add(debugInfoAttr);
} }
return true;
} }
/** /**
@@ -230,4 +213,31 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
} }
}); });
} }
private void processMethodParametersAttribute(MethodNode mth) {
MethodParametersAttr parametersAttr = mth.get(JadxAttrType.METHOD_PARAMETERS);
if (parametersAttr == null) {
return;
}
try {
List<MethodParametersAttr.Info> params = parametersAttr.getList();
if (params.size() != mth.getMethodInfo().getArgsCount()) {
return;
}
int i = 0;
for (RegisterArg mthArg : mth.getArgRegs()) {
MethodParametersAttr.Info paramInfo = params.get(i++);
String name = paramInfo.getName();
if (NameMapper.isValidAndPrintable(name)) {
CodeVar codeVar = mthArg.getSVar().getCodeVar();
codeVar.setName(name);
if (AccessFlags.hasFlag(paramInfo.getAccFlags(), AccessFlags.FINAL)) {
codeVar.setFinal(true);
}
}
}
} catch (Exception e) {
mth.addWarnComment("Failed to process method parameters attribute: " + parametersAttr.getList(), e);
}
}
} }
@@ -89,25 +89,33 @@ public class DebugInfoAttachVisitor extends AbstractVisitor {
} }
for (int i = start; i <= end; i++) { for (int i = start; i <= end; i++) {
InsnNode insn = insnArr[i]; InsnNode insn = insnArr[i];
if (insn != null) { if (insn == null) {
attachDebugInfo(insn.getResult(), debugInfoAttr, regNum); continue;
for (InsnArg arg : insn.getArguments()) {
attachDebugInfo(arg, debugInfoAttr, regNum);
}
} }
int count = 0;
for (InsnArg arg : insn.getArguments()) {
count += attachDebugInfo(arg, debugInfoAttr, regNum);
}
if (count != 0) {
// don't apply same info for result if applied to args
continue;
}
attachDebugInfo(insn.getResult(), debugInfoAttr, regNum);
} }
} }
mth.addAttr(new LocalVarsDebugInfoAttr(localVars)); mth.addAttr(new LocalVarsDebugInfoAttr(localVars));
} }
private void attachDebugInfo(InsnArg arg, RegDebugInfoAttr debugInfoAttr, int regNum) { private int attachDebugInfo(InsnArg arg, RegDebugInfoAttr debugInfoAttr, int regNum) {
if (arg instanceof RegisterArg) { if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg; RegisterArg reg = (RegisterArg) arg;
if (regNum == reg.getRegNum()) { if (regNum == reg.getRegNum()) {
reg.addAttr(debugInfoAttr); reg.addAttr(debugInfoAttr);
return 1;
} }
} }
return 0;
} }
public static ArgType getVarType(MethodNode mth, ILocalVar var) { public static ArgType getVarType(MethodNode mth, ILocalVar var) {
@@ -154,7 +154,11 @@ public class MarkFinallyVisitor extends AbstractVisitor {
// remove 'finally' from 'try' blocks, check all up paths on each exit (connected with finally exit) // remove 'finally' from 'try' blocks, check all up paths on each exit (connected with finally exit)
List<BlockNode> tryBlocks = allHandler.getTryBlock().getBlocks(); List<BlockNode> tryBlocks = allHandler.getTryBlock().getBlocks();
BlockNode bottomFinallyBlock = BlockUtils.followEmptyPath(BlockUtils.getBottomBlock(allHandler.getBlocks())); BlockNode bottomBlock = BlockUtils.getBottomBlock(allHandler.getBlocks());
if (bottomBlock == null) {
return false;
}
BlockNode bottomFinallyBlock = BlockUtils.followEmptyPath(bottomBlock);
BlockNode bottom = BlockUtils.getNextBlock(bottomFinallyBlock); BlockNode bottom = BlockUtils.getNextBlock(bottomFinallyBlock);
if (bottom == null) { if (bottom == null) {
return false; return false;
@@ -382,6 +382,9 @@ public class IfMakerHelper {
} }
if (useCount > 1) { if (useCount > 1) {
forceInlineInsns.add(insn); forceInlineInsns.add(insn);
} else {
// allow only forced assign inline
pass = false;
} }
} }
} }
@@ -16,8 +16,6 @@ import jadx.core.utils.RegionUtils;
import static jadx.core.utils.RegionUtils.insnsCount; import static jadx.core.utils.RegionUtils.insnsCount;
public class IfRegionVisitor extends AbstractVisitor { public class IfRegionVisitor extends AbstractVisitor {
private static final TernaryMod TERNARY_VISITOR = new TernaryMod();
private static final ProcessIfRegionVisitor PROCESS_IF_REGION_VISITOR = new ProcessIfRegionVisitor(); private static final ProcessIfRegionVisitor PROCESS_IF_REGION_VISITOR = new ProcessIfRegionVisitor();
private static final RemoveRedundantElseVisitor REMOVE_REDUNDANT_ELSE_VISITOR = new RemoveRedundantElseVisitor(); private static final RemoveRedundantElseVisitor REMOVE_REDUNDANT_ELSE_VISITOR = new RemoveRedundantElseVisitor();
@@ -30,7 +28,7 @@ public class IfRegionVisitor extends AbstractVisitor {
} }
public static void process(MethodNode mth) { public static void process(MethodNode mth) {
DepthRegionTraversal.traverseIterative(mth, TERNARY_VISITOR); TernaryMod.process(mth);
DepthRegionTraversal.traverse(mth, PROCESS_IF_REGION_VISITOR); DepthRegionTraversal.traverse(mth, PROCESS_IF_REGION_VISITOR);
DepthRegionTraversal.traverseIterative(mth, REMOVE_REDUNDANT_ELSE_VISITOR); DepthRegionTraversal.traverseIterative(mth, REMOVE_REDUNDANT_ELSE_VISITOR);
} }
@@ -25,10 +25,38 @@ import jadx.core.utils.InsnRemover;
/** /**
* Convert 'if' to ternary operation * Convert 'if' to ternary operation
*/ */
public class TernaryMod implements IRegionIterativeVisitor { public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativeVisitor {
private static final TernaryMod INSTANCE = new TernaryMod();
public static void process(MethodNode mth) {
// first: convert all found ternary nodes in one iteration
DepthRegionTraversal.traverse(mth, INSTANCE);
if (mth.contains(AFlag.REQUEST_CODE_SHRINK)) {
CodeShrinkVisitor.shrinkMethod(mth);
}
// second: iterative runs with shrink after each change
DepthRegionTraversal.traverseIterative(mth, INSTANCE);
}
@Override
public boolean enterRegion(MethodNode mth, IRegion region) {
if (processRegion(mth, region)) {
mth.add(AFlag.REQUEST_CODE_SHRINK);
}
return true;
}
@Override @Override
public boolean visitRegion(MethodNode mth, IRegion region) { public boolean visitRegion(MethodNode mth, IRegion region) {
if (processRegion(mth, region)) {
CodeShrinkVisitor.shrinkMethod(mth);
return true;
}
return false;
}
private static boolean processRegion(MethodNode mth, IRegion region) {
if (region instanceof IfRegion) { if (region instanceof IfRegion) {
return makeTernaryInsn(mth, (IfRegion) region); return makeTernaryInsn(mth, (IfRegion) region);
} }
@@ -115,9 +143,6 @@ public class TernaryMod implements IRegionIterativeVisitor {
header.getInstructions().add(ternInsn); header.getInstructions().add(ternInsn);
clearConditionBlocks(conditionBlocks, header); clearConditionBlocks(conditionBlocks, header);
// shrink method again
CodeShrinkVisitor.shrinkMethod(mth);
return true; return true;
} }
@@ -151,8 +176,6 @@ public class TernaryMod implements IRegionIterativeVisitor {
header.add(AFlag.RETURN); header.add(AFlag.RETURN);
clearConditionBlocks(conditionBlocks, header); clearConditionBlocks(conditionBlocks, header);
CodeShrinkVisitor.shrinkMethod(mth);
return true; return true;
} }
return false; return false;
@@ -291,4 +314,7 @@ public class TernaryMod implements IRegionIterativeVisitor {
// shrink method again // shrink method again
CodeShrinkVisitor.shrinkMethod(mth); CodeShrinkVisitor.shrinkMethod(mth);
} }
private TernaryMod() {
}
} }
@@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -25,6 +26,7 @@ import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
public class RenameVisitor extends AbstractVisitor { public class RenameVisitor extends AbstractVisitor {
private static final Pattern ANONYMOUS_CLASS_PATTERN = Pattern.compile("^\\d+$");
@Override @Override
public void init(RootNode root) { public void init(RootNode root) {
@@ -130,11 +132,12 @@ public class RenameVisitor extends AbstractVisitor {
private static String fixClsShortName(JadxArgs args, String clsName) { private static String fixClsShortName(JadxArgs args, String clsName) {
boolean renameValid = args.isRenameValid(); boolean renameValid = args.isRenameValid();
if (renameValid) { if (renameValid) {
char firstChar = clsName.charAt(0); if (ANONYMOUS_CLASS_PATTERN.matcher(clsName).matches()) {
if (Character.isDigit(firstChar)) {
return Consts.ANONYMOUS_CLASS_PREFIX + NameMapper.removeInvalidCharsMiddle(clsName); return Consts.ANONYMOUS_CLASS_PREFIX + NameMapper.removeInvalidCharsMiddle(clsName);
} }
if (firstChar == '$') {
char firstChar = clsName.charAt(0);
if (firstChar == '$' || Character.isDigit(firstChar)) {
return 'C' + NameMapper.removeInvalidCharsMiddle(clsName); return 'C' + NameMapper.removeInvalidCharsMiddle(clsName);
} }
} }
@@ -4,12 +4,14 @@ import java.util.ArrayList;
import java.util.BitSet; import java.util.BitSet;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.Named;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
@@ -40,6 +42,7 @@ public class CodeShrinkVisitor extends AbstractVisitor {
if (mth.isNoCode()) { if (mth.isNoCode()) {
return; return;
} }
mth.remove(AFlag.REQUEST_CODE_SHRINK);
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
shrinkBlock(mth, block); shrinkBlock(mth, block);
simplifyMoveInsns(mth, block); simplifyMoveInsns(mth, block);
@@ -76,7 +79,9 @@ public class CodeShrinkVisitor extends AbstractVisitor {
private static void checkInline(MethodNode mth, BlockNode block, InsnList insnList, private static void checkInline(MethodNode mth, BlockNode block, InsnList insnList,
List<WrapInfo> wrapList, ArgsInfo argsInfo, RegisterArg arg) { List<WrapInfo> wrapList, ArgsInfo argsInfo, RegisterArg arg) {
if (arg.contains(AFlag.DONT_INLINE)) { if (arg.contains(AFlag.DONT_INLINE)
|| arg.getParentInsn() == null
|| arg.getParentInsn().contains(AFlag.DONT_GENERATE)) {
return; return;
} }
SSAVar sVar = arg.getSVar(); SSAVar sVar = arg.getSVar();
@@ -89,21 +94,34 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|| assignInsn.contains(AFlag.WRAPPED)) { || assignInsn.contains(AFlag.WRAPPED)) {
return; return;
} }
// allow inline only one use arg
boolean assignInline = assignInsn.contains(AFlag.FORCE_ASSIGN_INLINE); boolean assignInline = assignInsn.contains(AFlag.FORCE_ASSIGN_INLINE);
if (!assignInline && sVar.getVariableUseCount() != 1) { if (!assignInline && sVar.isUsedInPhi()) {
return; return;
} }
List<RegisterArg> useList = sVar.getUseList(); // allow inline only one use arg
if (!useList.isEmpty()) { int useCount = 0;
RegisterArg useArg = useList.get(0); for (RegisterArg useArg : sVar.getUseList()) {
InsnNode parentInsn = useArg.getParentInsn(); InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn != null && parentInsn.contains(AFlag.DONT_GENERATE)) { if (parentInsn != null && parentInsn.contains(AFlag.DONT_GENERATE)) {
return; continue;
} }
if (!assignInline && useArg.contains(AFlag.DONT_INLINE_CONST)) { if (!assignInline && useArg.contains(AFlag.DONT_INLINE_CONST)) {
return; return;
} }
useCount++;
}
if (!assignInline && useCount != 1) {
return;
}
if (!assignInline && sVar.getName() != null) {
if (searchArgWithName(assignInsn, sVar.getName())) {
// allow inline if name is reused in result
} else if (varWithSameNameExists(mth, sVar)) {
// allow inline if var name is duplicated
} else {
// reject inline of named variable
return;
}
} }
int assignPos = insnList.getIndex(assignInsn); int assignPos = insnList.getIndex(assignInsn);
@@ -127,6 +145,31 @@ public class CodeShrinkVisitor extends AbstractVisitor {
} }
} }
private static boolean varWithSameNameExists(MethodNode mth, SSAVar inlineVar) {
for (SSAVar ssaVar : mth.getSVars()) {
if (ssaVar == inlineVar || ssaVar.getCodeVar() == inlineVar.getCodeVar()) {
continue;
}
if (Objects.equals(ssaVar.getName(), inlineVar.getName())) {
return ssaVar.getUseCount() > inlineVar.getUseCount();
}
}
return false;
}
private static boolean searchArgWithName(InsnNode assignInsn, String varName) {
InsnArg result = assignInsn.visitArgs(insnArg -> {
if (insnArg instanceof Named) {
String argName = ((Named) insnArg).getName();
if (Objects.equals(argName, varName)) {
return insnArg;
}
}
return null;
});
return result != null;
}
private static boolean assignInline(MethodNode mth, RegisterArg arg, InsnNode assignInsn, BlockNode assignBlock) { private static boolean assignInline(MethodNode mth, RegisterArg arg, InsnNode assignInsn, BlockNode assignBlock) {
RegisterArg useArg = arg.getSVar().getUseList().get(0); RegisterArg useArg = arg.getSVar().getUseList().get(0);
InsnNode useInsn = useArg.getParentInsn(); InsnNode useInsn = useArg.getParentInsn();
@@ -140,7 +140,7 @@ public class SSATransform extends AbstractVisitor {
stack.push(initState); stack.push(initState);
while (!stack.isEmpty()) { while (!stack.isEmpty()) {
RenameState state = stack.pop(); RenameState state = stack.pop();
renameVarsInBlock(state); renameVarsInBlock(mth, state);
for (BlockNode dominated : state.getBlock().getDominatesOn()) { for (BlockNode dominated : state.getBlock().getDominatesOn()) {
stack.push(RenameState.copyFrom(state, dominated)); stack.push(RenameState.copyFrom(state, dominated));
} }
@@ -156,7 +156,7 @@ public class SSATransform extends AbstractVisitor {
} }
} }
private static void renameVarsInBlock(RenameState state) { private static void renameVarsInBlock(MethodNode mth, RenameState state) {
BlockNode block = state.getBlock(); BlockNode block = state.getBlock();
for (InsnNode insn : block.getInstructions()) { for (InsnNode insn : block.getInstructions()) {
if (insn.getType() != InsnType.PHI) { if (insn.getType() != InsnType.PHI) {
@@ -168,8 +168,9 @@ public class SSATransform extends AbstractVisitor {
int regNum = reg.getRegNum(); int regNum = reg.getRegNum();
SSAVar var = state.getVar(regNum); SSAVar var = state.getVar(regNum);
if (var == null) { if (var == null) {
throw new JadxRuntimeException("Not initialized variable reg: " + regNum // TODO: in most cases issue in incorrectly attached exception handlers
+ ", insn: " + insn + ", block:" + block); mth.addWarnComment("Not initialized variable reg: " + regNum + ", insn: " + insn + ", block:" + block);
var = state.startVar(reg);
} }
var.use(reg); var.use(reg);
} }
@@ -0,0 +1,33 @@
package jadx.core.dex.visitors.typeinference;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.JadxVisitor;
@JadxVisitor(
name = "Finish Type Inference",
desc = "Check used types",
runAfter = {
TypeInferenceVisitor.class
}
)
public final class FinishTypeInference extends AbstractVisitor {
@Override
public void visit(MethodNode mth) {
if (mth.isNoCode() || mth.getSVars().isEmpty()) {
return;
}
mth.getSVars().forEach(var -> {
ArgType type = var.getTypeInfo().getType();
if (!type.isTypeKnown()) {
mth.addWarnComment("Type inference failed for: " + var.getDetailedVarInfo(mth));
}
ArgType codeVarType = var.getCodeVar().getType();
if (codeVarType == null) {
var.getCodeVar().setType(ArgType.UNKNOWN);
}
});
}
}
@@ -46,6 +46,10 @@ public final class TypeBoundCheckCastAssign implements ITypeBoundDynamic {
return insn.getResult(); return insn.getResult();
} }
public IndexInsnNode getInsn() {
return insn;
}
@Override @Override
public String toString() { public String toString() {
return "CHECK_CAST_ASSIGN{(" + insn.getIndex() + ") " + insn.getArg(0).getType() + "}"; return "CHECK_CAST_ASSIGN{(" + insn.getIndex() + ") " + insn.getArg(0).getType() + "}";
@@ -1,5 +1,7 @@
package jadx.core.dex.visitors.typeinference; package jadx.core.dex.visitors.typeinference;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
@@ -48,12 +50,24 @@ public final class TypeBoundInvokeAssign implements ITypeBoundDynamic {
mthDeclType = instanceType; mthDeclType = instanceType;
} }
ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(instanceType, mthDeclType, genericReturnType); ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(instanceType, mthDeclType, genericReturnType);
if (resultGeneric != null && !resultGeneric.isWildcard()) { ArgType result = processResultType(resultGeneric);
return resultGeneric; if (result != null) {
return result;
} }
return invokeNode.getCallMth().getReturnType(); return invokeNode.getCallMth().getReturnType();
} }
@Nullable
private ArgType processResultType(@Nullable ArgType resultGeneric) {
if (resultGeneric == null) {
return null;
}
if (!resultGeneric.isWildcard()) {
return resultGeneric;
}
return resultGeneric.getWildcardType();
}
private InsnArg getInstanceArg() { private InsnArg getInstanceArg() {
return invokeNode.getArg(0); return invokeNode.getArg(0);
} }
@@ -9,6 +9,7 @@ import java.util.Objects;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.ArgType.WildcardBound; import jadx.core.dex.instructions.args.ArgType.WildcardBound;
import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.instructions.args.PrimitiveType;
@@ -42,6 +43,17 @@ public class TypeCompare {
return compareObjects(first.getType(), second.getType()); return compareObjects(first.getType(), second.getType());
} }
public TypeCompareEnum compareTypes(ClassInfo first, ClassInfo second) {
return compareObjects(first.getType(), second.getType());
}
public TypeCompareEnum compareObjects(ArgType first, ArgType second) {
if (first == second || Objects.equals(first, second)) {
return TypeCompareEnum.EQUAL;
}
return compareObjectsNoPreCheck(first, second);
}
/** /**
* Compare two type and return result for first argument (narrow, wider or conflict) * Compare two type and return result for first argument (narrow, wider or conflict)
*/ */
@@ -81,7 +93,7 @@ public class TypeCompare {
boolean firstObj = first.isObject(); boolean firstObj = first.isObject();
boolean secondObj = second.isObject(); boolean secondObj = second.isObject();
if (firstObj && secondObj) { if (firstObj && secondObj) {
return compareObjects(first, second); return compareObjectsNoPreCheck(first, second);
} else { } else {
// primitive types conflicts with objects // primitive types conflicts with objects
if (firstObj && secondPrimitive) { if (firstObj && secondPrimitive) {
@@ -159,7 +171,7 @@ public class TypeCompare {
return CONFLICT; return CONFLICT;
} }
private TypeCompareEnum compareObjects(ArgType first, ArgType second) { private TypeCompareEnum compareObjectsNoPreCheck(ArgType first, ArgType second) {
boolean objectsEquals = first.getObject().equals(second.getObject()); boolean objectsEquals = first.getObject().equals(second.getObject());
boolean firstGenericType = first.isGenericType(); boolean firstGenericType = first.isGenericType();
boolean secondGenericType = second.isGenericType(); boolean secondGenericType = second.isGenericType();
@@ -262,7 +274,7 @@ public class TypeCompare {
return NARROW; return NARROW;
} }
for (ArgType extendType : extendTypes) { for (ArgType extendType : extendTypes) {
TypeCompareEnum res = compareObjects(extendType, objType); TypeCompareEnum res = compareObjectsNoPreCheck(extendType, objType);
if (!res.isNarrow()) { if (!res.isNarrow()) {
return res; return res;
} }
@@ -39,6 +39,10 @@ public enum TypeCompareEnum {
return this == WIDER || this == WIDER_BY_GENERIC; return this == WIDER || this == WIDER_BY_GENERIC;
} }
public boolean isWiderOrEqual() {
return isEqual() || isWider();
}
public boolean isNarrow() { public boolean isNarrow() {
return this == NARROW || this == NARROW_BY_GENERIC; return this == NARROW || this == NARROW_BY_GENERIC;
} }
@@ -19,7 +19,7 @@ import jadx.core.Consts;
import jadx.core.clsp.ClspGraph; import jadx.core.clsp.ClspGraph;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassBaseAttr; import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr; import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ArithNode;
@@ -57,6 +57,7 @@ import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnList; import jadx.core.utils.InsnList;
import jadx.core.utils.InsnUtils; import jadx.core.utils.InsnUtils;
import jadx.core.utils.ListUtils;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxOverflowException; import jadx.core.utils.exceptions.JadxOverflowException;
@@ -83,10 +84,12 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
this.resolvers = Arrays.asList( this.resolvers = Arrays.asList(
this::initTypeBounds, this::initTypeBounds,
this::runTypePropagation, this::runTypePropagation,
this::tryRestoreTypeVarCasts,
this::tryInsertCasts, this::tryInsertCasts,
this::tryDeduceTypes, this::tryDeduceTypes,
this::trySplitConstInsns, this::trySplitConstInsns,
this::tryToFixIncompatiblePrimitives, this::tryToFixIncompatiblePrimitives,
this::tryToForceImmutableTypes,
this::tryInsertAdditionalMove, this::tryInsertAdditionalMove,
this::runMultiVariableSearch, this::runMultiVariableSearch,
this::tryRemoveGenerics); this::tryRemoveGenerics);
@@ -321,7 +324,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
if (ctr.isNewInstance()) { if (ctr.isNewInstance()) {
ClassNode ctrCls = root.resolveClass(ctr.getClassType()); ClassNode ctrCls = root.resolveClass(ctr.getClassType());
if (ctrCls != null && ctrCls.contains(AFlag.DONT_GENERATE)) { if (ctrCls != null && ctrCls.contains(AFlag.DONT_GENERATE)) {
AnonymousClassBaseAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS_BASE); AnonymousClassAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS);
if (baseTypeAttr != null) { if (baseTypeAttr != null) {
return baseTypeAttr.getBaseType(); return baseTypeAttr.getBaseType();
} }
@@ -519,7 +522,60 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
return false; return false;
} }
@SuppressWarnings("ForLoopReplaceableByWhile") /**
* Fix check casts to type var extend type:
* <br>
* {@code <T extends Comparable> T var = (Comparable) obj; => T var = (T) obj; }
*/
private boolean tryRestoreTypeVarCasts(MethodNode mth) {
int changed = 0;
List<SSAVar> mthSVars = mth.getSVars();
for (SSAVar var : mthSVars) {
changed += restoreTypeVarCasts(var);
}
if (changed == 0) {
return false;
}
if (Consts.DEBUG_TYPE_INFERENCE) {
mth.addDebugComment("Restore " + changed + " type vars casts");
}
initTypeBounds(mth);
return runTypePropagation(mth);
}
private int restoreTypeVarCasts(SSAVar var) {
TypeInfo typeInfo = var.getTypeInfo();
Set<ITypeBound> bounds = typeInfo.getBounds();
if (!ListUtils.anyMatch(bounds, t -> t.getType().isGenericType())) {
return 0;
}
List<ITypeBound> casts = ListUtils.filter(bounds, TypeBoundCheckCastAssign.class::isInstance);
if (casts.isEmpty()) {
return 0;
}
ArgType bestType = selectBestTypeFromBounds(bounds).orElse(ArgType.UNKNOWN);
if (!bestType.isGenericType()) {
return 0;
}
List<ArgType> extendTypes = bestType.getExtendTypes();
if (extendTypes.size() != 1) {
return 0;
}
int fixed = 0;
ArgType extendType = extendTypes.get(0);
for (ITypeBound bound : casts) {
TypeBoundCheckCastAssign cast = (TypeBoundCheckCastAssign) bound;
ArgType castType = cast.getType();
TypeCompareEnum result = typeUpdate.getTypeCompare().compareTypes(extendType, castType);
if (result.isEqual() || result == TypeCompareEnum.NARROW_BY_GENERIC) {
cast.getInsn().updateIndex(bestType);
fixed++;
}
}
return fixed;
}
@SuppressWarnings({ "ForLoopReplaceableByWhile", "ForLoopReplaceableByForEach" })
private boolean tryInsertCasts(MethodNode mth) { private boolean tryInsertCasts(MethodNode mth) {
int added = 0; int added = 0;
List<SSAVar> mthSVars = mth.getSVars(); List<SSAVar> mthSVars = mth.getSVars();
@@ -835,6 +891,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
if (typeInfo.getType().isTypeKnown()) { if (typeInfo.getType().isTypeKnown()) {
return false; return false;
} }
boolean assigned = false;
for (ITypeBound bound : typeInfo.getBounds()) { for (ITypeBound bound : typeInfo.getBounds()) {
ArgType boundType = bound.getType(); ArgType boundType = bound.getType();
switch (bound.getBound()) { switch (bound.getBound()) {
@@ -842,6 +899,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
if (!boundType.contains(PrimitiveType.BOOLEAN)) { if (!boundType.contains(PrimitiveType.BOOLEAN)) {
return false; return false;
} }
assigned = true;
break; break;
case USE: case USE:
if (!boundType.canBeAnyNumber()) { if (!boundType.canBeAnyNumber()) {
@@ -850,6 +908,9 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
break; break;
} }
} }
if (!assigned) {
return false;
}
boolean fixed = false; boolean fixed = false;
for (ITypeBound bound : typeInfo.getBounds()) { for (ITypeBound bound : typeInfo.getBounds()) {
@@ -932,6 +993,36 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
return convertInsn; return convertInsn;
} }
private boolean tryToForceImmutableTypes(MethodNode mth) {
boolean fixed = false;
for (SSAVar ssaVar : mth.getSVars()) {
ArgType type = ssaVar.getTypeInfo().getType();
if (!type.isTypeKnown() && ssaVar.isTypeImmutable()) {
if (forceImmutableType(ssaVar)) {
fixed = true;
}
}
}
if (!fixed) {
return false;
}
return runTypePropagation(mth);
}
private boolean forceImmutableType(SSAVar ssaVar) {
for (RegisterArg useArg : ssaVar.getUseList()) {
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn != null) {
InsnType insnType = parentInsn.getType();
if (insnType == InsnType.AGET || insnType == InsnType.APUT) {
ssaVar.setType(ssaVar.getImmutableType());
return true;
}
}
}
return false;
}
private static void assignImmutableTypes(MethodNode mth) { private static void assignImmutableTypes(MethodNode mth) {
for (SSAVar ssaVar : mth.getSVars()) { for (SSAVar ssaVar : mth.getSVars()) {
ArgType immutableType = getSsaImmutableType(ssaVar); ArgType immutableType = getSsaImmutableType(ssaVar);
@@ -66,7 +66,11 @@ public final class TypeUpdate {
* Force type setting * Force type setting
*/ */
public TypeUpdateResult applyWithWiderIgnSame(MethodNode mth, SSAVar ssaVar, ArgType candidateType) { public TypeUpdateResult applyWithWiderIgnSame(MethodNode mth, SSAVar ssaVar, ArgType candidateType) {
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNSAME); return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNORE_SAME);
}
public TypeUpdateResult applyWithWiderIgnoreUnknown(MethodNode mth, SSAVar ssaVar, ArgType candidateType) {
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNORE_UNKNOWN);
} }
private TypeUpdateResult apply(MethodNode mth, SSAVar ssaVar, ArgType candidateType, TypeUpdateFlags flags) { private TypeUpdateResult apply(MethodNode mth, SSAVar ssaVar, ArgType candidateType, TypeUpdateFlags flags) {
@@ -110,6 +114,9 @@ public final class TypeUpdate {
} }
TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType); TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType);
if (compareResult == TypeCompareEnum.UNKNOWN && updateInfo.getFlags().isIgnoreUnknown()) {
return REJECT;
}
if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) { if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) {
// don't changed type // don't changed type
if (compareResult == TypeCompareEnum.EQUAL) { if (compareResult == TypeCompareEnum.EQUAL) {
@@ -506,7 +513,18 @@ public final class TypeUpdate {
private TypeUpdateResult arrayGetListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { private TypeUpdateResult arrayGetListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
if (isAssign(insn, arg)) { if (isAssign(insn, arg)) {
return updateTypeChecked(updateInfo, insn.getArg(0), ArgType.array(candidateType)); TypeUpdateResult result = updateTypeChecked(updateInfo, insn.getArg(0), ArgType.array(candidateType));
if (result == REJECT) {
ArgType arrType = insn.getArg(0).getType();
if (arrType.isTypeKnown() && arrType.isArray() && arrType.getArrayElement().isPrimitive()) {
TypeCompareEnum compResult = comparator.compareTypes(candidateType, arrType.getArrayElement());
if (compResult == TypeCompareEnum.WIDER) {
// allow implicit upcast for primitive types (int a = byteArr[n])
return CHANGED;
}
}
}
return result;
} }
InsnArg arrArg = insn.getArg(0); InsnArg arrArg = insn.getArg(0);
if (arrArg == arg) { if (arrArg == arg) {
@@ -514,7 +532,18 @@ public final class TypeUpdate {
if (arrayElement == null) { if (arrayElement == null) {
return REJECT; return REJECT;
} }
return updateTypeChecked(updateInfo, insn.getResult(), arrayElement); TypeUpdateResult result = updateTypeChecked(updateInfo, insn.getResult(), arrayElement);
if (result == REJECT) {
ArgType resType = insn.getResult().getType();
if (resType.isTypeKnown() && resType.isPrimitive()) {
TypeCompareEnum compResult = comparator.compareTypes(resType, arrayElement);
if (compResult == TypeCompareEnum.WIDER) {
// allow implicit upcast for primitive types (int a = byteArr[n])
return CHANGED;
}
}
}
return result;
} }
// index argument // index argument
return SAME; return SAME;
@@ -531,10 +560,10 @@ public final class TypeUpdate {
TypeUpdateResult result = updateTypeChecked(updateInfo, putArg, arrayElement); TypeUpdateResult result = updateTypeChecked(updateInfo, putArg, arrayElement);
if (result == REJECT) { if (result == REJECT) {
ArgType putType = putArg.getType(); ArgType putType = putArg.getType();
if (putType.isTypeKnown() && !putType.isPrimitive()) { if (putType.isTypeKnown()) {
TypeCompareEnum compResult = comparator.compareTypes(arrayElement, putType); TypeCompareEnum compResult = comparator.compareTypes(arrayElement, putType);
if (compResult == TypeCompareEnum.WIDER || compResult == TypeCompareEnum.WIDER_BY_GENERIC) { if (compResult == TypeCompareEnum.WIDER || compResult == TypeCompareEnum.WIDER_BY_GENERIC) {
// allow wider result (i.e allow put in Object[] any objects) // allow wider result (i.e. allow put any objects in Object[] or byte in int[])
return CHANGED; return CHANGED;
} }
} }
@@ -1,39 +1,58 @@
package jadx.core.dex.visitors.typeinference; package jadx.core.dex.visitors.typeinference;
import org.jetbrains.annotations.NotNull;
public class TypeUpdateFlags { public class TypeUpdateFlags {
private static final int ALLOW_WIDER = 1;
private static final int IGNORE_SAME = 2;
private static final int IGNORE_UNKNOWN = 4;
public static final TypeUpdateFlags FLAGS_EMPTY = new TypeUpdateFlags(false, false); public static final TypeUpdateFlags FLAGS_EMPTY = build(0);
public static final TypeUpdateFlags FLAGS_WIDER = new TypeUpdateFlags(true, false); public static final TypeUpdateFlags FLAGS_WIDER = build(ALLOW_WIDER);
public static final TypeUpdateFlags FLAGS_WIDER_IGNSAME = new TypeUpdateFlags(true, true); public static final TypeUpdateFlags FLAGS_WIDER_IGNORE_SAME = build(ALLOW_WIDER | IGNORE_SAME);
public static final TypeUpdateFlags FLAGS_WIDER_IGNORE_UNKNOWN = build(ALLOW_WIDER | IGNORE_UNKNOWN);
private final boolean allowWider; private final int flags;
private final boolean ignoreSame;
private TypeUpdateFlags(boolean allowWider, boolean ignoreSame) { @NotNull
this.allowWider = allowWider; private static TypeUpdateFlags build(int flags) {
this.ignoreSame = ignoreSame; return new TypeUpdateFlags(flags);
}
private TypeUpdateFlags(int flags) {
this.flags = flags;
} }
public boolean isAllowWider() { public boolean isAllowWider() {
return allowWider; return (flags & ALLOW_WIDER) != 0;
} }
public boolean isIgnoreSame() { public boolean isIgnoreSame() {
return ignoreSame; return (flags & IGNORE_SAME) != 0;
}
public boolean isIgnoreUnknown() {
return (flags & IGNORE_UNKNOWN) != 0;
} }
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (allowWider) { if (isAllowWider()) {
sb.append("ALLOW_WIDER"); sb.append("ALLOW_WIDER");
} }
if (ignoreSame) { if (isIgnoreSame()) {
if (sb.length() != 0) { if (sb.length() != 0) {
sb.append('|'); sb.append('|');
} }
sb.append("IGNORE_SAME"); sb.append("IGNORE_SAME");
} }
if (isIgnoreUnknown()) {
if (sb.length() != 0) {
sb.append('|');
}
sb.append("IGNORE_UNKNOWN");
}
return sb.toString(); return sb.toString();
} }
} }
@@ -1,5 +1,8 @@
package jadx.core.dex.visitors.usage; package jadx.core.dex.visitors.usage;
import java.util.Collections;
import java.util.List;
import jadx.api.plugins.input.data.ICallSite; import jadx.api.plugins.input.data.ICallSite;
import jadx.api.plugins.input.data.ICodeReader; import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.IMethodHandle; import jadx.api.plugins.input.data.IMethodHandle;
@@ -18,6 +21,7 @@ import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.JadxVisitor; import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.OverrideMethodVisitor; import jadx.core.dex.visitors.OverrideMethodVisitor;
import jadx.core.dex.visitors.rename.RenameVisitor; import jadx.core.dex.visitors.rename.RenameVisitor;
import jadx.core.utils.ListUtils;
import jadx.core.utils.input.InsnDataUtils; import jadx.core.utils.input.InsnDataUtils;
@JadxVisitor( @JadxVisitor(
@@ -134,4 +138,11 @@ public class UsageInfoVisitor extends AbstractVisitor {
} }
} }
} }
public static void replaceMethodUsage(MethodNode mergeIntoMth, MethodNode sourceMth) {
List<MethodNode> mergedUsage = ListUtils.distinctMergeSortedLists(mergeIntoMth.getUseIn(), sourceMth.getUseIn());
mergedUsage.remove(sourceMth);
mergeIntoMth.setUseIn(mergedUsage);
sourceMth.setUseIn(Collections.emptyList());
}
} }
@@ -110,7 +110,8 @@ public class ExportGradleProject {
Integer versionCode = Integer.valueOf(manifest.getAttribute("android:versionCode")); Integer versionCode = Integer.valueOf(manifest.getAttribute("android:versionCode"));
String versionName = manifest.getAttribute("android:versionName"); String versionName = manifest.getAttribute("android:versionName");
Integer minSdk = Integer.valueOf(usesSdk.getAttribute("android:minSdkVersion")); Integer minSdk = Integer.valueOf(usesSdk.getAttribute("android:minSdkVersion"));
Integer targetSdk = Integer.valueOf(usesSdk.getAttribute("android:targetSdkVersion")); String stringTargetSdk = usesSdk.getAttribute("android:targetSdkVersion");
Integer targetSdk = stringTargetSdk.isEmpty() ? minSdk : Integer.valueOf(stringTargetSdk);
String appName = "UNKNOWN"; String appName = "UNKNOWN";
if (application.hasAttribute("android:label")) { if (application.hasAttribute("android:label")) {
@@ -35,6 +35,7 @@ import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.conditions.IfCondition; import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
public class BlockUtils { public class BlockUtils {
@@ -382,6 +383,7 @@ public class BlockUtils {
/** /**
* Return first successor which not exception handler and not follow loop back edge * Return first successor which not exception handler and not follow loop back edge
*/ */
@Nullable
public static BlockNode getNextBlock(BlockNode block) { public static BlockNode getNextBlock(BlockNode block) {
List<BlockNode> s = block.getCleanSuccessors(); List<BlockNode> s = block.getCleanSuccessors();
return s.isEmpty() ? null : s.get(0); return s.isEmpty() ? null : s.get(0);
@@ -451,6 +453,31 @@ public class BlockUtils {
} }
} }
public static void dfsVisit(MethodNode mth, Consumer<BlockNode> visitor) {
BitSet visited = newBlocksBitSet(mth);
Deque<BlockNode> queue = new ArrayDeque<>();
BlockNode enterBlock = mth.getEnterBlock();
queue.addLast(enterBlock);
visited.set(mth.getEnterBlock().getId());
while (true) {
BlockNode current = queue.pollLast();
if (current == null) {
return;
}
visitor.accept(current);
List<BlockNode> successors = current.getSuccessors();
int count = successors.size();
for (int i = count - 1; i >= 0; i--) { // to preserve order in queue
BlockNode next = successors.get(i);
int nextId = next.getId();
if (!visited.get(nextId)) {
queue.addLast(next);
visited.set(nextId);
}
}
}
}
public static List<BlockNode> collectPredecessors(MethodNode mth, BlockNode start, Collection<BlockNode> stopBlocks) { public static List<BlockNode> collectPredecessors(MethodNode mth, BlockNode start, Collection<BlockNode> stopBlocks) {
BitSet bs = newBlocksBitSet(mth); BitSet bs = newBlocksBitSet(mth);
if (!stopBlocks.isEmpty()) { if (!stopBlocks.isEmpty()) {
@@ -594,6 +621,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 +729,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 +737,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 +755,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 +766,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;

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