Compare commits

..

162 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
Skylot d22db30166 fix: use secure xml parser for process manifest 2022-01-20 11:17:12 +00:00
Skylot 6db61e7a59 chore: update dependencies 2022-01-20 10:23:49 +00:00
Skylot 86582de521 feat: use kotlin intrinsic methods for variables rename (#1207) 2022-01-19 17:30:04 +00:00
Skylot a7c63c2eb3 fix: handle method override with several bases (#1234) 2022-01-18 18:27:09 +00:00
Skylot 081a0e21ee fix: precalculate class deps for inline methods (#1339) 2022-01-17 14:38:38 +00:00
Skylot 9ac9c05265 fix: simplify cascading casts (#1336) 2022-01-15 16:31:18 +00:00
Skylot b7daf79b26 fix: add explicit type for non-int constants (#1336) 2022-01-15 14:11:44 +00:00
Skylot b67a3561a4 build: add CodeQL analysis 2022-01-13 22:37:36 +03:00
Skylot 52ac6dbbaf docs: add security.md 2022-01-13 16:45:32 +00:00
Skylot 72381ad8f3 fix: correct literal negate for double and float (#1334) 2022-01-13 14:00:53 +00:00
Skylot 6a065c46f4 chore: update dependencies 2022-01-13 12:12:15 +00:00
Skylot 092d0d7e67 fix(gui): reduce tree focus switching 2022-01-12 19:57:38 +03:00
Skylot 5ca7285558 fix(gui): correct handling for tree row click (#1324) 2022-01-12 16:57:25 +00:00
Skylot 7576f9cd5e fix: wrap negative literals before cast (#1327) 2022-01-12 17:31:40 +03:00
Skylot 46b5725d98 refactor(test): replace inputs with test profiles 2022-01-12 17:31:37 +03:00
Jan S 72542fa6f9 fix(gui): processing threads spinner initialization (#1331)(PR #1332)
* fix: processing threads spinner initialization (#1331)
* fix: processing threads spinner initialization (#1331)
2022-01-12 14:23:07 +00:00
demonlol a250d0461b fix(dbg): support multiple main <action> and <activity-alias> tags (#1322)(PR #1323)
* fix(dgb): support multiple main <action> and <activity-alias> tags in manifest
* Update jadx-gui/src/main/java/jadx/gui/device/debugger/DbgUtils.java
2022-01-02 20:09:24 +03:00
Skylot c7795bfc48 fix: improve anonymous class inline (#523) 2021-12-26 13:06:49 +00:00
Skylot 5de46b7e40 chore: update gradle and dependencies 2021-12-24 12:53:30 +00:00
Skylot 99c70872c1 fix: use debug line numbers only at fixed offsets (#1315) 2021-12-22 22:55:14 +03:00
Skylot 3566669303 chore: update lgtm config 2021-12-22 12:24:01 +00:00
Skylot 4557d05256 fix: use correct type for anonymous class instance (#597) 2021-12-21 17:47:52 +00:00
Skylot fa421d165e build: disable missing warnings from javadoc 2021-12-21 12:52:52 +00:00
Skylot ecf20020d7 chore: cache current working dir in static field, other minor changes 2021-12-20 19:25:07 +00:00
Skylot ae85af61c7 fix: skip input file name checks by zip name validator (#1310) 2021-12-20 18:55:28 +00:00
Skylot 659bbbf4fb fix: correct usage of Path.getParent() 2021-12-20 16:48:50 +00:00
Jan S 427e2dddc4 fix: use relative file paths in .jadx project file (#1312) (PR #1313)
* chore: use relative file paths in .jadx project file (#1312)
* code beautified
* requested changes
2021-12-20 13:52:51 +00:00
skylot d47483f957 docs: use jadx as a library 2021-12-19 20:36:58 +00:00
Skylot 4bd8e26ae7 build: add maven publish 2021-12-19 16:24:09 +00:00
Skylot 01f47282ed fix: forbid 'printStackTrace()' usage 2021-12-18 19:24:36 +00:00
Skylot afdd37cd97 fix: add comments with option references to improve usability 2021-12-15 12:24:37 +00:00
Skylot addaffcd1d chore: update dependencies 2021-12-15 11:56:01 +00:00
Skylot 63f7ce20a4 fix: add merged condition blocks for loop region (#1307) 2021-12-14 14:25:59 +00:00
Skylot f37c23db7a fix: use correct top block for try blocks with same start (#1304) 2021-12-13 18:14:27 +00:00
Skylot d2bde0be21 fix: invoke in nested anonymous classes (#1305) 2021-12-13 00:12:30 +03:00
SiderealArt 9c446ebbd6 feat(gui): add Traditional Chinese translation (PR #1306) 2021-12-12 16:05:10 +00:00
Skylot 0f00fb9a27 fix: handle move-result after invoke-custom with string concat 2021-12-11 16:22:27 +00:00
Skylot 2d6f819c86 chore: update gradle and dependencies 2021-12-11 16:22:27 +00:00
Skylot 56683ac409 fix: improve try/catch bounds detection (#1303) 2021-12-09 17:34:53 +00:00
skylot a72523c7df docs: add link to decompilation troubleshooting 2021-12-08 13:11:31 +00:00
Surendrajat 46eeb0bc22 fix(gui): forward navigation shortcut on macOS (#1297)(PR #1301)
* fix: forward navigation shortcut on macOS
* apply suggestion
2021-12-06 16:45:29 +03:00
Skylot 6e8baef9b2 feat(gui): allow to minimize/maximize search windows (#1298) 2021-12-04 11:04:17 +00:00
Skylot 947b621733 feat: add option to use dx/d8 for convert java bytecode (#1299) 2021-12-03 15:05:28 +00:00
Skylot 4cc00bdaf2 fix: handle super case for invokespecial opcode (#1300) 2021-12-02 18:13:19 +00:00
Moredistant 59ef569a63 fix(gui): update chinese translation (PR #1296) 2021-11-30 11:57:16 +03:00
Choiman1559 abae225915 Update Korean translation (#1294)
* Update Messages_ko_KR.properties

* Update Messages_ko_KR.properties

Add missing translations
2021-11-29 19:56:01 +03:00
Jan S 05bdf9daae perf(res): XML decoding speed enhancement (PR #1293)
* chore: XML decoding speed improved for large APKs (finding class references)
* skip attach class node to xml for SimpleCodeWriter (used in jadx-cli)

Co-authored-by: Skylot <skylot@gmail.com>
2021-11-29 15:08:54 +03:00
Haeter 0a8192168a fix(gui): update Quark report parsing (#1289) (PR #1291) 2021-11-28 19:31:28 +03:00
Hen Ry 88fd5a517e fix(gui): update German translation (PR #1290)
* Updated German translation
2021-11-28 19:15:07 +03:00
zhongqingsong 74c5b616a4 fix(gui): update Chinese translation (PR #1287)
1. According to the English version of the document, complete the left texts.
2. Fix some inaccurate word, such as field, old CN is variable(变量),  inadequacy. signer, old CN is somebody(人), now it's something(者)。
3. Fix improper use of symbols, Lack of symbols in some place, some EN symbol translate to CN symbol.
4. Other change
2021-11-26 18:40:01 +03:00
Skylot 22a61d715b build: sometimes build failing without running gradle daemon 2021-11-25 14:47:17 +03:00
Skylot a90ec7c64a fix: include inlined classes in usage search (#1285) 2021-11-25 14:47:13 +03:00
Jan Peter Stotz b22812b43a fix: APK signature description for unprotected entries only applies to v1 signatures 2021-11-24 16:46:38 +03:00
Jan Peter Stotz 4c0da8c3d5 fix: binary xml hexadecimal int value decoding 2021-11-24 16:46:38 +03:00
Moredistant 9aa30f77b7 fix(gui): update chinese translation (PR #1284) 2021-11-23 15:54:53 +03:00
Martin Kay 2dbef83fa6 feat(gui): smali code highlighting (PR #1283)
* smali code highlighting is basically perfect
* Optimize smali highlight color matching, and provide original jflex generation

* reformat code
* disable checkstyle
* update shell to be more environment independent

Co-authored-by: Skylot <skylot@gmail.com>
2021-11-23 15:53:37 +03:00
Skylot 6ec7f789ef fix: restore usage data after class reload (#1281) 2021-11-22 13:56:15 +00:00
Skylot 31c0afe29e fix: don't unload field init values (#1277) 2021-11-21 18:54:32 +00:00
Skylot 46b07863c1 build: fix bundle build 2021-11-20 20:49:57 +00:00
380 changed files with 14762 additions and 3549 deletions
+41
View File
@@ -0,0 +1,41 @@
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 9 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ['java']
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
queries: +security-extended
languages: ${{ matrix.language }}
# Don't build tests in jadx-core also skip tests execution and checkstyle tasks
- run: |
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+2
View File
@@ -34,3 +34,5 @@ jadx-output/
*.cfg
*.orig
quark.json
cliff.toml
+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
+36 -6
View File
@@ -5,12 +5,15 @@
[![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/)
[![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)
**jadx** - Dex to Java decompiler
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
**Main features:**
- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
@@ -21,7 +24,9 @@ Command line and GUI tools for producing Java source code from Android Dex and A
- jump to declaration
- find usage
- 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)
@@ -37,7 +42,7 @@ After download unpack zip file go to `bin` directory and run:
On Windows run `.bat` files with double-click\
**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
1. Arch linux
@@ -49,6 +54,9 @@ For windows you can download it from [oracle.com](https://www.oracle.com/java/te
brew install jadx
```
### Use jadx as a library
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
### Build from source
JDK 8 or higher must be installed:
```
@@ -71,10 +79,16 @@ options:
-dr, --output-dir-res - output directory for resources
-r, --no-res - do not decode resources
-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
-e, --export-gradle - save as android gradle project
-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)
--no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info
@@ -88,9 +102,15 @@ options:
--deobf-min - min length of name, renamed if shorter, default: 3
--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-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-parse-kotlin-metadata - parse kotlin metadata to class and package names
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
--rename-flags - fix options (comma-separated list of):
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
'valid' - rename java identifiers to make them valid,
@@ -100,18 +120,28 @@ options:
--fs-case-sensitive - treat filesystem as case sensitive, false by default
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
--comments-level - set code comments level, values: none, user_only, error, warn, info, debug, default: info
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
--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
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
--version - print jadx version
-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:
jadx -d out classes.dex
jadx --rename-flags "none" classes.dex
jadx --rename-flags "valid, printable" classes.dex
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
+7
View File
@@ -0,0 +1,7 @@
# Security Policy
## Reporting a Vulnerability
To report a security issue, please email `skylot@gmail.com` with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
We will check and respond within 3 working days. If the issue is confirmed as a vulnerability, we will apply required mitigations at the next release.
This project follows a 90 day disclosure timeline.
+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>
+30 -33
View File
@@ -1,6 +1,6 @@
plugins {
id 'com.github.ben-manes.versions' version '0.39.0'
id 'com.diffplug.spotless' version '6.0.0'
id 'com.github.ben-manes.versions' version '0.42.0'
id 'com.diffplug.spotless' version '6.4.2'
}
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -10,7 +10,6 @@ println("jadx version: ${jadxVersion}")
allprojects {
apply plugin: 'java'
apply plugin: 'checkstyle'
apply plugin: 'maven-publish'
version = jadxVersion
@@ -27,32 +26,24 @@ allprojects {
}
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
}
dependencies {
implementation 'org.slf4j:slf4j-api:1.7.32'
implementation 'org.slf4j:slf4j-api:1.7.36'
compileOnly 'org.jetbrains:annotations:23.0.0'
testImplementation 'ch.qos.logback:logback-classic:1.2.7'
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
testImplementation 'org.hamcrest:hamcrest-library:2.2'
testImplementation 'org.mockito:mockito-core:4.1.0'
testImplementation 'org.assertj:assertj-core:3.21.0'
testImplementation 'org.mockito:mockito-core:4.4.0'
testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api: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'
}
test {
useJUnitPlatform()
maxParallelForks = Runtime.runtime.availableProcessors()
}
repositories {
@@ -76,8 +67,9 @@ spotless {
if (JavaVersion.current() < JavaVersion.VERSION_16) {
removeUnusedImports()
} else {
// google-format broken on java 16 (https://github.com/diffplug/spotless/issues/834)
println('Warning! Unused imports remove is disabled for Java 16')
// google-format on Java 16+ issue: https://github.com/diffplug/spotless/issues/834
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)
@@ -135,19 +127,7 @@ task copyExe(type: Copy) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
task dist {
group 'jadx'
description = 'Build jadx distribution zip'
dependsOn(pack)
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
if (os.isWindows()) {
dependsOn('copyExe')
}
}
task distWin(type: Copy, dependsOn: 'jadx-gui:distWinWithJre') {
task distWinBundle(type: Copy, dependsOn: 'jadx-gui:distWinWithJre') {
group 'jadx'
description = 'Copy bundle to build dir'
destinationDir buildDir
@@ -157,6 +137,23 @@ task distWin(type: Copy, dependsOn: 'jadx-gui:distWinWithJre') {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
task dist {
group 'jadx'
description = 'Build jadx distribution zip'
dependsOn(pack)
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
if (os.isWindows()) {
if (project.hasProperty("bundleJRE")) {
println("Build win bundle with JRE")
dependsOn('distWinBundle')
} else {
dependsOn('copyExe')
}
}
}
task cleanBuildDir(type: Delete) {
group 'jadx'
delete buildDir
+3
View File
@@ -0,0 +1,3 @@
plugins {
id 'groovy-gradle-plugin'
}
@@ -0,0 +1,79 @@
plugins {
id 'java-library'
id 'maven-publish'
id 'signing'
}
group = 'io.github.skylot'
version = jadxVersion
java {
withJavadocJar()
withSourcesJar()
}
publishing {
publications {
mavenJava(MavenPublication) {
artifactId = project.name
from components.java
versionMapping {
usage('java-api') {
fromResolutionOf('runtimeClasspath')
}
usage('java-runtime') {
fromResolutionResult()
}
}
pom {
name = project.name
description = 'Dex to Java decompiler'
url = 'https://github.com/skylot/jadx'
licenses {
license {
name = 'The Apache License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id = 'skylot'
name = 'Skylot'
email = 'skylot@gmail.com'
url = 'https://github.com/skylot'
}
}
scm {
connection = 'scm:git:git://github.com/skylot/jadx.git'
developerConnection = 'scm:git:ssh://github.com:skylot/jadx.git'
url = 'https://github.com/skylot/jadx'
}
}
}
}
repositories {
maven {
def releasesRepoUrl = uri('https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/')
def snapshotsRepoUrl = uri('https://s01.oss.sonatype.org/content/repositories/snapshots/')
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials {
username = project.properties['ossrhUser'].toString()
password = project.properties['ossrhPassword'].toString()
}
}
}
}
signing {
required { gradle.taskGraph.hasTask("publish") }
sign publishing.publications.mavenJava
}
javadoc {
if (JavaVersion.current().isJava9Compatible()) {
options.addBooleanOption('html5', true)
}
// disable 'missing' warnings
options.addStringOption('Xdoclint:all,-missing', '-quiet')
}
+6
View File
@@ -124,6 +124,12 @@
<module name="IllegalImport">
<property name="illegalClasses" value="jadx.core.utils.DebugUtils"/>
</module>
<module name="RegexpSinglelineJava">
<property name="id" value="printstacktrace"/>
<property name="format" value="\.printStackTrace\(\)"/>
<property name="ignoreComments" value="true"/>
<property name="message" value="Using Throwable.printStackTrace() is forbidden. Use logger to print exception"/>
</module>
</module>
<module name="NewlineAtEndOfFile"/>
+1
View File
@@ -0,0 +1 @@
SmaliTokenMaker.java
+5
View File
@@ -0,0 +1,5 @@
Refer to the following instructions to modify and use to generate SmaliTokenMarker
```shell
jflex SmaliTokenMaker.flex --skel skeleton.default
```
+681
View File
@@ -0,0 +1,681 @@
/*
* Generated on 11/22/21, 8:58 PM
*/
package jadx.gui.ui.codeearea;
import java.io.*;
import javax.swing.text.Segment;
import org.fife.ui.rsyntaxtextarea.*;
/**
* 用于Smali代码高亮
* MartinKay@qq.com
*/
%%
%public
%class SmaliTokenMaker
%extends AbstractJFlexCTokenMaker
%unicode
/* Case sensitive */
%type org.fife.ui.rsyntaxtextarea.Token
%{
/**
* Constructor. This must be here because JFlex does not generate a
* no-parameter constructor.
*/
public SmaliTokenMaker() {
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
* @see #addToken(int, int, int)
*/
private void addHyperlinkToken(int start, int end, int tokenType) {
int so = start + offsetShift;
addToken(zzBuffer, start,end, tokenType, so, true);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
*/
private void addToken(int tokenType) {
addToken(zzStartRead, zzMarkedPos-1, tokenType);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
* @see #addHyperlinkToken(int, int, int)
*/
private void addToken(int start, int end, int tokenType) {
int so = start + offsetShift;
addToken(zzBuffer, start,end, tokenType, so, false);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param array The character array.
* @param start The starting offset in the array.
* @param end The ending offset in the array.
* @param tokenType The token's type.
* @param startOffset The offset in the document at which this token
* occurs.
* @param hyperlink Whether this token is a hyperlink.
*/
public void addToken(char[] array, int start, int end, int tokenType,
int startOffset, boolean hyperlink) {
super.addToken(array, start,end, tokenType, startOffset, hyperlink);
zzStartRead = zzMarkedPos;
}
/**
* {@inheritDoc}
*/
public String[] getLineCommentStartAndEnd(int languageIndex) {
return new String[] { "#", null };
}
/**
* Returns the first token in the linked list of tokens generated
* from <code>text</code>. This method must be implemented by
* subclasses so they can correctly implement syntax highlighting.
*
* @param text The text from which to get tokens.
* @param initialTokenType The token type we should start with.
* @param startOffset The offset into the document at which
* <code>text</code> starts.
* @return The first <code>Token</code> in a linked list representing
* the syntax highlighted text.
*/
public Token getTokenList(Segment text, int initialTokenType, int startOffset) {
resetTokenList();
this.offsetShift = -text.offset + startOffset;
// Start off in the proper state.
int state = Token.NULL;
switch (initialTokenType) {
/* No multi-line comments */
/* No documentation comments */
default:
state = Token.NULL;
}
s = text;
try {
yyreset(zzReader);
yybegin(state);
return yylex();
} catch (IOException ioe) {
ioe.printStackTrace();
return new TokenImpl();
}
}
/**
* Refills the input buffer.
*
* @return <code>true</code> if EOF was reached, otherwise
* <code>false</code>.
*/
private boolean zzRefill() {
return zzCurrentPos>=s.offset+s.count;
}
/**
* Resets the scanner to read from a new input stream.
* Does not close the old reader.
*
* All internal variables are reset, the old input stream
* <b>cannot</b> be reused (internal buffer is discarded and lost).
* Lexical state is set to <tt>YY_INITIAL</tt>.
*
* @param reader the new input stream
*/
public final void yyreset(Reader reader) {
// 's' has been updated.
zzBuffer = s.array;
/*
* We replaced the line below with the two below it because zzRefill
* no longer "refills" the buffer (since the way we do it, it's always
* "full" the first time through, since it points to the segment's
* array). So, we assign zzEndRead here.
*/
//zzStartRead = zzEndRead = s.offset;
zzStartRead = s.offset;
zzEndRead = zzStartRead + s.count - 1;
zzCurrentPos = zzMarkedPos = zzPushbackPos = s.offset;
zzLexicalState = YYINITIAL;
zzReader = reader;
zzAtBOL = true;
zzAtEOF = false;
}
%}
Letter = [A-Za-z]
LetterOrUnderscore = ({Letter}|"_")
NonzeroDigit = [1-9]
Digit = ("0"|{NonzeroDigit})
HexDigit = ({Digit}|[A-Fa-f])
OctalDigit = ([0-7])
AnyCharacterButApostropheOrBackSlash = ([^\\'])
AnyCharacterButDoubleQuoteOrBackSlash = ([^\\\"\n])
EscapedSourceCharacter = ("u"{HexDigit}{HexDigit}{HexDigit}{HexDigit})
Escape = ("\\"(([btnfr\"'\\])|([0123]{OctalDigit}?{OctalDigit}?)|({OctalDigit}{OctalDigit}?)|{EscapedSourceCharacter}))
NonSeparator = ([^\t\f\r\n\ \(\)\{\}\[\]\;\,\.\=\>\<\!\~\?\:\+\-\*\/\&\|\^\%\"\']|"#"|"\\")
IdentifierStart = ({LetterOrUnderscore}|"$")
IdentifierPart = ({IdentifierStart}|{Digit}|("\\"{EscapedSourceCharacter}))
LineTerminator = (\n)
WhiteSpace = ([ \t\f]+)
CharLiteral = ([\']({AnyCharacterButApostropheOrBackSlash}|{Escape})[\'])
UnclosedCharLiteral = ([\'][^\'\n]*)
ErrorCharLiteral = ({UnclosedCharLiteral}[\'])
StringLiteral = ([\"]({AnyCharacterButDoubleQuoteOrBackSlash}|{Escape})*[\"])
UnclosedStringLiteral = ([\"]([\\].|[^\\\"])*[^\"]?)
ErrorStringLiteral = ({UnclosedStringLiteral}[\"])
/* No multi-line comments */
/* No documentation comments */
LineCommentBegin = "#"
IntegerLiteral = ({Digit}+)
HexLiteral = (0x{HexDigit}+)
FloatLiteral = (({Digit}+)("."{Digit}+)?(e[+-]?{Digit}+)? | ({Digit}+)?("."{Digit}+)(e[+-]?{Digit}+)?)
ErrorNumberFormat = (({IntegerLiteral}|{HexLiteral}|{FloatLiteral}){NonSeparator}+)
BooleanLiteral = ("true"|"false")
Separator = ([\(\)\{\}\[\]])
Separator2 = ([\;,.])
Identifier = ({IdentifierStart}{IdentifierPart}*)
URLGenDelim = ([:\/\?#\[\]@])
URLSubDelim = ([\!\$&'\(\)\*\+,;=])
URLUnreserved = ({LetterOrUnderscore}|{Digit}|[\-\.\~])
URLCharacter = ({URLGenDelim}|{URLSubDelim}|{URLUnreserved}|[%])
URLCharacters = ({URLCharacter}*)
URLEndCharacter = ([\/\$]|{Letter}|{Digit})
URL = (((https?|f(tp|ile))"://"|"www.")({URLCharacters}{URLEndCharacter})?)
/* Custom Regex */
/* fully-qualified name Rules */
SimpleName = ([a-zA-Z0-9_$]*)
QUALIFIED_TYPE_NAME = ("L"({SimpleName}{SLASH})*{SimpleName}*";")
/* Types */
VOID_TYPE = ("V")
BOOLEAN_TYPE = ("Z")
BYTE_TYPE = ("B")
SHORT_TYPE = ("S")
CHAR_TYPE = ("C")
INT_TYPE = ("I")
LONG_TYPE = ("J")
FLOAT_TYPE = ("F")
DOUBLE_TYPE = ("D")
/* Multi Args Types Highlight */
MULTI_ARGS_TYPES = (({BOOLEAN_TYPE}|{BYTE_TYPE}|{SHORT_TYPE}|{CHAR_TYPE}|{INT_TYPE}|{LONG_TYPE}|{FLOAT_TYPE}|{DOUBLE_TYPE})+);
/* Types fully-qualified name */
COMPOUND_METHOD_ARG_LITERAL = (({BOOLEAN_TYPE}|{BYTE_TYPE}|{SHORT_TYPE}|{CHAR_TYPE}|{INT_TYPE}|{LONG_TYPE}|{FLOAT_TYPE}|{DOUBLE_TYPE})+{QUALIFIED_TYPE_NAME})
LBRACK = ("[")
RBRACK = ("]")
LPAREN = ("(")
RPAREN = (")")
LBRACE = ("{")
RBRACE = ("}")
COLON = (":")
ASSIGN = ("=")
DOT = (".")
SUB = ("-")
COMMA = (",")
SLASH = ("/")
LT = ("<")
GT = (">")
ARROW = ("->")
SEMI = (";")
ARROW_FUNCTION = ({ARROW}[a-zA-Z_$<>]*)
CustomSeparator = ({Separator}|{ARROW_FUNCTION})
/* Register */
VREGISTER = ("v"("0"|[1-9])*)
PREGISTER = ("p"("0"|[1-9])*)
/* Flags */
FLAG_PSWITCH = (":pswitch_"{SimpleName})
FLAG_PSWITCH_DATA = (":pswitch_data_"{SimpleName})
FLAG_GOTO = (":goto_"{SimpleName})
FLAG_COND = (":cond_"{SimpleName})
FLAG_TRY_START = (":try_start_"{SimpleName})
FLAG_TRY_END = (":try_end_"{SimpleName})
FLAG_CATCH = (":catch_"{SimpleName})
FLAG_CATCHALL = (":catchall_"{SimpleName})
FLAG_ARRAY = (":array_"{SimpleName})
/* No string state */
/* No char state */
/* No MLC state */
/* No documentation comment state */
%state EOL_COMMENT
%%
<YYINITIAL> {
/* Keywords Instructions Highlight */
"nop" |
"move" |
"move/from16" |
"move/16" |
"move-wide" |
"move-wide/from16" |
"move-wide/16" |
"move-object" |
"move-object/from16" |
"move-object/16" |
"move-result" |
"move-result-wide" |
"move-result-object" |
"move-exception" |
"return-void" |
"return" |
"return-wide" |
"return-object" |
"const/4" |
"const/16" |
"const" |
"const/high16" |
"const-wide/16" |
"const-wide/32" |
"const-wide" |
"const-wide/high16" |
"const-string" |
"const-string/jumbo" |
"const-class" |
"monitor-enter" |
"monitor-exit" |
"check-cast" |
"instance-of" |
"array-length" |
"new-instance" |
"new-array" |
"filled-new-array" |
"filled-new-array/range" |
"fill-array-data" |
"throw" |
"goto" |
"goto/16" |
"goto/32" |
"cmpl-float" |
"cmpg-float" |
"cmpl-double" |
"cmpg-double" |
"cmp-long" |
"if-eq" |
"if-ne" |
"if-lt" |
"if-ge" |
"if-gt" |
"if-le" |
"if-eqz" |
"if-nez" |
"if-ltz" |
"if-gez" |
"if-gtz" |
"if-lez" |
"aget" |
"aget-wide" |
"aget-object" |
"aget-boolean" |
"aget-byte" |
"aget-char" |
"aget-short" |
"aput" |
"aput-wide" |
"aput-object" |
"aput-boolean" |
"aput-byte" |
"aput-char" |
"aput-short" |
"iget" |
"iget-wide" |
"iget-object" |
"iget-boolean" |
"iget-byte" |
"iget-char" |
"iget-short" |
"iput" |
"iput-wide" |
"iput-object" |
"iput-boolean" |
"iput-byte" |
"iput-char" |
"iput-short" |
"sget" |
"sget-wide" |
"sget-object" |
"sget-boolean" |
"sget-byte" |
"sget-char" |
"sget-short" |
"sput" |
"sput-wide" |
"sput-object" |
"sput-boolean" |
"sput-byte" |
"sput-char" |
"sput-short" |
"invoke-virtual" |
"invoke-super" |
"invoke-direct" |
"invoke-static" |
"invoke-interface" |
"invoke-virtual/range" |
"invoke-super/range" |
"invoke-direct/range" |
"invoke-static/range" |
"invoke-interface/range" |
"neg-int" |
"not-int" |
"neg-long" |
"not-long" |
"neg-float" |
"neg-double" |
"int-to-long" |
"int-to-float" |
"int-to-double" |
"long-to-int" |
"long-to-float" |
"long-to-double" |
"float-to-int" |
"float-to-long" |
"float-to-double" |
"double-to-int" |
"double-to-long" |
"double-to-float" |
"int-to-byte" |
"int-to-char" |
"int-to-short" |
"add-int" |
"sub-int" |
"mul-int" |
"div-int" |
"rem-int" |
"and-int" |
"or-int" |
"xor-int" |
"shl-int" |
"shr-int" |
"ushr-int" |
"add-long" |
"sub-long" |
"mul-long" |
"div-long" |
"rem-long" |
"and-long" |
"or-long" |
"xor-long" |
"shl-long" |
"shr-long" |
"ushr-long" |
"add-float" |
"sub-float" |
"mul-float" |
"div-float" |
"rem-float" |
"add-double" |
"sub-double" |
"mul-double" |
"div-double" |
"rem-double" |
"add-int/2addr" |
"sub-int/2addr" |
"mul-int/2addr" |
"div-int/2addr" |
"rem-int/2addr" |
"and-int/2addr" |
"or-int/2addr" |
"xor-int/2addr" |
"shl-int/2addr" |
"shr-int/2addr" |
"ushr-int/2addr" |
"add-long/2addr" |
"sub-long/2addr" |
"mul-long/2addr" |
"div-long/2addr" |
"rem-long/2addr" |
"and-long/2addr" |
"or-long/2addr" |
"xor-long/2addr" |
"shl-long/2addr" |
"shr-long/2addr" |
"ushr-long/2addr" |
"add-float/2addr" |
"sub-float/2addr" |
"mul-float/2addr" |
"div-float/2addr" |
"rem-float/2addr" |
"add-double/2addr" |
"sub-double/2addr" |
"mul-double/2addr" |
"div-double/2addr" |
"rem-double/2addr" |
"add-int/lit16" |
"rsub-int" |
"mul-int/lit16" |
"div-int/lit16" |
"rem-int/lit16" |
"and-int/lit16" |
"or-int/lit16" |
"xor-int/lit16" |
"add-int/lit8" |
"rsub-int/lit8" |
"mul-int/lit8" |
"div-int/lit8" |
"rem-int/lit8" |
"and-int/lit8" |
"or-int/lit8" |
"xor-int/lit8" |
"shl-int/lit8" |
"shr-int/lit8" |
"ushr-int/lit8" |
"invoke-polymorphic" |
"invoke-polymorphic/range" |
"invoke-custom" |
"invoke-custom/range" |
"const-method-handle" |
"const-method-type" |
"packed-switch" |
"sparse-switch" { addToken(Token.FUNCTION); }
/* Keywords Modifiers(IDENTIFIER标识符、修饰符) Highlight */
"public" |
"private" |
"protected" |
"final" |
"annotation" |
"static" |
"synthetic" |
"constructor" |
"abstract" |
"enum" |
"interface" |
"transient" |
"bridge" |
"declared-synchronized" |
"volatile" |
"strictfp" |
"varargs" |
"native" { addToken(Token.RESERVED_WORD); }
/* Keywords Directives Highlight */
".method" |
".end method" |
".implements" |
".class" |
".prologue" |
".source" |
".super" |
".field" |
".end field" |
".registers" |
".locals" |
".param" |
".line" |
".catch" |
".catchall" |
".annotation" |
".end annotation" |
".local" |
".end local" |
".restart local" |
".packed-switch" |
".end packed-switch" |
".array-data" |
".end array-data" |
".sparse-switch" |
".end sparse-switch" |
".end param" { addToken(Token.RESERVED_WORD_2); }
/* VARIABLE Register Highlight */
{VREGISTER} |
{PREGISTER} { addToken(Token.VARIABLE); }
/* Data types Highlight */
{QUALIFIED_TYPE_NAME} |
{COMPOUND_METHOD_ARG_LITERAL} |
{MULTI_ARGS_TYPES} |
{QUALIFIED_TYPE_NAME} |
{VOID_TYPE} |
{BOOLEAN_TYPE} |
{BYTE_TYPE} |
{SHORT_TYPE} |
{CHAR_TYPE} |
{INT_TYPE} |
{LONG_TYPE} |
{FLOAT_TYPE} |
{DOUBLE_TYPE} { addToken(Token.DATA_TYPE); }
/* FLAGS */
{FLAG_PSWITCH} |
{FLAG_PSWITCH_DATA} |
{FLAG_GOTO} |
{FLAG_COND} |
{FLAG_TRY_START} |
{FLAG_TRY_END} |
{FLAG_CATCHALL} |
{FLAG_ARRAY} |
{FLAG_CATCH} { addToken(Token.MARKUP_TAG_NAME); }
/* Functions */
/* No functions */
{BooleanLiteral} { addToken(Token.LITERAL_BOOLEAN); }
{LineTerminator} { addNullToken(); return firstToken; }
{Identifier} { addToken(Token.IDENTIFIER); }
{WhiteSpace} { addToken(Token.WHITESPACE); }
/* String/Character literals. */
{CharLiteral} { addToken(Token.LITERAL_CHAR); }
{UnclosedCharLiteral} { addToken(Token.ERROR_CHAR); addNullToken(); return firstToken; }
{ErrorCharLiteral} { addToken(Token.ERROR_CHAR); }
{StringLiteral} { addToken(Token.LITERAL_STRING_DOUBLE_QUOTE); }
{UnclosedStringLiteral} { addToken(Token.ERROR_STRING_DOUBLE); addNullToken(); return firstToken; }
{ErrorStringLiteral} { addToken(Token.ERROR_STRING_DOUBLE); }
/* Comment literals. */
/* No multi-line comments */
/* No documentation comments */
{LineCommentBegin} { start = zzMarkedPos-1; yybegin(EOL_COMMENT); }
/* Separators. */
{CustomSeparator} { addToken(Token.SEPARATOR); }
{Separator2} { addToken(Token.IDENTIFIER); }
/* Operators. */
"!" |
";" |
"." |
"=" |
"/" |
"'" |
"(" |
")" |
"," |
"->" |
";->" |
"<" |
">" |
"@" |
"[" |
"]" |
"{" |
"}" { addToken(Token.OPERATOR); }
/* Numbers */
{IntegerLiteral} { addToken(Token.LITERAL_NUMBER_DECIMAL_INT); }
{HexLiteral} { addToken(Token.LITERAL_NUMBER_HEXADECIMAL); }
{FloatLiteral} { addToken(Token.LITERAL_NUMBER_FLOAT); }
{ErrorNumberFormat} { addToken(Token.ERROR_NUMBER_FORMAT); }
/* Ended with a line not in a string or comment. */
<<EOF>> { addNullToken(); return firstToken; }
/* Catch any other (unhandled) characters. */
. { addToken(Token.IDENTIFIER); }
}
/* No char state */
/* No string state */
/* No multi-line comment state */
/* No documentation comment state */
<EOL_COMMENT> {
[^hwf\n]+ {}
{URL} { int temp=zzStartRead; addToken(start,zzStartRead-1, Token.COMMENT_EOL); addHyperlinkToken(temp,zzMarkedPos-1, Token.COMMENT_EOL); start = zzMarkedPos; }
[hwf] {}
\n { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
<<EOF>> { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
}
+243
View File
@@ -0,0 +1,243 @@
/** This character denotes the end of file */
public static final int YYEOF = -1;
/** initial size of the lookahead buffer */
--- private static final int ZZ_BUFFERSIZE = ...;
/** lexical states */
--- lexical states, charmap
/* error codes */
private static final int ZZ_UNKNOWN_ERROR = 0;
private static final int ZZ_NO_MATCH = 1;
private static final int ZZ_PUSHBACK_2BIG = 2;
/* error messages for the codes above */
private static final String ZZ_ERROR_MSG[] = {
"Unkown internal scanner error",
"Error: could not match input",
"Error: pushback value was too large"
};
--- isFinal list
/** the input device */
private java.io.Reader zzReader;
/** the current state of the DFA */
private int zzState;
/** the current lexical state */
private int zzLexicalState = YYINITIAL;
/** this buffer contains the current text to be matched and is
the source of the yytext() string */
private char zzBuffer[];
/** the textposition at the last accepting state */
private int zzMarkedPos;
/** the textposition at the last state to be included in yytext */
private int zzPushbackPos;
/** the current text position in the buffer */
private int zzCurrentPos;
/** startRead marks the beginning of the yytext() string in the buffer */
private int zzStartRead;
/** endRead marks the last character in the buffer, that has been read
from input */
private int zzEndRead;
/** number of newlines encountered up to the start of the matched text */
private int yyline;
/** the number of characters up to the start of the matched text */
private int yychar;
/**
* the number of characters from the last newline up to the start of the
* matched text
*/
private int yycolumn;
/**
* zzAtBOL == true <=> the scanner is currently at the beginning of a line
*/
private boolean zzAtBOL = true;
/** zzAtEOF == true <=> the scanner is at the EOF */
private boolean zzAtEOF;
--- user class code
/**
* Creates a new scanner
* There is also a java.io.InputStream version of this constructor.
*
* @param in the java.io.Reader to read input from.
*/
--- constructor declaration
/**
* Closes the input stream.
*/
public final void yyclose() throws java.io.IOException {
zzAtEOF = true; /* indicate end of file */
zzEndRead = zzStartRead; /* invalidate buffer */
if (zzReader != null)
zzReader.close();
}
/**
* Enters a new lexical state
*
* @param newState the new lexical state
*/
public final void yybegin(int newState) {
zzLexicalState = newState;
}
/**
* Returns the text matched by the current regular expression.
*/
public final String yytext() {
return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
}
/**
* Returns the character at position <tt>pos</tt> from the
* matched text.
*
* It is equivalent to yytext().charAt(pos), but faster
*
* @param pos the position of the character to fetch.
* A value from 0 to yylength()-1.
*
* @return the character at position pos
*/
public final char yycharat(int pos) {
return zzBuffer[zzStartRead+pos];
}
/**
* Returns the length of the matched text region.
*/
public final int yylength() {
return zzMarkedPos-zzStartRead;
}
/**
* Reports an error that occured while scanning.
*
* In a wellformed scanner (no or only correct usage of
* yypushback(int) and a match-all fallback rule) this method
* will only be called with things that "Can't Possibly Happen".
* If this method is called, something is seriously wrong
* (e.g. a JFlex bug producing a faulty scanner etc.).
*
* Usual syntax/scanner level error handling should be done
* in error fallback rules.
*
* @param errorCode the code of the errormessage to display
*/
--- zzScanError declaration
String message;
try {
message = ZZ_ERROR_MSG[errorCode];
}
catch (ArrayIndexOutOfBoundsException e) {
message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
}
--- throws clause
}
/**
* Pushes the specified amount of characters back into the input stream.
*
* They will be read again by then next call of the scanning method
*
* @param number the number of characters to be read again.
* This number must not be greater than yylength()!
*/
--- yypushback decl (contains zzScanError exception)
if ( number > yylength() )
zzScanError(ZZ_PUSHBACK_2BIG);
zzMarkedPos -= number;
}
--- zzDoEOF
/**
* Resumes scanning until the next regular expression is matched,
* the end of input is encountered or an I/O-Error occurs.
*
* @return the next token
* @exception java.io.IOException if any I/O-Error occurs
*/
--- yylex declaration
int zzInput;
int zzAction;
// cached fields:
int zzCurrentPosL;
int zzMarkedPosL;
int zzEndReadL = zzEndRead;
char [] zzBufferL = zzBuffer;
char [] zzCMapL = ZZ_CMAP;
--- local declarations
while (true) {
zzMarkedPosL = zzMarkedPos;
--- start admin (line, char, col count)
zzAction = -1;
zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
--- start admin (lexstate etc)
zzForAction: {
while (true) {
--- next input, line, col, char count, next transition, isFinal action
zzAction = zzState;
zzMarkedPosL = zzCurrentPosL;
--- line count update
}
}
}
// store back cached position
zzMarkedPos = zzMarkedPosL;
--- char count update
--- actions
default:
if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
zzAtEOF = true;
--- eofvalue
}
else {
--- no match
}
}
}
}
--- main
}
+1 -1
View File
@@ -1,2 +1,2 @@
org.gradle.daemon=false
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
distributionPath=wrapper/dists
distributionSha256Sum=de8f52ad49bdc759164f72439a3bf56ddb1589c4cde802d3cec7d6ad0e0ee410
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+4 -3
View File
@@ -7,16 +7,17 @@ dependencies {
runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
runtimeOnly(project(':jadx-plugins:jadx-java-input'))
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
implementation 'com.beust:jcommander:1.81'
implementation 'ch.qos.logback:logback-classic:1.2.7'
implementation 'com.beust:jcommander:1.82'
implementation 'ch.qos.logback:logback-classic:1.2.11'
}
application {
applicationName = 'jadx'
mainClass.set('jadx.cli.JadxCLI')
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
applicationDefaultJvmArgs = ['-Xms128M', '-XX:MaxRAMPercentage=70.0', '-XX:+UseG1GC']
}
applicationDistribution.with {
@@ -17,6 +17,11 @@ import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameterized;
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> {
private final JCommander jc;
@@ -70,40 +75,44 @@ public class JCommanderWrapper<T> {
maxNamesLen = len;
}
}
maxNamesLen += 3;
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
for (Field f : getFields(args.getClass())) {
String name = f.getName();
ParameterDescription p = paramsMap.get(name);
if (p == null) {
if (p == null || p.getParameter().hidden()) {
continue;
}
StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames());
String description = p.getDescription();
addSpaces(opt, maxNamesLen - opt.length() + 3);
addSpaces(opt, maxNamesLen - opt.length());
if (description.contains("\n")) {
String[] lines = description.split("\n");
opt.append("- ").append(lines[0]);
for (int i = 1; i < lines.length; i++) {
opt.append('\n');
addSpaces(opt, maxNamesLen + 5);
addSpaces(opt, maxNamesLen + 2);
opt.append(lines[i]);
}
} else {
opt.append("- ").append(description);
}
String defaultValue = getDefaultValue(args, f, opt);
if (defaultValue != null) {
if (defaultValue != null && !description.contains("(default)")) {
opt.append(", default: ").append(defaultValue);
}
out.println(opt);
}
out.println(appendPluginOptions(maxNamesLen));
out.println();
out.println("Examples:");
out.println(" jadx -d out classes.dex");
out.println(" jadx --rename-flags \"none\" classes.dex");
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
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(' ');
}
}
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.impl.NoOpCodeCache;
import jadx.api.impl.SimpleCodeWriter;
import jadx.cli.LogHelper.LogLevelEnum;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.files.FileUtils;
@@ -21,7 +22,7 @@ public class JadxCLI {
LOG.error("Incorrect arguments: {}", e.getMessage());
result = 1;
} catch (Exception e) {
LOG.error("jadx error: {}", e.getMessage(), e);
LOG.error("Process error:", e);
result = 1;
} finally {
FileUtils.deleteTempRootDir();
@@ -32,23 +33,25 @@ public class JadxCLI {
public static int execute(String[] args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) {
return processAndSave(jadxArgs.toJadxArgs());
return processAndSave(jadxArgs);
}
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.setCodeWriterProvider(SimpleCodeWriter::new);
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load();
if (LogHelper.getLogLevel() == LogHelper.LogLevelEnum.QUIET) {
jadx.save();
} else {
jadx.save(500, (done, total) -> {
int progress = (int) (done * 100.0 / total);
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
});
if (checkForErrors(jadx)) {
return 1;
}
LogHelper.setLogLevelsForDecompileStage();
if (!SingleClassMode.process(jadx, cliArgs)) {
save(jadx);
}
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
@@ -60,4 +63,30 @@ public class JadxCLI {
}
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");
}
}
}
+133 -11
View File
@@ -2,19 +2,25 @@ package jadx.cli;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.beust.jcommander.DynamicParameter;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
import jadx.api.CommentsLevel;
import jadx.api.DecompilationMode;
import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils;
@@ -38,9 +44,12 @@ public class JadxCLIArgs {
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
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;
@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'")
protected String outputFormat = "java";
@@ -50,6 +59,17 @@ public class JadxCLIArgs {
@Parameter(names = { "-j", "--threads-count" }, description = "processing 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)")
protected boolean showInconsistentCode = false;
@@ -92,7 +112,18 @@ public class JadxCLIArgs {
)
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;
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
@@ -101,6 +132,13 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
protected boolean deobfuscationParseKotlinMetadata = false;
@Parameter(
names = { "--use-kotlin-methods-for-var-names" },
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
converter = UseKotlinMethodsForVarNamesConverter.class
)
protected UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
@Parameter(
names = { "--rename-flags" },
description = "fix options (comma-separated list of):"
@@ -122,12 +160,15 @@ public class JadxCLIArgs {
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
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;
@Parameter(names = { "--use-dx" }, description = "use dx/d8 to convert java bytecode")
protected boolean useDx = false;
@Parameter(
names = { "--comments-level" },
description = "set code comments level, values: error, warn, info, debug, user_only, none",
description = "set code comments level, values: error, warn, info, debug, user-only, none",
converter = CommentsLevelConverter.class
)
protected CommentsLevel commentsLevel = CommentsLevel.INFO;
@@ -151,6 +192,9 @@ public class JadxCLIArgs {
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
protected boolean printHelp = false;
@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
protected Map<String, String> pluginOptions = new HashMap<>();
public boolean processArgs(String[] args) {
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
return jcw.parse(args) && process(jcw);
@@ -186,7 +230,6 @@ public class JadxCLIArgs {
if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive, got: " + threadsCount);
}
LogHelper.setLogLevelFromArgs(this);
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
jcw.printUsage();
@@ -204,22 +247,28 @@ public class JadxCLIArgs {
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
args.setThreadsCount(threadsCount);
args.setSkipSources(skipSources);
if (singleClass != null) {
args.setClassFilter(className -> singleClass.equals(className));
}
args.setSkipResources(skipResources);
args.setFallbackMode(fallbackMode);
if (fallbackMode) {
args.setDecompilationMode(DecompilationMode.FALLBACK);
} else {
args.setDecompilationMode(decompilationMode);
}
args.setShowInconsistentCode(showInconsistentCode);
args.setCfgOutput(cfgOutput);
args.setRawCFGOutput(rawCfgOutput);
args.setReplaceConsts(replaceConsts);
args.setDeobfuscationOn(deobfuscationOn);
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
args.setDeobfuscationForceSave(deobfuscationForceSave);
if (deobfuscationForceSave) {
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
} else {
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
}
args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject);
@@ -231,6 +280,8 @@ public class JadxCLIArgs {
args.setRenameFlags(renameFlags);
args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel);
args.setUseDxInput(useDx);
args.setPluginOptions(pluginOptions);
return args;
}
@@ -250,6 +301,14 @@ public class JadxCLIArgs {
return outDirRes;
}
public String getSingleClass() {
return singleClass;
}
public String getSingleClassOutput() {
return singleClassOutput;
}
public boolean isSkipResources() {
return skipResources;
}
@@ -266,6 +325,14 @@ public class JadxCLIArgs {
return fallbackMode;
}
public boolean isUseDx() {
return useDx;
}
public DecompilationMode getDecompilationMode() {
return decompilationMode;
}
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
@@ -306,6 +373,10 @@ public class JadxCLIArgs {
return deobfuscationMapFile;
}
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
return deobfuscationMapFileMode;
}
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
}
@@ -318,6 +389,10 @@ public class JadxCLIArgs {
return deobfuscationParseKotlinMetadata;
}
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public boolean isEscapeUnicode() {
return escapeUnicode;
}
@@ -362,6 +437,14 @@ public class JadxCLIArgs {
return commentsLevel;
}
public LogHelper.LogLevelEnum getLogLevel() {
return logLevel;
}
public Map<String, String> getPluginOptions() {
return pluginOptions;
}
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
private final String paramName;
@@ -404,9 +487,48 @@ public class JadxCLIArgs {
}
}
public static class UseKotlinMethodsForVarNamesConverter implements IStringConverter<UseKotlinMethodsForVarNames> {
@Override
public UseKotlinMethodsForVarNames convert(String value) {
try {
return UseKotlinMethodsForVarNames.valueOf(value.replace('-', '_').toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: "
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
}
}
}
public static class DeobfuscationMapFileModeConverter implements IStringConverter<DeobfuscationMapFileMode> {
@Override
public DeobfuscationMapFileMode convert(String value) {
try {
return DeobfuscationMapFileMode.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: "
+ JadxCLIArgs.enumValuesString(DeobfuscationMapFileMode.values()));
}
}
}
public static 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) {
return Stream.of(values)
.map(v -> v.name().toLowerCase(Locale.ROOT))
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
.collect(Collectors.joining(", "));
}
}
+49 -20
View File
@@ -1,5 +1,6 @@
package jadx.cli;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory;
@@ -32,33 +33,61 @@ public class LogHelper {
}
}
@Nullable("For disable log level control")
private static LogLevelEnum logLevelValue;
public static void setLogLevelFromArgs(JadxCLIArgs args) {
if (isCustomLogConfig()) {
return;
}
LogLevelEnum logLevel = args.logLevel;
if (args.quiet) {
logLevel = LogLevelEnum.QUIET;
} else if (args.verbose) {
logLevel = LogLevelEnum.DEBUG;
}
applyLogLevel(logLevel);
public static void initLogLevel(JadxCLIArgs args) {
logLevelValue = getLogLevelFromArgs(args);
}
public static void applyLogLevel(LogLevelEnum logLevel) {
logLevelValue = logLevel;
private static LogLevelEnum getLogLevelFromArgs(JadxCLIArgs args) {
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);
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
@@ -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;
}
}
@@ -39,6 +39,7 @@ public class ConvertToClsSet {
Path output = inputPaths.remove(0);
JadxPluginManager pluginManager = new JadxPluginManager();
pluginManager.load();
List<ILoadResult> loadedInputs = new ArrayList<>();
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
+4 -3
View File
@@ -1,11 +1,11 @@
plugins {
id 'java-library'
id 'jadx-library'
}
dependencies {
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'
constraints {
// 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-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 {
@@ -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
}
+92 -11
View File
@@ -4,11 +4,14 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.data.ICodeData;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache;
@@ -35,7 +38,6 @@ public class JadxArgs {
private boolean cfgOutput = false;
private boolean rawCFGOutput = false;
private boolean fallbackMode = false;
private boolean showInconsistentCode = false;
private boolean useImports = true;
@@ -54,11 +56,12 @@ public class JadxArgs {
private Predicate<String> classFilter = null;
private boolean deobfuscationOn = false;
private boolean deobfuscationForceSave = false;
private boolean useSourceNameAsClassAlias = false;
private boolean parseKotlinMetadata = false;
private File deobfuscationMapFile = null;
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
private int deobfuscationMinLength = 0;
private int deobfuscationMaxLength = Integer.MAX_VALUE;
@@ -81,10 +84,27 @@ public class JadxArgs {
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
private DecompilationMode decompilationMode = DecompilationMode.AUTO;
private ICodeData codeData;
private CommentsLevel commentsLevel = CommentsLevel.INFO;
private boolean useDxInput = false;
public enum UseKotlinMethodsForVarNames {
DISABLE, APPLY, APPLY_AND_HIDE
}
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
/**
* Don't save files (can be using for performance testing)
*/
private boolean skipFilesSave = false;
private Map<String, String> pluginOptions = new HashMap<>();
public JadxArgs() {
// use default options
}
@@ -136,7 +156,7 @@ public class JadxArgs {
}
public void setThreadsCount(int threadsCount) {
this.threadsCount = threadsCount;
this.threadsCount = Math.max(1, threadsCount); // make sure threadsCount >= 1
}
public boolean isCfgOutput() {
@@ -156,11 +176,17 @@ public class JadxArgs {
}
public boolean isFallbackMode() {
return fallbackMode;
return decompilationMode == DecompilationMode.FALLBACK;
}
/**
* Deprecated: use 'decompilation mode' property
*/
@Deprecated
public void setFallbackMode(boolean fallbackMode) {
this.fallbackMode = fallbackMode;
if (fallbackMode) {
this.decompilationMode = DecompilationMode.FALLBACK;
}
}
public boolean isShowInconsistentCode() {
@@ -251,12 +277,24 @@ public class JadxArgs {
this.deobfuscationOn = deobfuscationOn;
}
@Deprecated
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
return deobfuscationMapFileMode == DeobfuscationMapFileMode.OVERWRITE;
}
@Deprecated
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() {
@@ -391,6 +429,14 @@ public class JadxArgs {
this.outputFormat = outputFormat;
}
public DecompilationMode getDecompilationMode() {
return decompilationMode;
}
public void setDecompilationMode(DecompilationMode decompilationMode) {
this.decompilationMode = decompilationMode;
}
public ICodeCache getCodeCache() {
return codeCache;
}
@@ -423,6 +469,38 @@ public class JadxArgs {
this.commentsLevel = commentsLevel;
}
public boolean isUseDxInput() {
return useDxInput;
}
public void setUseDxInput(boolean useDxInput) {
this.useDxInput = useDxInput;
}
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public void setUseKotlinMethodsForVarNames(UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
}
public boolean isSkipFilesSave() {
return skipFilesSave;
}
public void setSkipFilesSave(boolean skipFilesSave) {
this.skipFilesSave = skipFilesSave;
}
public Map<String, String> getPluginOptions() {
return pluginOptions;
}
public void setPluginOptions(Map<String, String> pluginOptions) {
this.pluginOptions = pluginOptions;
}
@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -430,18 +508,17 @@ public class JadxArgs {
+ ", outDirSrc=" + outDirSrc
+ ", outDirRes=" + outDirRes
+ ", threadsCount=" + threadsCount
+ ", cfgOutput=" + cfgOutput
+ ", rawCFGOutput=" + rawCFGOutput
+ ", fallbackMode=" + fallbackMode
+ ", decompilationMode=" + decompilationMode
+ ", showInconsistentCode=" + showInconsistentCode
+ ", useImports=" + useImports
+ ", skipResources=" + skipResources
+ ", skipSources=" + skipSources
+ ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationMapFile=" + deobfuscationMapFile
+ ", deobfuscationForceSave=" + deobfuscationForceSave
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
+ ", deobfuscationMinLength=" + deobfuscationMinLength
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
+ ", escapeUnicode=" + escapeUnicode
@@ -454,6 +531,10 @@ public class JadxArgs {
+ ", commentsLevel=" + commentsLevel
+ ", codeCache=" + codeCache
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
+ ", 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.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.core.Jadx;
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.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
@@ -107,6 +110,7 @@ public final class JadxDecompiler implements Closeable {
reset();
JadxArgsValidator.validate(args);
LOG.info("loading ...");
loadPlugins(args);
loadInputFiles();
root = new RootNode(args);
@@ -121,12 +125,16 @@ public final class JadxDecompiler implements Closeable {
loadedInputs.clear();
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
long start = System.currentTimeMillis();
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
ILoadResult loadResult = inputPlugin.loadFiles(inputFiles);
if (loadResult != null && !loadResult.isEmpty()) {
loadedInputs.add(loadResult);
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
}
}
private void reset() {
@@ -159,6 +167,27 @@ public final class JadxDecompiler implements Closeable {
reset();
}
private void loadPlugins(JadxArgs args) {
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
pluginManager.load();
if (LOG.isDebugEnabled()) {
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
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) {
pluginManager.register(plugin);
}
@@ -272,6 +301,9 @@ public final class JadxDecompiler implements Closeable {
}
private void appendResourcesSaveTasks(List<Runnable> tasks, File outDir) {
if (args.isSkipFilesSave()) {
return;
}
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
for (ResourceFile resourceFile : getResources()) {
if (resourceFile.getType() != ResourceType.ARSC
@@ -296,7 +328,13 @@ public final class JadxDecompiler implements Closeable {
}
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(() -> {
for (JavaClass cls : decompileBatch) {
try {
@@ -413,6 +451,10 @@ public final class JadxDecompiler implements Closeable {
classesMap.put(innerCls.getClassNode(), innerCls);
loadJavaClass(innerCls);
}
for (JavaClass inlinedCls : javaClass.getInlinedClasses()) {
classesMap.put(inlinedCls.getClassNode(), inlinedCls);
loadJavaClass(inlinedCls);
}
}
/**
@@ -443,12 +485,12 @@ public final class JadxDecompiler implements Closeable {
if (parentClass.contains(AFlag.DONT_GENERATE)) {
return null;
}
if (parentClass != cls) {
JavaClass parentJavaClass = classesMap.get(parentClass);
if (parentJavaClass == null) {
getClasses();
parentJavaClass = classesMap.get(parentClass);
}
JavaClass parentJavaClass = classesMap.get(parentClass);
if (parentJavaClass == null) {
getClasses();
parentJavaClass = classesMap.get(parentClass);
}
if (parentJavaClass != null) {
loadJavaClass(parentJavaClass);
javaClass = classesMap.get(cls);
if (javaClass != null) {
@@ -472,7 +514,9 @@ public final class JadxDecompiler implements Closeable {
return null;
}
// 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) {
return null;
}
@@ -481,12 +525,26 @@ public final class JadxDecompiler implements Closeable {
if (javaMethod != null) {
return javaMethod;
}
if (mth.getParentClass().hasNotGeneratedParent()) {
if (parentClass.hasNotGeneratedParent()) {
return null;
}
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
private JavaField getJavaFieldByNode(FieldNode fld) {
JavaField javaField = fieldsMap.get(fld);
+27 -11
View File
@@ -11,6 +11,8 @@ import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
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.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
@@ -23,6 +25,7 @@ public final class JavaClass implements JavaNode {
private final JavaClass parent;
private List<JavaClass> innerClasses = Collections.emptyList();
private List<JavaClass> inlinedClasses = Collections.emptyList();
private List<JavaField> fields = Collections.emptyList();
private List<JavaMethod> methods = Collections.emptyList();
private boolean listsLoaded;
@@ -68,6 +71,10 @@ public final class JavaClass implements JavaNode {
cls.unloadCode();
}
public boolean isNoCode() {
return cls.contains(AFlag.DONT_GENERATE);
}
public synchronized String getSmali() {
return cls.getDisassembledCode();
}
@@ -100,13 +107,23 @@ public final class JavaClass implements JavaNode {
}
this.innerClasses = Collections.unmodifiableList(list);
}
int inlinedClsCount = cls.getInlinedClasses().size();
if (inlinedClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
for (ClassNode inner : cls.getInlinedClasses()) {
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists();
list.add(javaClass);
}
this.inlinedClasses = Collections.unmodifiableList(list);
}
int fieldsCount = cls.getFields().size();
if (fieldsCount != 0) {
List<JavaField> flds = new ArrayList<>(fieldsCount);
for (FieldNode f : cls.getFields()) {
if (!f.contains(AFlag.DONT_GENERATE)) {
JavaField javaField = new JavaField(f, this);
JavaField javaField = new JavaField(this, f);
flds.add(javaField);
}
}
@@ -226,7 +243,7 @@ public final class JavaClass implements JavaNode {
@Override
public JavaClass getTopParentClass() {
if (cls.contains(AFlag.ANONYMOUS_CLASS)) {
if (cls.contains(AType.ANONYMOUS_CLASS)) {
// moved to usage class
return getParentForAnonymousClass();
}
@@ -234,15 +251,9 @@ public final class JavaClass implements JavaNode {
}
private JavaClass getParentForAnonymousClass() {
List<JavaNode> useIn = getUseIn();
if (useIn.isEmpty()) {
return this;
}
JavaNode useNode = useIn.get(0);
if (useNode.equals(this)) {
return this;
}
return useNode.getTopParentClass();
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
ClassNode topParentClass = attr.getOuterCls().getTopParentClass();
return getRootDecompiler().convertClassNode(topParentClass);
}
public AccessInfo getAccessInfo() {
@@ -254,6 +265,11 @@ public final class JavaClass implements JavaNode {
return innerClasses;
}
public List<JavaClass> getInlinedClasses() {
loadLists();
return inlinedClasses;
}
public List<JavaField> getFields() {
loadLists();
return fields;
@@ -13,7 +13,7 @@ public final class JavaField implements JavaNode {
private final FieldNode field;
private final JavaClass parent;
JavaField(FieldNode f, JavaClass cls) {
JavaField(JavaClass cls, FieldNode f) {
this.field = f;
this.parent = cls;
}
@@ -28,6 +28,10 @@ public final class JavaField implements JavaNode {
return parent.getFullName() + '.' + getName();
}
public String getRawName() {
return field.getName();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
@@ -2,9 +2,12 @@ package jadx.api;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.jetbrains.annotations.ApiStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
@@ -14,6 +17,7 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils;
public final class JavaMethod implements JavaNode {
private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class);
private final MethodNode mth;
private final JavaClass parent;
@@ -73,7 +77,14 @@ public final class JavaMethod implements JavaNode {
}
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
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());
}
@@ -37,6 +37,10 @@ public class ResourceFile {
private ZipRef zipRef;
private String deobfName;
public static ResourceFile createResourceFile(JadxDecompiler decompiler, File file, ResourceType type) {
return new ResourceFile(decompiler, file.getAbsolutePath(), type);
}
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) {
return null;
@@ -41,7 +41,7 @@ public enum ResourceType {
}
public static ResourceType getFileType(String fileName) {
if (fileName.matches("[^/]+/resources.pb")) {
if (fileName.endsWith("/resources.pb")) {
return ARSC;
}
int dot = fileName.lastIndexOf('.');
@@ -126,8 +126,9 @@ public final class ResourcesLoader {
if (name.endsWith(".9.png")) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
decoder.decode(inputStream, os);
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
if (decoder.decode(inputStream, os)) {
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
}
} catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
}
@@ -145,16 +146,8 @@ public final class ResourcesLoader {
return null;
});
} else {
addResourceFile(list, file);
}
}
private void addResourceFile(List<ResourceFile> list, File file) {
String name = file.getAbsolutePath();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
list.add(rf);
ResourceType type = ResourceType.getFileType(file.getAbsolutePath());
list.add(ResourceFile.createResourceFile(jadxRef, file, type));
}
}
@@ -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;
}
}
+89 -18
View File
@@ -12,6 +12,8 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.visitors.AnonymousClassVisitor;
import jadx.core.dex.visitors.AttachCommentsVisitor;
import jadx.core.dex.visitors.AttachMethodDetails;
import jadx.core.dex.visitors.AttachTryCatchVisitor;
@@ -31,12 +33,14 @@ import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.InlineMethods;
import jadx.core.dex.visitors.MarkMethodsForInline;
import jadx.core.dex.visitors.MethodInvokeVisitor;
import jadx.core.dex.visitors.MethodVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.MoveInlineVisitor;
import jadx.core.dex.visitors.OverrideMethodVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ProcessAnonymous;
import jadx.core.dex.visitors.ProcessInstructionsVisitor;
import jadx.core.dex.visitors.ProcessMethodsForInline;
import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.ShadowFieldVisitor;
import jadx.core.dex.visitors.SignatureProcessor;
@@ -46,6 +50,7 @@ import jadx.core.dex.visitors.blocks.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
@@ -57,8 +62,10 @@ import jadx.core.dex.visitors.rename.CodeRenameVisitor;
import jadx.core.dex.visitors.rename.RenameVisitor;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
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.usage.UsageInfoVisitor;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
@@ -66,21 +73,20 @@ public class Jadx {
private Jadx() {
}
static {
if (Consts.DEBUG) {
LOG.info("debug enabled");
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
switch (args.getDecompilationMode()) {
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() {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new SignatureProcessor());
@@ -88,15 +94,12 @@ public class Jadx {
passes.add(new RenameVisitor());
passes.add(new UsageInfoVisitor());
passes.add(new ProcessAnonymous());
passes.add(new ProcessMethodsForInline());
return passes;
}
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
if (args.isFallbackMode()) {
return getFallbackPassesList();
}
public static List<IDexTreeVisitor> getRegionsModePasses(JadxArgs args) {
List<IDexTreeVisitor> passes = new ArrayList<>();
// instructions IR
passes.add(new CheckCode());
if (args.isDebugInfo()) {
@@ -128,6 +131,10 @@ public class Jadx {
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new FinishTypeInference());
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
passes.add(new ProcessKotlinInternals());
}
passes.add(new CodeRenameVisitor());
if (args.isInlineMethods()) {
passes.add(new InlineMethods());
@@ -135,6 +142,7 @@ public class Jadx {
passes.add(new GenericTypesVisitor());
passes.add(new ShadowFieldVisitor());
passes.add(new DeboxingVisitor());
passes.add(new AnonymousClassVisitor());
passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode());
@@ -170,7 +178,69 @@ public class Jadx {
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() {
if (version != null) {
return version;
}
try {
ClassLoader classLoader = Jadx.class.getClassLoader();
if (classLoader != null) {
@@ -180,6 +250,7 @@ public class Jadx {
Manifest manifest = new Manifest(is);
String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null) {
version = ver;
return ver;
}
}
@@ -188,6 +259,6 @@ public class Jadx {
} catch (Exception e) {
LOG.error("Can't get manifest file", e);
}
return "dev";
return VERSION_DEV;
}
}
@@ -1,18 +1,19 @@
package jadx.core;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.LoadStage;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -23,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_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
private static ICodeInfo process(ClassNode cls, boolean codegen) {
private ICodeInfo process(ClassNode cls, boolean codegen) {
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
// nothing to do
return null;
@@ -39,17 +44,17 @@ public final class ProcessClass {
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
cls.remove(AFlag.CLASS_DEEP_RELOAD);
cls.deepUnload();
cls.root().runPreDecompileStageForClass(cls);
cls.add(AFlag.CLASS_UNLOADED);
}
if (cls.contains(AFlag.CLASS_UNLOADED)) {
cls.remove(AFlag.CLASS_UNLOADED);
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 (cls.getState() == GENERATED_AND_UNLOADED) {
// allow to run code generation again
cls.setState(NOT_LOADED);
}
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
@@ -63,7 +68,7 @@ public final class ProcessClass {
}
if (cls.getState() == LOADED) {
cls.setState(PROCESS_STARTED);
for (IDexTreeVisitor visitor : cls.root().getPasses()) {
for (IDexTreeVisitor visitor : passes) {
DepthTraversal.visit(visitor, cls);
}
cls.setState(PROCESS_COMPLETE);
@@ -88,27 +93,19 @@ public final class ProcessClass {
}
@NotNull
public static ICodeInfo generateCode(ClassNode cls) {
public ICodeInfo generateCode(ClassNode cls) {
ClassNode topParentClass = cls.getTopParentClass();
if (topParentClass != cls) {
return generateCode(topParentClass);
}
try {
Set<ClassNode> useIn = new HashSet<>(cls.getUseIn());
List<ClassNode> usedInDeps = new ArrayList<>();
for (ClassNode depCls : cls.getDependencies()) {
if (useIn.contains(depCls)) {
// postpone to resolve cross dependencies
usedInDeps.add(depCls);
} else {
process(depCls, false);
}
process(depCls, false);
}
if (!usedInDeps.isEmpty()) {
// process current class before its usage
if (!cls.getCodegenDeps().isEmpty()) {
process(cls, false);
for (ClassNode depCls : usedInDeps) {
process(depCls, false);
for (ClassNode codegenDep : cls.getCodegenDeps()) {
process(codegenDep, false);
}
}
ICodeInfo code = process(cls, true);
@@ -120,4 +117,19 @@ public final class ProcessClass {
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.attributes.JadxAttrType;
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.MethodParamsAttr;
import jadx.core.Consts;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.info.FieldInfo;
@@ -48,7 +48,7 @@ public class AnnotationGen {
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();
if (n >= paramList.size()) {
return;
@@ -285,7 +285,7 @@ public class ClassGen {
private boolean isInnerClassesPresents() {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
if (!innerCls.contains(AType.ANONYMOUS_CLASS)) {
return true;
}
}
@@ -356,7 +356,7 @@ public class ClassGen {
badCode = false;
}
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);
} else {
mthGen = new MethodGen(this, mth);
@@ -459,7 +459,7 @@ public class ClassGen {
}
if (f.getCls() != null) {
code.add(' ');
new ClassGen(f.getCls(), this).addClassBody(code);
new ClassGen(f.getCls(), this).addClassBody(code, true);
}
if (it.hasNext()) {
code.add(',');
@@ -526,12 +526,42 @@ public class ClassGen {
if (outerType != null) {
useClass(code, outerType);
code.add('.');
// import not needed, force use short name
useClassShortName(code, type.getObject());
addInnerType(code, type);
return;
}
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();
if (generics != null) {
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) {
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
@@ -590,6 +611,9 @@ public class ClassGen {
return fullName;
}
String shortName = extClsInfo.getAliasShortName();
if (useCls.equals(extClsInfo)) {
return shortName;
}
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
return shortName;
}
@@ -599,6 +623,9 @@ public class ClassGen {
if (extClsInfo.isInner()) {
return expandInnerClassName(useCls, extClsInfo);
}
if (searchCollision(cls.root(), useCls, extClsInfo)) {
return fullName;
}
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
return shortName;
}
@@ -606,9 +633,6 @@ public class ClassGen {
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
return shortName;
}
if (searchCollision(cls.root(), useCls, extClsInfo)) {
return fullName;
}
// ignore classes from default package
if (extClsInfo.isDefaultPackage()) {
return shortName;
@@ -42,7 +42,7 @@ public class ConditionGen extends InsnGen {
super(insnGen.mgen, insnGen.fallback);
}
void add(ICodeWriter code, IfCondition condition) throws CodegenException {
public void add(ICodeWriter code, IfCondition condition) throws CodegenException {
add(code, new CondStack(), condition);
}
@@ -15,12 +15,12 @@ import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.data.annotations.VarRef;
import jadx.api.plugins.input.data.MethodHandleType;
import jadx.core.deobf.NameMapper;
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.GenericInfoAttr;
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.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
@@ -51,6 +51,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
@@ -112,7 +113,7 @@ public class InsnGen {
}
code.add(mgen.getNameGen().useArg(reg));
} else if (arg.isLiteral()) {
code.add(lit((LiteralArg) arg));
addLiteralArg(code, (LiteralArg) arg, flags);
} else if (arg.isInsnWrap()) {
addWrappedArg(code, (InsnWrapArg) arg, flags);
} else if (arg.isNamed()) {
@@ -122,6 +123,15 @@ public class InsnGen {
}
}
private void addLiteralArg(ICodeWriter code, LiteralArg litArg, Set<Flags> flags) {
String literalStr = lit(litArg);
if (!flags.contains(Flags.BODY_ONLY_NOWRAP) && literalStr.startsWith("-")) {
code.add('(').add(literalStr).add(')');
} else {
code.add(literalStr);
}
}
private void addWrappedArg(ICodeWriter code, InsnWrapArg arg, Set<Flags> flags) throws CodegenException {
InsnNode wrapInsn = arg.getWrapInsn();
if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
@@ -164,7 +174,7 @@ public class InsnGen {
private void instanceField(ICodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
ClassNode pCls = mth.getParentClass();
FieldNode fieldNode = pCls.root().deepResolveField(field);
FieldNode fieldNode = pCls.root().resolveField(field);
if (fieldNode != null) {
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
if (replace != null) {
@@ -202,7 +212,7 @@ public class InsnGen {
}
code.add('.');
}
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field);
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
@@ -509,7 +519,7 @@ public class InsnGen {
code.add(' ');
code.add(ifInsn.getOp().getSymbol()).add(' ');
addArg(code, insn.getArg(1));
code.add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget()));
code.add(") goto ").add(MethodGen.getLabelName(ifInsn));
break;
case GOTO:
@@ -530,13 +540,24 @@ public class InsnGen {
code.add(") {");
code.incIndent();
int[] keys = sw.getKeys();
int[] targets = sw.getTargets();
for (int i = 0; i < keys.length; i++) {
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
code.add(MethodGen.getLabelName(targets[i])).add(';');
int size = keys.length;
BlockNode[] targetBlocks = sw.getTargetBlocks();
if (targetBlocks != null) {
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.startLine('}');
break;
@@ -674,19 +695,27 @@ public class InsnGen {
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
}
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()) {
code.attachAnnotation(callMth);
code.attachAnnotation(refMth);
code.add("super");
} else if (insn.isThis()) {
code.attachAnnotation(callMth);
code.attachAnnotation(refMth);
code.add("this");
} else {
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)
code.attachAnnotation(mth.root().resolveClass(insn.getCallMth().getDeclClass()));
} else {
code.attachAnnotation(callMth);
code.attachAnnotation(refMth);
}
mgen.getClassGen().addClsName(code, insn.getClassType());
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
@@ -711,20 +740,13 @@ public class InsnGen {
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
if (this.mth.getParentClass() == cls) {
cls.remove(AFlag.ANONYMOUS_CLASS);
cls.remove(AType.ANONYMOUS_CLASS);
cls.remove(AFlag.DONT_GENERATE);
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
throw new CodegenException("Anonymous inner class unlimited recursion detected."
+ " Convert class to inner: " + cls.getClassInfo().getFullName());
}
cls.add(AFlag.DONT_GENERATE);
ArgType parent;
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
} else {
parent = cls.getSuperClass();
}
ArgType parent = cls.get(AType.ANONYMOUS_CLASS).getBaseType();
// hide empty anonymous constructors
for (MethodNode ctor : cls.getMethods()) {
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
@@ -732,14 +754,22 @@ public class InsnGen {
ctor.add(AFlag.DONT_GENERATE);
}
}
code.add("new ");
if (parent == null) {
code.add("Object");
} else {
useClass(code, parent);
}
useClass(code, parent);
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
if (callMth != null) {
// copy var names
List<RegisterArg> mthArgs = callMth.getArgRegs();
int argsCount = Math.min(insn.getArgsCount(), mthArgs.size());
for (int i = 0; i < argsCount; i++) {
InsnArg arg = insn.getArg(i);
if (arg.isRegister()) {
RegisterArg mthArg = mthArgs.get(i);
RegisterArg insnArg = (RegisterArg) arg;
mthArg.getSVar().setCodeVar(insnArg.getSVar().getCodeVar());
}
}
}
generateMethodArguments(code, insn, 0, callMth);
code.add(' ');
@@ -755,7 +785,7 @@ public class InsnGen {
return;
}
MethodInfo callMth = insn.getCallMth();
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
MethodNode callMthNode = mth.root().resolveMethod(callMth);
int k = 0;
switch (type) {
@@ -763,8 +793,7 @@ public class InsnGen {
case VIRTUAL:
case INTERFACE:
InsnArg arg = insn.getArg(0);
// FIXME: add 'this' for equals methods in scope
if (!arg.isThis()) {
if (needInvokeArg(arg)) {
addArgDot(code, arg);
}
k++;
@@ -799,6 +828,20 @@ public class InsnGen {
generateMethodArguments(code, insn, k, callMthNode);
}
// FIXME: add 'this' for equals methods in scope
private boolean needInvokeArg(InsnArg arg) {
if (arg.isAnyThis()) {
if (arg.isThis()) {
return false;
}
ClassNode clsNode = mth.root().resolveClass(arg.getType());
if (clsNode != null && clsNode.contains(AFlag.DONT_GENERATE)) {
return false;
}
}
return true;
}
private void makeInvokeLambda(ICodeWriter code, InvokeCustomNode customNode) throws CodegenException {
if (customNode.isUseRef()) {
makeRefLambda(code, customNode);
@@ -1016,7 +1059,6 @@ public class InsnGen {
} else {
condGen.wrap(code, insn.getCondition());
code.add(" ? ");
addCastIfNeeded(code, first, second);
addArg(code, first, false);
code.add(" : ");
addArg(code, second, false);
@@ -1026,33 +1068,6 @@ public class InsnGen {
}
}
private void addCastIfNeeded(ICodeWriter code, InsnArg first, InsnArg second) {
if (first.isLiteral() && second.isLiteral()) {
if (first.getType() == ArgType.BYTE) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (lit1 != Byte.MAX_VALUE && lit1 != Byte.MIN_VALUE
&& lit2 != Byte.MAX_VALUE && lit2 != Byte.MIN_VALUE) {
code.add("(byte) ");
}
} else if (first.getType() == ArgType.SHORT) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (lit1 != Short.MAX_VALUE && lit1 != Short.MIN_VALUE
&& lit2 != Short.MAX_VALUE && lit2 != Short.MIN_VALUE) {
code.add("(short) ");
}
} else if (first.getType() == ArgType.CHAR) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (!NameMapper.isPrintableChar((char) (lit1))
&& !NameMapper.isPrintableChar((char) (lit2))) {
code.add("(char) ");
}
}
}
}
private void makeArith(ArithNode insn, ICodeWriter code, Set<Flags> state) throws CodegenException {
if (insn.contains(AFlag.ARITH_ONEARG)) {
makeArithOneArg(insn, code);
@@ -6,17 +6,19 @@ import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedValue;
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.Jadx;
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.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxOverflowException;
@@ -111,7 +114,18 @@ public class MethodGen {
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
}
if (mth.contains(AFlag.INCONSISTENT_CODE) && mth.checkCommentsLevel(CommentsLevel.ERROR)) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump */");
code.startLine("/*");
code.incIndent();
code.startLine("Code decompiled incorrectly, please refer to instructions dump.");
if (!mth.root().getArgs().isShowInconsistentCode()) {
if (code.isMetadataSupported()) {
code.startLine("To view partially-correct code enable 'Show inconsistent code' option in preferences");
} else {
code.startLine("To view partially-correct add '--show-bad-code' argument");
}
}
code.decIndent();
code.startLine("*/");
}
code.startLineWithNum(mth.getSourceLine());
@@ -169,7 +183,7 @@ public class MethodGen {
if (overrideAttr == null) {
return;
}
if (!overrideAttr.isAtBaseMth()) {
if (!overrideAttr.getBaseMethods().contains(mth)) {
code.startLine("@Override");
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
code.add(" // ");
@@ -184,7 +198,7 @@ public class MethodGen {
}
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;
Iterator<RegisterArg> it = args.iterator();
while (it.hasNext()) {
@@ -227,10 +241,11 @@ public class MethodGen {
classGen.useType(code, argType);
}
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.add(nameGen.assignArg(var));
code.add(varName);
i++;
if (it.hasNext()) {
@@ -240,12 +255,28 @@ public class MethodGen {
}
public void addInstructions(ICodeWriter code) throws CodegenException {
if (mth.root().getArgs().isFallbackMode()) {
addFallbackMethodCode(code, FALLBACK_MODE);
} else if (classGen.isFallbackMode()) {
dumpInstructions(code);
} else {
addRegionInsns(code);
JadxArgs args = mth.root().getArgs();
switch (args.getDecompilationMode()) {
case AUTO:
if (classGen.isFallbackMode()) {
// TODO: try simple mode first
dumpInstructions(code);
} else {
addRegionInsns(code);
}
break;
case RESTRUCTURE:
addRegionInsns(code);
break;
case SIMPLE:
addSimpleMethodCode(code);
break;
case FALLBACK:
addFallbackMethodCode(code, FALLBACK_MODE);
break;
}
}
@@ -267,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) {
if (mth.checkCommentsLevel(CommentsLevel.ERROR)) {
code.startLine("/*");
@@ -314,7 +398,14 @@ public class MethodGen {
.filter(insn -> insn.getType() != InsnType.NOP)
.count();
if (insnCountEstimate > 100) {
code.startLine("// Method dump skipped, instructions count: " + insnArr.length);
code.incIndent();
code.startLine("Method dump skipped, instructions count: " + insnArr.length);
if (code.isMetadataSupported()) {
code.startLine("To view this dump change 'Code comments level' option to 'DEBUG'");
} else {
code.startLine("To view this dump add '--comments-level debug' option");
}
code.decIndent();
return;
}
}
@@ -334,60 +425,81 @@ public class MethodGen {
public static void addFallbackInsns(ICodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
int startIndent = code.getIndent();
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
MethodGen methodGen = getFallbackMethodGen(mth);
InsnGen insnGen = new InsnGen(methodGen, true);
InsnNode prevInsn = null;
for (InsnNode insn : insnArr) {
if (insn == null) {
continue;
}
if (insn.contains(AType.JADX_ERROR)) {
for (JadxError error : insn.getAll(AType.JADX_ERROR)) {
code.startLine("// ").add(error.getError());
}
continue;
methodGen.dumpInsn(code, insnGen, option, startIndent, prevInsn, insn);
prevInsn = insn;
}
}
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.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();
}
if (insn.getType() == InsnType.NOP) {
continue;
}
try {
boolean escapeComment = isCommentEscapeNeeded(insn, option);
if (escapeComment) {
code.decIndent();
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();
}
addCatchComment(code, insn, true);
CodeGenUtils.addCodeComments(code, mth, insn);
} catch (Exception e) {
LOG.debug("Error generate fallback instruction: ", e.getCause());
code.setIndent(startIndent);
code.startLine("// error: " + insn);
}
return false;
}
CatchAttr catchAttr = insn.get(AType.EXC_CATCH);
if (catchAttr != null) {
code.add(" // " + catchAttr);
}
CodeGenUtils.addCodeComments(code, mth, insn);
} catch (Exception e) {
LOG.debug("Error generate fallback instruction: ", e.getCause());
code.setIndent(startIndent);
code.startLine("// error: " + insn);
private void addCatchComment(ICodeWriter code, InsnNode insn, boolean raw) {
CatchAttr catchAttr = insn.get(AType.EXC_CATCH);
if (catchAttr == null) {
return;
}
code.add(" // Catch:");
for (ExceptionHandler handler : catchAttr.getHandlers()) {
code.add(' ');
classGen.useClass(code, handler.getArgType());
code.add(" -> ");
if (raw) {
code.add(getLabelName(handler.getHandlerOffset()));
} else {
code.add(getLabelName(handler.getHandlerBlock()));
}
prevInsn = insn;
}
}
@@ -430,7 +542,22 @@ public class MethodGen {
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) {
return "L_" + InsnUtils.formatOffset(offset);
if (offset < 0) {
return String.format("LB_%x", -offset);
}
return String.format("L%x", offset);
}
}
@@ -162,8 +162,7 @@ public class NameGen {
InsnNode assignInsn = assignArg.getParentInsn();
if (assignInsn != null) {
String name = makeNameFromInsn(assignInsn);
if (name != null && !NameMapper.isReserved(name)) {
assignArg.setName(name);
if (name != null && NameMapper.isValidAndPrintable(name)) {
return name;
}
}
@@ -202,7 +201,11 @@ public class NameGen {
return vName;
}
if (shortName != null) {
return StringUtils.escape(shortName.toLowerCase());
String lower = StringUtils.escape(shortName.toLowerCase());
if (shortName.equals(lower)) {
return lower + "Var";
}
return lower;
}
}
return StringUtils.escape(type.toString());
@@ -265,13 +268,17 @@ public class NameGen {
private String makeNameFromInvoke(MethodInfo callMth) {
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")) {
return fromName(name.substring(3));
}
if ("iterator".equals(name)) {
return "it";
}
ArgType declType = callMth.getDeclClass().getType();
if ("toString".equals(name)) {
return makeNameForType(declType);
}
@@ -161,7 +161,7 @@ public class RegionGen extends InsnGen {
}
public void makeLoop(LoopRegion region, ICodeWriter code) throws CodegenException {
code.startLineWithNum(region.getConditionSourceLine());
code.startLineWithNum(region.getSourceLine());
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
if (labelAttr != null) {
code.add(mgen.getNameGen().getLoopLabel(labelAttr)).add(": ");
@@ -213,7 +213,7 @@ public class RegionGen extends InsnGen {
code.add("do {");
CodeGenUtils.addCodeComments(code, mth, condInsn);
makeRegionIndent(code, region.getBody());
code.startLineWithNum(region.getConditionSourceLine());
code.startLineWithNum(region.getSourceLine());
code.add("} while (");
conditionGen.add(code, condition);
code.add(");");
@@ -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.Map;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
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> mthPresetMap = new HashMap<>();
@Nullable
public static DeobfPresets build(RootNode root) {
Path deobfMapPath = getPathDeobfMapPath(root);
if (deobfMapPath == null) {
return null;
if (root.getArgs().getDeobfuscationMapFileMode() != DeobfuscationMapFileMode.IGNORE) {
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
}
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
return new DeobfPresets(deobfMapPath);
}
@Nullable
private static Path getPathDeobfMapPath(RootNode root) {
JadxArgs jadxArgs = root.getArgs();
File deobfMapFile = jadxArgs.getDeobfuscationMapFile();
if (deobfMapFile != null) {
return deobfMapFile.toPath();
}
List<File> inputFiles = jadxArgs.getInputFiles();
if (inputFiles.isEmpty()) {
return null;
}
Path inputFilePath = inputFiles.get(0).getAbsoluteFile().toPath();
Path inputFilePath = jadxArgs.getInputFiles().get(0).toPath().toAbsolutePath();
String baseName = FileUtils.getPathBaseName(inputFilePath);
return inputFilePath.getParent().resolve(baseName + ".jobf");
}
@@ -70,9 +63,9 @@ public class DeobfPresets {
/**
* Loads deobfuscator presets
*/
public void load() {
public boolean load() {
if (!Files.exists(deobfMapFile)) {
return;
return false;
}
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
try {
@@ -106,8 +99,10 @@ public class DeobfPresets {
break;
}
}
return true;
} catch (Exception 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,
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
if (LOG.isDebugEnabled()) {
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
}
LOG.info("Deobfuscation map file saved as: {}", deobfMapFile);
}
public String getForCls(ClassInfo cls) {
@@ -16,6 +16,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.core.dex.attributes.AFlag;
@@ -76,22 +77,25 @@ public class Deobfuscator {
}
public void execute() {
if (!args.isDeobfuscationForceSave()) {
deobfPresets.load();
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
if (args.getDeobfuscationMapFileMode().shouldRead()) {
if (deobfPresets.load()) {
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
}
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
initIndexes();
}
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
initIndexes();
}
process();
}
public void savePresets() {
DeobfuscationMapFileMode mode = args.getDeobfuscationMapFileMode();
if (!mode.shouldWrite()) {
return;
}
Path deobfMapFile = deobfPresets.getDeobfMapFile();
if (Files.exists(deobfMapFile) && !args.isDeobfuscationForceSave()) {
LOG.info("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
deobfMapFile.toAbsolutePath());
if (mode == DeobfuscationMapFileMode.READ_OR_SAVE && Files.exists(deobfMapFile)) {
return;
}
try {
@@ -112,16 +116,25 @@ public class Deobfuscator {
deobfPresets.getPkgPresetMap().put(p.getName(), p.getAlias());
}
}
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
if (deobfClsInfo.getAlias() != null) {
deobfPresets.getClsPresetMap().put(deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias());
for (ClassNode cls : root.getClasses()) {
ClassInfo classInfo = cls.getClassInfo();
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 pkgName = null;
if (this.parseKotlinMetadata) {
ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls);
ClsAliasPair kotlinCls = KotlinMetadataUtils.getClassAlias(cls);
if (kotlinCls != null) {
alias = prepareNameFull(kotlinCls.getShortName(), "C");
pkgName = kotlinCls.getPackage();
alias = kotlinCls.getName();
pkgName = kotlinCls.getPkg();
}
}
if (alias == null && this.useSourceNameAsAlias) {
@@ -572,6 +585,7 @@ public class Deobfuscator {
if (!pkg.hasAlias()) {
String pkgName = pkg.getName();
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.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
@@ -592,20 +606,6 @@ public class Deobfuscator {
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) {
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
}
@@ -143,19 +143,15 @@ public class NameMapper {
/**
* Return modified string with removed:
* <p>
* <ul>
* <li>not printable chars (including unicode)
* <li>chars not valid for java identifier part
* </ul>
* <p>
* Note: this 'middle' method must be used with prefixed string:
* <p>
* <ul>
* <li>can leave invalid chars for java identifier start (i.e numbers)
* <li>result not checked for reserved words
* </ul>
* <p>
*/
public static String removeInvalidCharsMiddle(String name) {
if (isValidIdentifier(name) && isAllCharsPrintable(name)) {
@@ -0,0 +1,38 @@
package jadx.core.deobf;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Provides a list of all top level domains with 3 characters and less,
* so we can exclude them from deobfuscation.
*/
public class TldHelper {
private static final Set<String> TLD_SET = loadTldFile();
private static Set<String> loadTldFile() {
Set<String> tldNames = new HashSet<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(Deobfuscator.class.getResourceAsStream("tld_3.txt")))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (!line.startsWith("#") && !line.isEmpty()) {
tldNames.add(line);
}
}
return tldNames;
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load top level domain list tld_3.txt", e);
}
}
public static boolean contains(String name) {
return TLD_SET.contains(name);
}
}
@@ -33,8 +33,8 @@ public enum AFlag {
SKIP_FIRST_ARG,
SKIP_ARG, // skip argument in invoke call
NO_SKIP_ARGS,
ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS,
THIS,
SUPER,
@@ -76,8 +76,13 @@ public enum AFlag {
INCONSISTENT_CODE, // warning about incorrect decompilation
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
REQUEST_CODE_SHRINK,
RERUN_SSA_TRANSFORM,
METHOD_CANDIDATE_FOR_INLINE,
DISABLE_BLOCKS_LOCK,
// Class processing flags
RESTART_CODEGEN, // codegen must be executed again
RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage
@@ -2,6 +2,7 @@ package jadx.core.dex.attributes;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
@@ -10,19 +11,23 @@ import jadx.core.dex.attributes.nodes.EnumMapAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
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.JadxError;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
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.nodes.IMethodDetails;
import jadx.core.dex.trycatch.CatchAttr;
@@ -51,6 +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<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
public static final AType<AnonymousClassAttr> ANONYMOUS_CLASS = new AType<>();
public static final AType<InlinedAttr> INLINED = new AType<>();
// field
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
@@ -59,6 +66,8 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
// method
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = 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<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
@@ -72,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<AttrList<LoopInfo>> LOOP = 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<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 + '}';
}
}
@@ -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;
}
}
@@ -11,6 +11,7 @@ import jadx.api.CommentsLevel;
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.utils.Utils;
public class JadxCommentsAttr implements IJadxAttribute {
@@ -44,4 +45,12 @@ public class JadxCommentsAttr implements IJadxAttribute {
public IJadxAttrType<JadxCommentsAttr> getAttrType() {
return AType.JADX_COMMENTS;
}
@Override
public String toString() {
return "JadxCommentsAttr{\n "
+ Utils.listToString(comments.entrySet(), "\n ",
e -> e.getKey() + ": \n -> " + Utils.listToString(e.getValue(), "\n -> "))
+ '}';
}
}
@@ -50,11 +50,7 @@ public class JadxError implements Comparable<JadxError> {
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("JadxError: ");
if (error != null) {
str.append(error);
str.append(' ');
}
str.append("JadxError: ").append(error).append(' ');
if (cause != null) {
str.append(cause.getClass());
str.append(':');
@@ -1,7 +1,6 @@
package jadx.core.dex.attributes.nodes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -20,10 +19,10 @@ public class LoopInfo {
private int id;
private LoopInfo parentLoop;
public LoopInfo(BlockNode start, BlockNode end) {
public LoopInfo(BlockNode start, BlockNode end, Set<BlockNode> loopBlocks) {
this.start = start;
this.end = end;
this.loopBlocks = Collections.unmodifiableSet(BlockUtils.getAllPathsBlocks(start, end));
this.loopBlocks = loopBlocks;
}
public BlockNode getStart() {
@@ -0,0 +1,28 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.MethodNode;
public class MethodBridgeAttr extends PinnedAttribute {
private final MethodNode bridgeMth;
public MethodBridgeAttr(MethodNode bridgeMth) {
this.bridgeMth = bridgeMth;
}
public MethodNode getBridgeMth() {
return bridgeMth;
}
@Override
public AType<MethodBridgeAttr> getAttrType() {
return AType.BRIDGED_BY;
}
@Override
public String toString() {
return "BRIDGED_BY: " + bridgeMth;
}
}
@@ -1,6 +1,7 @@
package jadx.core.dex.attributes.nodes;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
@@ -20,27 +21,26 @@ public class MethodOverrideAttr extends PinnedAttribute {
*/
private SortedSet<MethodNode> relatedMthNodes;
public MethodOverrideAttr(List<IMethodDetails> overrideList, SortedSet<MethodNode> relatedMthNodes) {
private Set<IMethodDetails> baseMethods;
public MethodOverrideAttr(List<IMethodDetails> overrideList, SortedSet<MethodNode> relatedMthNodes, Set<IMethodDetails> baseMethods) {
this.overrideList = overrideList;
this.relatedMthNodes = relatedMthNodes;
}
public boolean isAtBaseMth() {
return overrideList.isEmpty();
this.baseMethods = baseMethods;
}
public List<IMethodDetails> getOverrideList() {
return overrideList;
}
public void setOverrideList(List<IMethodDetails> overrideList) {
this.overrideList = overrideList;
}
public SortedSet<MethodNode> getRelatedMthNodes() {
return relatedMthNodes;
}
public Set<IMethodDetails> getBaseMethods() {
return baseMethods;
}
public void setRelatedMthNodes(SortedSet<MethodNode> relatedMthNodes) {
this.relatedMthNodes = relatedMthNodes;
}
@@ -52,6 +52,6 @@ public class MethodOverrideAttr extends PinnedAttribute {
@Override
public String toString() {
return "METHOD_OVERRIDE: " + overrideList;
return "METHOD_OVERRIDE: " + getBaseMethods();
}
}
@@ -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 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;
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) {
this.parentClass = null;
splitAndApplyNames(root, type, false);
this.parentClass = null;
}
public void convertToInner(ClassNode parent) {
this.parentClass = parent.getClassInfo();
splitAndApplyNames(parent.root(), type, true);
this.parentClass = parent.getClassInfo();
}
public void updateNames(RootNode root) {
@@ -176,6 +176,9 @@ public class ConstStorage {
@Nullable
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
if (!replaceEnabled) {
return null;
}
PrimitiveType type = arg.getType().getPrimitiveType();
if (type == null) {
return null;
@@ -5,6 +5,7 @@ import java.util.List;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
@@ -70,6 +71,15 @@ public class IfNode extends GotoNode {
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) {
this.op = op;
setArg(0, arg1);
@@ -389,11 +389,11 @@ public class InsnDecoder {
return arrLenInsn;
case AGET:
return arrayGet(insn, ArgType.INT_FLOAT);
return arrayGet(insn, ArgType.INT_FLOAT, ArgType.NARROW_NUMBERS_NO_BOOL);
case AGET_BOOLEAN:
return arrayGet(insn, ArgType.BOOLEAN);
case AGET_BYTE:
return arrayGet(insn, ArgType.BYTE);
return arrayGet(insn, ArgType.BYTE, ArgType.NARROW_INTEGRAL);
case AGET_BYTE_BOOLEAN:
return arrayGet(insn, ArgType.BYTE_BOOLEAN);
case AGET_CHAR:
@@ -406,7 +406,7 @@ public class InsnDecoder {
return arrayGet(insn, ArgType.UNKNOWN_OBJECT);
case APUT:
return arrayPut(insn, ArgType.INT_FLOAT);
return arrayPut(insn, ArgType.INT_FLOAT, ArgType.NARROW_NUMBERS_NO_BOOL);
case APUT_BOOLEAN:
return arrayPut(insn, ArgType.BOOLEAN);
case APUT_BYTE:
@@ -437,7 +437,9 @@ public class InsnDecoder {
case INVOKE_VIRTUAL:
return invoke(insn, InvokeType.VIRTUAL, false);
case INVOKE_CUSTOM:
return invoke(insn, InvokeType.CUSTOM, false);
return invokeCustom(insn, false);
case INVOKE_SPECIAL:
return invokeSpecial(insn);
case INVOKE_DIRECT_RANGE:
return invoke(insn, InvokeType.DIRECT, true);
@@ -448,7 +450,7 @@ public class InsnDecoder {
case INVOKE_VIRTUAL_RANGE:
return invoke(insn, InvokeType.VIRTUAL, true);
case INVOKE_CUSTOM_RANGE:
return invoke(insn, InvokeType.CUSTOM, true);
return invokeCustom(insn, true);
case NEW_INSTANCE:
ArgType clsType = ArgType.parse(insn.getIndexAsType());
@@ -506,7 +508,17 @@ public class InsnDecoder {
private InsnNode makeNewArray(InsnData insn) {
ArgType indexType = ArgType.parse(insn.getIndexAsType());
int dim = (int) insn.getLiteral();
ArgType arrType = dim == 0 ? indexType : ArgType.array(indexType, dim);
ArgType arrType;
if (dim == 0) {
arrType = indexType;
} else {
if (indexType.isArray()) {
// java bytecode can pass array as a base type
arrType = indexType;
} else {
arrType = ArgType.array(indexType, dim);
}
}
int regsCount = insn.getRegsCount();
NewArrayNode newArr = new NewArrayNode(arrType, regsCount - 1);
newArr.setResult(InsnArg.reg(insn, 0, arrType));
@@ -565,10 +577,27 @@ public class InsnDecoder {
return inode;
}
private InsnNode invoke(InsnData insn, InvokeType type, boolean isRange) {
if (type == InvokeType.CUSTOM) {
return InvokeCustomBuilder.build(method, insn, isRange);
private InsnNode invokeCustom(InsnData insn, boolean isRange) {
return InvokeCustomBuilder.build(method, insn, isRange);
}
private InsnNode invokeSpecial(InsnData insn) {
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
if (mthRef == null) {
throw new JadxRuntimeException("Failed to load method reference for insn: " + insn);
}
MethodInfo mthInfo = MethodInfo.fromRef(root, mthRef);
// convert 'special' to 'direct/super' same as dx
InvokeType type;
if (mthInfo.isConstructor() || Objects.equals(mthInfo.getDeclClass(), method.getParentClass().getClassInfo())) {
type = InvokeType.DIRECT;
} else {
type = InvokeType.SUPER;
}
return new InvokeNode(mthInfo, insn, type, false);
}
private InsnNode invoke(InsnData insn, InvokeType type, boolean isRange) {
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
if (mthRef == null) {
throw new JadxRuntimeException("Failed to load method reference for insn: " + insn);
@@ -578,16 +607,24 @@ public class InsnDecoder {
}
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);
inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, resType));
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(arrElemType)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
return inode;
}
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);
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.typeImmutableIfKnownReg(insn, 0, argType));
return inode;
@@ -655,7 +655,7 @@ public abstract class ArgType {
if (from.equals(to)) {
return false;
}
TypeCompareEnum result = root.getTypeUpdate().getTypeCompare().compareTypes(from, to);
TypeCompareEnum result = root.getTypeCompare().compareTypes(from, to);
return !result.isNarrow();
}
@@ -232,6 +232,27 @@ public abstract class InsnArg extends Typed {
return contains(AFlag.THIS);
}
/**
* Return true for 'this' from other classes (often occur in anonymous classes)
*/
public boolean isAnyThis() {
if (contains(AFlag.THIS)) {
return true;
}
InsnNode wrappedInsn = unwrap();
if (wrappedInsn != null && wrappedInsn.getType() == InsnType.IGET) {
return wrappedInsn.getArg(0).isAnyThis();
}
return false;
}
public InsnNode unwrap() {
if (isInsnWrap()) {
return ((InsnWrapArg) this).getWrapInsn();
}
return null;
}
public boolean isConst() {
return isLiteral() || (isInsnWrap() && ((InsnWrapArg) this).getWrapInsn().isConstInsn());
}
@@ -1,5 +1,7 @@
package jadx.core.dex.instructions.args;
import org.jetbrains.annotations.Nullable;
import jadx.core.codegen.TypeGen;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -57,12 +59,48 @@ public final class LiteralArg extends InsnArg {
}
public boolean isInteger() {
PrimitiveType type = this.type.getPrimitiveType();
return type == PrimitiveType.INT
|| type == PrimitiveType.BYTE
|| type == PrimitiveType.CHAR
|| type == PrimitiveType.SHORT
|| type == PrimitiveType.LONG;
switch (type.getPrimitiveType()) {
case INT:
case BYTE:
case CHAR:
case SHORT:
case LONG:
return true;
default:
return false;
}
}
public boolean isNegative() {
if (isInteger()) {
return literal < 0;
}
if (type == ArgType.FLOAT) {
float val = Float.intBitsToFloat(((int) literal));
return val < 0 && Float.isFinite(val);
}
if (type == ArgType.DOUBLE) {
double val = Double.longBitsToDouble(literal);
return val < 0 && Double.isFinite(val);
}
return false;
}
@Nullable
public LiteralArg negate() {
long neg;
if (isInteger()) {
neg = -literal;
} else if (type == ArgType.FLOAT) {
float val = Float.intBitsToFloat(((int) literal));
neg = Float.floatToIntBits(-val);
} else if (type == ArgType.DOUBLE) {
double val = Double.longBitsToDouble(literal);
neg = Double.doubleToLongBits(-val);
} else {
return null;
}
return new LiteralArg(neg, type);
}
@Override
@@ -59,6 +59,11 @@ public class SSAVar {
return assign;
}
@Nullable
public InsnNode getAssignInsn() {
return assign.getParentInsn();
}
public void setAssign(@NotNull RegisterArg assign) {
this.assign = assign;
}
@@ -187,19 +192,13 @@ public class SSAVar {
return usedInPhi;
}
public boolean isUsedInPhi() {
return usedInPhi != null && !usedInPhi.isEmpty();
public boolean isAssignInPhi() {
InsnNode assignInsn = getAssignInsn();
return assignInsn != null && assignInsn.getType() == InsnType.PHI;
}
public int getVariableUseCount() {
int count = useList.size();
if (usedInPhi == null) {
return count;
}
for (PhiInsn phiInsn : usedInPhi) {
count += phiInsn.getResult().getSVar().getUseCount();
}
return count;
public boolean isUsedInPhi() {
return usedInPhi != null && !usedInPhi.isEmpty();
}
public void setName(String name) {
@@ -16,9 +16,11 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.DecompilationMode;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.IFieldData;
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.ProcessClass;
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.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
@@ -42,12 +46,12 @@ import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.nodes.utils.TypeUtils;
import jadx.core.utils.ListUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
@@ -79,6 +83,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
* Top level classes used in this class (only for top level classes, empty for inners)
*/
private List<ClassNode> dependencies = Collections.emptyList();
/**
* Top level classes needed for code generation stage
*/
private List<ClassNode> codegenDeps = Collections.emptyList();
/**
* Classes which uses this class
*/
@@ -108,6 +116,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
ListConsumer<IFieldData, FieldNode> fieldsConsumer = new ListConsumer<>(fld -> FieldNode.build(this, fld));
ListConsumer<IMethodData, MethodNode> methodsConsumer = new ListConsumer<>(mth -> MethodNode.build(this, mth));
cls.visitFieldsAndMethods(fieldsConsumer, methodsConsumer);
if (this.fields != null && this.methods != null) {
// TODO: temporary solution for restore usage info in reloaded methods and fields
restoreUsageData(this.fields, this.methods, fieldsConsumer.getResult(), methodsConsumer.getResult());
}
this.fields = fieldsConsumer.getResult();
this.methods = methodsConsumer.getResult();
@@ -124,6 +136,24 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
}
}
private void restoreUsageData(List<FieldNode> oldFields, List<MethodNode> oldMethods,
List<FieldNode> newFields, List<MethodNode> newMethods) {
Map<FieldInfo, FieldNode> oldFieldMap = Utils.groupBy(oldFields, FieldNode::getFieldInfo);
for (FieldNode newField : newFields) {
FieldNode oldField = oldFieldMap.get(newField.getFieldInfo());
if (oldField != null) {
newField.setUseIn(oldField.getUseIn());
}
}
Map<MethodInfo, MethodNode> oldMethodsMap = Utils.groupBy(oldMethods, MethodNode::getMethodInfo);
for (MethodNode newMethod : newMethods) {
MethodNode oldMethod = oldMethodsMap.get(newMethod.getMethodInfo());
if (oldMethod != null) {
newMethod.setUseIn(oldMethod.getUseIn());
}
}
}
private ArgType checkSuperType(IClassData cls) {
String superType = cls.getSuperType();
if (superType == null) {
@@ -261,12 +291,15 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return true;
}
public boolean checkProcessed() {
return getTopParentClass().getState().isProcessComplete();
}
public void ensureProcessed() {
ClassNode topClass = getTopParentClass();
ProcessState state = topClass.getState();
if (state != PROCESS_COMPLETE) {
if (!checkProcessed()) {
ClassNode topParentClass = getTopParentClass();
throw new JadxRuntimeException("Expected class to be processed at this point,"
+ " class: " + topClass + ", state: " + state);
+ " class: " + topParentClass + ", state: " + topParentClass.getState());
}
}
@@ -274,6 +307,26 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
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() {
return decompile(true);
}
@@ -325,11 +378,22 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return code;
}
}
ICodeInfo codeInfo = ProcessClass.generateCode(this);
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
codeCache.add(clsRawName, 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
public void load() {
for (MethodNode mth : getMethods()) {
@@ -535,6 +599,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return innerClasses;
}
public List<ClassNode> getInlinedClasses() {
return inlinedClasses;
}
/**
* Get all inner and inlined classes recursively
*
@@ -566,6 +634,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
if (inlinedClasses.isEmpty()) {
inlinedClasses = new ArrayList<>(5);
}
cls.addAttr(new InlinedAttr(this));
inlinedClasses.add(cls);
}
@@ -576,7 +645,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
}
public boolean isAnonymous() {
return contains(AFlag.ANONYMOUS_CLASS);
return contains(AType.ANONYMOUS_CLASS);
}
public boolean isInner() {
@@ -707,6 +776,26 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
this.dependencies = dependencies;
}
public void removeDependency(ClassNode dep) {
this.dependencies = ListUtils.safeRemoveAndTrim(this.dependencies, dep);
}
public List<ClassNode> getCodegenDeps() {
return codegenDeps;
}
public void setCodegenDeps(List<ClassNode> codegenDeps) {
this.codegenDeps = codegenDeps;
}
public void addCodegenDep(ClassNode dep) {
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
}
public int getTotalDepsCount() {
return dependencies.size() + codegenDeps.size();
}
public List<ClassNode> getUseIn() {
return useIn;
}
@@ -9,6 +9,7 @@ import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.ListUtils;
public class FieldNode extends NotificationAttrNode implements ICodeNode {
@@ -80,6 +81,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
this.useIn = useIn;
}
public synchronized void addUseIn(MethodNode mth) {
useIn = ListUtils.safeAdd(useIn, mth);
}
@Override
public String typeName() {
return "field";
@@ -0,0 +1,26 @@
package jadx.core.dex.nodes;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.regions.conditions.IfCondition;
public interface IConditionRegion extends IRegion {
@Nullable
IfCondition getCondition();
/**
* Blocks merged into condition
* Needed for backtracking
* TODO: merge into condition object ???
*/
List<BlockNode> getConditionBlocks();
void invertCondition();
boolean simplifyCondition();
int getConditionSourceLine();
}
@@ -120,12 +120,7 @@ public class InsnNode extends LineAttrNode {
if (getArgsCount() == 0) {
return false;
}
for (InsnArg insnArg : arguments) {
if (insnArg == arg || arg.sameRegAndSVar(insnArg)) {
return true;
}
}
return false;
return InsnUtils.containsVar(arguments, arg);
}
/**
@@ -327,6 +322,40 @@ public class InsnNode extends LineAttrNode {
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.
*/
@@ -390,17 +419,16 @@ public class InsnNode extends LineAttrNode {
/**
* Make copy of InsnNode object.
* <p>
* <br>
* NOTE: can't copy instruction with result argument
* (SSA variable can't be used in two different assigns).
* <p>
* <br>
* Prefer use next methods:
* <ul>
* <li>{@link #copyWithoutResult()} to explicitly state that result not needed
* <li>{@link #copy(RegisterArg)} to provide new result arg
* <li>{@link #copyWithNewSsaVar(MethodNode)} to make new SSA variable for result arg
* </ul>
* <p>
*/
public InsnNode copy() {
if (this.getClass() != InsnNode.class) {
@@ -99,9 +99,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
@Override
public void unload() {
loaded = false;
if (noCode) {
return;
}
// don't unload retType, argTypes, typeParameters
thisArg = null;
argsList = null;
@@ -339,6 +336,14 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
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) {
if (loops.isEmpty()) {
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.ILoadResult;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage;
@@ -51,9 +52,9 @@ public class RootNode {
private final JadxArgs args;
private final List<IDexTreeVisitor> preDecompilePasses;
private final List<IDexTreeVisitor> passes;
private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>();
private final ProcessClass processClasses;
private final ErrorsCounter errorsCounter = new ErrorsCounter();
private final StringUtils stringUtils;
private final ConstStorage constValues;
@@ -76,7 +77,7 @@ public class RootNode {
public RootNode(JadxArgs args) {
this.args = args;
this.preDecompilePasses = Jadx.getPreDecompilePassesList();
this.passes = Jadx.getPassesList(args);
this.processClasses = new ProcessClass(this.getArgs());
this.stringUtils = new StringUtils(args);
this.constValues = new ConstStorage(args);
this.typeUpdate = new TypeUpdate(this);
@@ -334,6 +335,12 @@ public class RootNode {
return resolveClass(clsInfo);
}
/**
* Searches for ClassNode by its full name (original or alias name)
* <br>
* Warning: This method has a runtime of O(n) (n = number of classes).
* If you need to call it more than once consider {@link #buildFullAliasClassCache()} instead
*/
@Nullable
public ClassNode searchClassByFullAlias(String fullName) {
for (ClassNode cls : classes) {
@@ -346,6 +353,20 @@ public class RootNode {
return null;
}
public Map<String, ClassNode> buildFullAliasClassCache() {
Map<String, ClassNode> classNameCache = new HashMap<>(classes.size());
for (ClassNode cls : classes) {
ClassInfo classInfo = cls.getClassInfo();
String fullName = classInfo.getFullName();
String alias = classInfo.getAliasFullName();
classNameCache.put(fullName, cls);
if (alias != null && !fullName.equals(alias)) {
classNameCache.put(alias, cls);
}
}
return classNameCache;
}
public List<ClassNode> searchClassByShortName(String shortName) {
List<ClassNode> list = new ArrayList<>();
for (ClassNode cls : classes) {
@@ -358,15 +379,6 @@ public class RootNode {
@Nullable
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());
if (cls == null) {
return null;
@@ -410,19 +422,14 @@ public class RootNode {
@Nullable
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());
if (cls == null) {
return null;
}
FieldNode fieldNode = cls.searchField(field);
if (fieldNode != null) {
return fieldNode;
}
return deepResolveField(cls, field);
}
@@ -454,18 +461,16 @@ public class RootNode {
return null;
}
public ProcessClass getProcessClasses() {
return processClasses;
}
public List<IDexTreeVisitor> getPasses() {
return passes;
return processClasses.getPasses();
}
public void initPasses() {
for (IDexTreeVisitor pass : passes) {
try {
pass.init(this);
} catch (Exception e) {
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
}
}
processClasses.initPasses(this);
}
public ICodeWriter makeCodeWriter() {
@@ -2,7 +2,6 @@ package jadx.core.dex.nodes.parser;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
@@ -197,6 +196,8 @@ public class SignatureParser {
String obj = slice();
if (!innerType) {
obj += ';';
} else {
obj = obj.replace('/', '.');
}
List<ArgType> typeVars = consumeGenericArgs();
consume('>');
@@ -227,7 +228,7 @@ public class SignatureParser {
}
private List<ArgType> consumeGenericArgs() {
List<ArgType> list = new LinkedList<>();
List<ArgType> list = new ArrayList<>();
ArgType type;
do {
if (lookAhead('*')) {
@@ -251,8 +252,8 @@ public class SignatureParser {
/**
* Map of generic types names to extends classes.
* <p/>
* Example: "<T:Ljava/lang/Exception;:Ljava/lang/Object;>"
* <p>
* Example: "&lt;T:Ljava/lang/Exception;:Ljava/lang/Object;&gt;"
*/
@SuppressWarnings("ConditionalBreakInInfiniteLoop")
public List<ArgType> consumeGenericTypeParameters() {
@@ -8,6 +8,9 @@ import org.jetbrains.annotations.Nullable;
import jadx.core.clsp.ClspClass;
import jadx.core.clsp.ClspMethod;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.BaseInvokeNode;
import jadx.core.dex.instructions.args.ArgType;
@@ -15,6 +18,7 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
public class MethodUtils {
private final RootNode root;
@@ -34,7 +38,7 @@ public class MethodUtils {
@Nullable
public IMethodDetails getMethodDetails(MethodInfo callMth) {
MethodNode mthNode = root.deepResolveMethod(callMth);
MethodNode mthNode = root.resolveMethod(callMth);
if (mthNode != null) {
return mthNode;
}
@@ -67,7 +71,7 @@ public class MethodUtils {
return null;
}
public boolean processMethodArgsOverloaded(ArgType startCls, MethodInfo mthInfo, @Nullable List<IMethodDetails> collectedMths) {
private boolean processMethodArgsOverloaded(ArgType startCls, MethodInfo mthInfo, @Nullable List<IMethodDetails> collectedMths) {
if (startCls == null || !startCls.isObject()) {
return false;
}
@@ -122,4 +126,25 @@ public class MethodUtils {
}
return false;
}
@Nullable
public IMethodDetails getOverrideBaseMth(MethodNode mth) {
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr == null) {
return null;
}
return Utils.getOne(overrideAttr.getBaseMethods());
}
public ClassInfo getMethodOriginDeclClass(MethodNode mth) {
IMethodDetails baseMth = getOverrideBaseMth(mth);
if (baseMth != null) {
return baseMth.getMethodInfo().getDeclClass();
}
MethodBridgeAttr bridgeAttr = mth.get(AType.BRIDGED_BY);
if (bridgeAttr != null) {
return getMethodOriginDeclClass(bridgeAttr.getBridgeMth());
}
return mth.getMethodInfo().getDeclClass();
}
}
@@ -63,7 +63,7 @@ public class TypeUtils {
public ArgType expandTypeVariables(ClassNode cls, ArgType type) {
if (type.containsTypeVariable()) {
expandTypeVar(cls, type, cls.getGenericTypeParameters());
expandTypeVar(cls, type, getKnownTypeVarsAtClass(cls));
}
return type;
}
@@ -115,11 +115,18 @@ public class TypeUtils {
return varsAttr.getTypeVars();
}
private static Set<ArgType> collectKnownTypeVarsAtMethod(MethodNode mth) {
ClassNode declCls = mth.getParentClass();
Set<ArgType> typeVars = new HashSet<>(declCls.getGenericTypeParameters());
declCls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters()));
private static Collection<ArgType> getKnownTypeVarsAtClass(ClassNode cls) {
if (cls.isInner()) {
Set<ArgType> typeVars = new HashSet<>(cls.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());
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.IfOp;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
public final class Compare {
private final IfNode insn;
@@ -35,13 +34,8 @@ public final class Compare {
return this;
}
/**
* Change 'a != false' to 'a == true'
*/
public void normalize() {
if (getOp() == IfOp.NE && getB().isFalse()) {
insn.changeCondition(IfOp.EQ, getA(), LiteralArg.litTrue());
}
insn.normalize();
}
@Override
@@ -0,0 +1,87 @@
package jadx.core.dex.regions.conditions;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IConditionRegion;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.AbstractRegion;
import jadx.core.utils.BlockUtils;
public abstract class ConditionRegion extends AbstractRegion implements IConditionRegion {
@Nullable
private IfCondition condition;
private List<BlockNode> conditionBlocks = Collections.emptyList();
public ConditionRegion(IRegion parent) {
super(parent);
}
@Override
@Nullable
public IfCondition getCondition() {
return condition;
}
@Override
public List<BlockNode> getConditionBlocks() {
return conditionBlocks;
}
@Override
public void invertCondition() {
if (condition != null) {
condition = IfCondition.invert(condition);
}
}
@Override
public boolean simplifyCondition() {
if (condition == null) {
return false;
}
IfCondition updated = IfCondition.simplify(condition);
if (updated != condition) {
condition = updated;
return true;
}
return false;
}
@Override
public int getConditionSourceLine() {
for (BlockNode block : conditionBlocks) {
InsnNode lastInsn = BlockUtils.getLastInsn(block);
if (lastInsn != null) {
int sourceLine = lastInsn.getSourceLine();
if (sourceLine != 0) {
return sourceLine;
}
}
}
return 0;
}
/**
* Prefer way for update condition info
*/
public void updateCondition(IfInfo info) {
this.condition = info.getCondition();
this.conditionBlocks = info.getMergedBlocks();
}
public void updateCondition(IfCondition condition, List<BlockNode> conditionBlocks) {
this.condition = condition;
this.conditionBlocks = conditionBlocks;
}
public void updateCondition(BlockNode block) {
this.condition = IfCondition.fromIfBlock(block);
this.conditionBlocks = Collections.singletonList(block);
}
}
@@ -272,6 +272,12 @@ public final class IfCondition extends AttrNode {
}
}
public List<InsnNode> collectInsns() {
List<InsnNode> list = new ArrayList<>();
visitInsns(list::add);
return list;
}
@Nullable
public InsnNode getFirstInsn() {
if (mode == Mode.COMPARE) {
@@ -3,7 +3,6 @@ package jadx.core.dex.regions.conditions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import jadx.api.ICodeWriter;
import jadx.core.codegen.RegionGen;
@@ -11,16 +10,9 @@ import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBranchRegion;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.AbstractRegion;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.exceptions.CodegenException;
public final class IfRegion extends AbstractRegion implements IBranchRegion {
private List<BlockNode> conditionBlocks;
private IfCondition condition;
public final class IfRegion extends ConditionRegion implements IBranchRegion {
private IContainer thenRegion;
private IContainer elseRegion;
@@ -28,14 +20,6 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
super(parent);
}
public IfCondition getCondition() {
return condition;
}
public void setCondition(IfCondition condition) {
this.condition = condition;
}
public IContainer getThenRegion() {
return thenRegion;
}
@@ -52,31 +36,8 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
this.elseRegion = elseRegion;
}
public List<BlockNode> getConditionBlocks() {
return conditionBlocks;
}
public void setConditionBlocks(List<BlockNode> conditionBlocks) {
this.conditionBlocks = conditionBlocks;
}
public void setConditionBlocks(Set<BlockNode> conditionBlocks) {
List<BlockNode> list = new ArrayList<>(conditionBlocks);
Collections.sort(list);
this.conditionBlocks = list;
}
public boolean simplifyCondition() {
IfCondition cond = IfCondition.simplify(condition);
if (cond != condition) {
condition = cond;
return true;
}
return false;
}
public void invert() {
condition = IfCondition.invert(condition);
invertCondition();
// swap regions
IContainer tmp = thenRegion;
thenRegion = elseRegion;
@@ -84,20 +45,12 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
}
public int getSourceLine() {
for (BlockNode block : conditionBlocks) {
InsnNode lastInsn = BlockUtils.getLastInsn(block);
if (lastInsn != null) {
int sourceLine = lastInsn.getSourceLine();
if (sourceLine != 0) {
return sourceLine;
}
}
}
return 0;
return getConditionSourceLine();
}
@Override
public List<IContainer> getSubBlocks() {
List<BlockNode> conditionBlocks = getConditionBlocks();
List<IContainer> all = new ArrayList<>(conditionBlocks.size() + 2);
all.addAll(conditionBlocks);
if (thenRegion != null) {
@@ -151,6 +104,6 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
@Override
public String toString() {
return "IF " + conditionBlocks + " THEN: " + thenRegion + " ELSE: " + elseRegion;
return "IF " + getConditionBlocks() + " THEN: " + thenRegion + " ELSE: " + elseRegion;
}
}
@@ -1,7 +1,6 @@
package jadx.core.dex.regions.loops;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.Nullable;
@@ -9,55 +8,45 @@ import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
import jadx.core.codegen.RegionGen;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.AbstractRegion;
import jadx.core.dex.regions.conditions.ConditionRegion;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.CodegenException;
public final class LoopRegion extends AbstractRegion {
public final class LoopRegion extends ConditionRegion {
private final LoopInfo info;
/**
* loop header contains one 'if' insn, equals null for infinite loop
*/
@Nullable
private IfCondition condition;
private final BlockNode conditionBlock;
// instruction which must be executed before condition in every loop
private BlockNode preCondition;
private IRegion body;
private final boolean conditionAtEnd;
private final @Nullable BlockNode header;
// instruction which must be executed before condition in every loop
private @Nullable BlockNode preCondition;
private IRegion body;
private LoopType type;
public LoopRegion(IRegion parent, LoopInfo info, @Nullable BlockNode header, boolean reversed) {
super(parent);
this.info = info;
this.conditionBlock = header;
this.condition = IfCondition.fromIfBlock(header);
this.header = header;
this.conditionAtEnd = reversed;
if (header != null) {
updateCondition(header);
}
}
public LoopInfo getInfo() {
return info;
}
public IfCondition getCondition() {
return condition;
}
public void setCondition(IfCondition condition) {
this.condition = condition;
}
@Nullable
public BlockNode getHeader() {
return conditionBlock;
return header;
}
public IRegion getBody() {
@@ -79,10 +68,6 @@ public final class LoopRegion extends AbstractRegion {
this.preCondition = preCondition;
}
private IfNode getIfInsn() {
return (IfNode) BlockUtils.getLastInsn(conditionBlock);
}
/**
* Check if pre-conditions can be inlined into loop condition
*/
@@ -91,7 +76,14 @@ public final class LoopRegion extends AbstractRegion {
if (insns.isEmpty()) {
return true;
}
IfNode ifInsn = getIfInsn();
IfCondition condition = getCondition();
if (condition == null) {
return false;
}
List<RegisterArg> conditionArgs = condition.getRegisterArgs();
if (conditionArgs.isEmpty()) {
return false;
}
int size = insns.size();
for (int i = 0; i < size; i++) {
InsnNode insn = insns.get(i);
@@ -110,7 +102,7 @@ public final class LoopRegion extends AbstractRegion {
}
}
// or in if insn
if (!found && ifInsn.containsVar(res)) {
if (!found && InsnUtils.containsVar(conditionArgs, res)) {
found = true;
}
if (!found) {
@@ -124,8 +116,8 @@ public final class LoopRegion extends AbstractRegion {
* Move all preCondition block instructions before conditionBlock instructions
*/
public void mergePreCondition() {
if (preCondition != null && conditionBlock != null) {
List<InsnNode> condInsns = conditionBlock.getInstructions();
if (preCondition != null && header != null) {
List<InsnNode> condInsns = header.getInstructions();
List<InsnNode> preCondInsns = preCondition.getInstructions();
preCondInsns.addAll(condInsns);
condInsns.clear();
@@ -135,9 +127,13 @@ public final class LoopRegion extends AbstractRegion {
}
}
public int getConditionSourceLine() {
InsnNode lastInsn = BlockUtils.getLastInsn(conditionBlock);
return lastInsn == null ? 0 : lastInsn.getSourceLine();
public int getSourceLine() {
InsnNode lastInsn = BlockUtils.getLastInsn(header);
int headerLine = lastInsn == null ? 0 : lastInsn.getSourceLine();
if (headerLine != 0) {
return headerLine;
}
return getConditionSourceLine();
}
public LoopType getType() {
@@ -150,17 +146,15 @@ public final class LoopRegion extends AbstractRegion {
@Override
public List<IContainer> getSubBlocks() {
List<IContainer> all = new ArrayList<>(3);
List<IContainer> all = new ArrayList<>(2 + getConditionBlocks().size());
if (preCondition != null) {
all.add(preCondition);
}
if (conditionBlock != null) {
all.add(conditionBlock);
}
all.addAll(getConditionBlocks());
if (body != null) {
all.add(body);
}
return Collections.unmodifiableList(all);
return all;
}
@Override
@@ -1,5 +1,6 @@
package jadx.core.dex.trycatch;
import java.util.Comparator;
import java.util.List;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
@@ -8,9 +9,14 @@ import jadx.core.utils.Utils;
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;
public CatchAttr(List<ExceptionHandler> handlers) {
private CatchAttr(List<ExceptionHandler> handlers) {
this.handlers = handlers;
}
@@ -0,0 +1,133 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
name = "AnonymousClassVisitor",
desc = "Prepare anonymous class for inline",
runBefore = {
ModVisitor.class,
CodeShrinkVisitor.class
}
)
public class AnonymousClassVisitor extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws JadxException {
if (cls.contains(AType.ANONYMOUS_CLASS)) {
for (MethodNode mth : cls.getMethods()) {
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
processAnonymousConstructor(mth);
break;
}
}
}
return true;
}
private static void processAnonymousConstructor(MethodNode mth) {
List<InsnNode> usedInsns = new ArrayList<>();
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(mth, usedInsns);
if (argsMap.isEmpty()) {
mth.add(AFlag.NO_SKIP_ARGS);
} else {
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
FieldNode field = entry.getValue();
if (field == null) {
continue;
}
InsnArg arg = entry.getKey();
field.addAttr(new FieldReplaceAttr(arg));
field.add(AFlag.DONT_GENERATE);
if (arg.isRegister()) {
arg.add(AFlag.SKIP_ARG);
SkipMethodArgsAttr.skipArg(mth, ((RegisterArg) arg));
}
}
}
for (InsnNode usedInsn : usedInsns) {
usedInsn.add(AFlag.DONT_GENERATE);
}
}
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode mth, List<InsnNode> usedInsns) {
MethodInfo callMth = mth.getMethodInfo();
ClassNode cls = mth.getParentClass();
List<RegisterArg> argList = mth.getArgRegs();
ClassNode outerCls = mth.getUseIn().get(0).getParentClass();
int startArg = 0;
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(outerCls.getClassInfo().getType())) {
startArg = 1;
}
Map<InsnArg, FieldNode> map = new LinkedHashMap<>();
int argsCount = argList.size();
for (int i = startArg; i < argsCount; i++) {
RegisterArg arg = argList.get(i);
InsnNode useInsn = getParentInsnSkipMove(arg);
if (useInsn == null) {
return Collections.emptyMap();
}
switch (useInsn.getType()) {
case IPUT:
FieldNode fieldNode = cls.searchField((FieldInfo) ((IndexInsnNode) useInsn).getIndex());
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
return Collections.emptyMap();
}
map.put(arg, fieldNode);
usedInsns.add(useInsn);
break;
case CONSTRUCTOR:
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
if (!superConstr.isSuper()) {
return Collections.emptyMap();
}
usedInsns.add(useInsn);
break;
default:
return Collections.emptyMap();
}
}
return map;
}
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar.getUseCount() != 1) {
return null;
}
RegisterArg useArg = sVar.getUseList().get(0);
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn == null) {
return null;
}
if (parentInsn.getType() == InsnType.MOVE) {
return getParentInsnSkipMove(parentInsn.getResult());
}
return parentInsn;
}
}
@@ -1,6 +1,7 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
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.info.ClassInfo;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
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.JadxRuntimeException;
import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset;
@@ -51,11 +56,11 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
tries.forEach(tryData -> LOG.debug(" - {}", tryData));
}
for (ITry tryData : tries) {
List<ExceptionHandler> handlers = attachHandlers(mth, tryData.getCatch(), insnByOffset);
List<ExceptionHandler> handlers = convertToHandlers(mth, tryData.getCatch(), insnByOffset);
if (handlers.isEmpty()) {
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) {
// merge handlers
List<ExceptionHandler> handlers = Utils.concat(existAttr.getHandlers(), catchAttr.getHandlers());
insn.addAttr(new CatchAttr(handlers));
insn.addAttr(CatchAttr.build(handlers));
} else {
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();
String[] handlerTypes = catchBlock.getTypes();
@@ -117,6 +122,7 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
if (allHandlerOffset >= 0) {
Utils.addToList(list, createHandler(mth, insnByOffset, allHandlerOffset, null));
}
checkAndFilterHandlers(mth, list);
return list;
}
@@ -143,6 +149,45 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
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) {
InsnNode nop = new InsnNode(InsnType.NOP, 0);
nop.setOffset(offset);
@@ -1,13 +1,17 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
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.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
@@ -28,6 +32,7 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.exceptions.JadxException;
@@ -55,7 +60,7 @@ public class ClassModifier extends AbstractVisitor {
removeSyntheticFields(cls);
cls.getMethods().forEach(ClassModifier::removeSyntheticMethods);
cls.getMethods().forEach(ClassModifier::removeEmptyMethods);
cls.getMethods().forEach(ClassModifier::cleanInsnsInAnonymousConstructor);
cls.getMethods().forEach(ClassModifier::processAnonymousConstructor);
return false;
}
@@ -152,7 +157,7 @@ public class ClassModifier extends AbstractVisitor {
return;
}
// remove synthetic constructor for inner classes
if (af.isConstructor()) {
if (mth.isConstructor() && mth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE)) {
InsnNode insn = BlockUtils.getOnlyOneInsnFromMth(mth);
if (insn != null) {
List<RegisterArg> args = mth.getArgRegs();
@@ -207,7 +212,14 @@ public class ClassModifier extends AbstractVisitor {
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);
}
}
}
}
@@ -241,7 +253,7 @@ public class ClassModifier extends AbstractVisitor {
return false;
}
MethodInfo callMth = invokeInsn.getCallMth();
MethodNode wrappedMth = mth.root().deepResolveMethod(callMth);
MethodNode wrappedMth = mth.root().resolveMethod(callMth);
if (wrappedMth == null) {
return false;
}
@@ -326,27 +338,86 @@ public class ClassModifier extends AbstractVisitor {
/**
* Remove super call and put into removed fields from anonymous constructor
*/
private static void cleanInsnsInAnonymousConstructor(MethodNode mth) {
private static void processAnonymousConstructor(MethodNode mth) {
if (!mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
return;
}
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
InsnType type = insn.getType();
if (type == InsnType.CONSTRUCTOR) {
ConstructorInsn ctorInsn = (ConstructorInsn) insn;
if (ctorInsn.isSuper()) {
ctorInsn.add(AFlag.DONT_GENERATE);
}
} else if (type == InsnType.IPUT) {
FieldInfo fldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
FieldNode fieldNode = mth.root().resolveField(fldInfo);
if (fieldNode != null && fieldNode.contains(AFlag.DONT_GENERATE)) {
insn.add(AFlag.DONT_GENERATE);
}
}
List<InsnNode> usedInsns = new ArrayList<>();
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(mth, usedInsns);
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
FieldNode field = entry.getValue();
if (field == null) {
continue;
}
InsnArg arg = entry.getKey();
field.addAttr(new FieldReplaceAttr(arg));
field.add(AFlag.DONT_GENERATE);
if (arg.isRegister()) {
arg.add(AFlag.SKIP_ARG);
SkipMethodArgsAttr.skipArg(mth, ((RegisterArg) arg));
}
}
for (InsnNode usedInsn : usedInsns) {
usedInsn.add(AFlag.DONT_GENERATE);
}
}
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode mth, List<InsnNode> usedInsns) {
MethodInfo callMth = mth.getMethodInfo();
ClassNode cls = mth.getParentClass();
List<RegisterArg> argList = mth.getArgRegs();
ClassNode outerCls = mth.getUseIn().get(0).getParentClass();
int startArg = 0;
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(outerCls.getClassInfo().getType())) {
startArg = 1;
}
Map<InsnArg, FieldNode> map = new LinkedHashMap<>();
int argsCount = argList.size();
for (int i = startArg; i < argsCount; i++) {
RegisterArg arg = argList.get(i);
InsnNode useInsn = getParentInsnSkipMove(arg);
if (useInsn == null) {
return Collections.emptyMap();
}
switch (useInsn.getType()) {
case IPUT:
FieldNode fieldNode = cls.searchField((FieldInfo) ((IndexInsnNode) useInsn).getIndex());
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
return Collections.emptyMap();
}
map.put(arg, fieldNode);
usedInsns.add(useInsn);
break;
case CONSTRUCTOR:
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
if (!superConstr.isSuper()) {
return Collections.emptyMap();
}
usedInsns.add(useInsn);
break;
default:
return Collections.emptyMap();
}
}
return map;
}
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar.getUseCount() != 1) {
return null;
}
RegisterArg useArg = sVar.getUseList().get(0);
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn == null) {
return null;
}
if (parentInsn.getType() == InsnType.MOVE) {
return getParentInsnSkipMove(parentInsn.getResult());
}
return parentInsn;
}
private static boolean isNonDefaultConstructorExists(MethodNode defCtor) {
@@ -65,6 +65,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
SSAVar sVar = insn.getResult().getSVar();
InsnArg constArg;
Runnable onSuccess = null;
InsnType insnType = insn.getType();
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);
constArg = InsnArg.wrapArg(constGet);
constArg.setType(ArgType.STRING);
onSuccess = () -> f.addUseIn(mth);
}
} else if (insnType == InsnType.CONST_CLASS) {
if (sVar.isUsedInPhi()) {
@@ -104,6 +106,9 @@ public class ConstInlineVisitor extends AbstractVisitor {
// all check passed, run replace
if (replaceConst(mth, insn, constArg)) {
toRemove.add(insn);
if (onSuccess != null) {
onSuccess.run();
}
}
}
@@ -235,7 +240,10 @@ public class ConstInlineVisitor extends AbstractVisitor {
fieldNode = mth.getParentClass().getConstField((int) literal, false);
}
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 {
if (needExplicitCast(useInsn, litArg)) {
litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
@@ -137,6 +137,17 @@ public class DeboxingVisitor extends AbstractVisitor {
if (ssaVar.isTypeImmutable()) {
return false;
}
InsnNode assignInsn = ssaVar.getAssignInsn();
if (assignInsn == null) {
// method arg
return false;
}
InsnType assignInsnType = assignInsn.getType();
if (assignInsnType == InsnType.CONST || assignInsnType == InsnType.MOVE) {
if (assignInsn.getArg(0).getType().isObject()) {
return false;
}
}
for (RegisterArg useArg : ssaVar.getUseList()) {
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn == null) {
@@ -15,6 +15,7 @@ import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.codegen.TypeGen;
import jadx.core.deobf.NameMapper;
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.EnumField;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
@@ -71,7 +72,14 @@ public class EnumVisitor extends AbstractVisitor {
@Override
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();
if (accessFlags.isEnum()) {
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
@@ -179,8 +187,7 @@ public class EnumVisitor extends AbstractVisitor {
if (!enumClsInfo.equals(cls.getClassInfo())) {
ClassNode enumCls = cls.root().resolveClass(enumClsInfo);
if (enumCls != null) {
processEnumCls(enumField, enumCls);
cls.addInlinedClass(enumCls);
processEnumCls(cls, enumField, enumCls);
}
}
List<RegisterArg> regs = new ArrayList<>();
@@ -381,7 +388,11 @@ public class EnumVisitor extends AbstractVisitor {
if (constrCls == 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;
}
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
@@ -466,7 +477,7 @@ public class EnumVisitor extends AbstractVisitor {
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
for (MethodNode innerMth : innerCls.getMethods()) {
if (innerMth.getAccessFlags().isConstructor()) {
@@ -474,7 +485,11 @@ public class EnumVisitor extends AbstractVisitor {
}
}
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) {
@@ -86,7 +86,7 @@ public class FixAccessModifiers extends AbstractVisitor {
if (!accessFlags.isPublic()) {
// if class is used in inlinable method => make it public
for (MethodNode useMth : cls.getUseInMth()) {
boolean canInline = MarkMethodsForInline.canInline(useMth) || useMth.contains(AType.METHOD_INLINE);
boolean canInline = useMth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE) || useMth.contains(AType.METHOD_INLINE);
if (canInline && !useMth.getUseIn().isEmpty()) {
return AccessFlags.PUBLIC;
}
@@ -26,9 +26,6 @@ public class InitCodeVariables extends AbstractVisitor {
@Override
public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode()) {
return;
}
initCodeVars(mth);
}
@@ -42,16 +39,24 @@ public class InitCodeVariables extends AbstractVisitor {
private static void initCodeVars(MethodNode mth) {
RegisterArg thisArg = mth.getThisArg();
if (thisArg != null) {
initCodeVar(thisArg.getSVar());
initCodeVar(mth, thisArg);
}
for (RegisterArg mthArg : mth.getArgRegs()) {
initCodeVar(mthArg.getSVar());
initCodeVar(mth, mthArg);
}
for (SSAVar ssaVar : mth.getSVars()) {
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) {
if (ssaVar.isCodeVarSet()) {
return;
@@ -10,7 +10,6 @@ import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
@@ -47,7 +46,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
if (mia != null) {
return mia;
}
if (canInline(mth)) {
if (mth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE)) {
if (mth.getBasicBlocks() == null) {
return null;
}
@@ -59,14 +58,6 @@ public class MarkMethodsForInline extends AbstractVisitor {
return MethodInlineAttr.inlineNotNeeded(mth);
}
public static boolean canInline(MethodNode mth) {
if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) {
return false;
}
AccessInfo accessFlags = mth.getAccessFlags();
return accessFlags.isSynthetic() && accessFlags.isStatic();
}
@Nullable
private static MethodInlineAttr inlineMth(MethodNode mth) {
List<InsnNode> insns = BlockUtils.collectInsnsWithLimit(mth.getBasicBlocks(), 2);
@@ -79,7 +70,11 @@ public class MarkMethodsForInline extends AbstractVisitor {
if (insn.getType() == InsnType.RETURN && insn.getArgsCount() == 1) {
// synthetic field getter
// set arg from 'return' instruction
return addInlineAttr(mth, InsnNode.wrapArg(insn.getArg(0)));
InsnArg arg = insn.getArg(0);
if (!arg.isInsnWrap()) {
return null;
}
return addInlineAttr(mth, ((InsnWrapArg) arg).getWrapInsn());
}
// method invoke
return addInlineAttr(mth, insn);
@@ -111,7 +106,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
InsnType insnType = insn.getType();
if (insnType == InsnType.INVOKE) {
InvokeNode invoke = (InvokeNode) insn;
MethodNode callMthNode = mth.root().deepResolveMethod(invoke.getCallMth());
MethodNode callMthNode = mth.root().resolveMethod(invoke.getCallMth());
if (callMthNode != null) {
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;
}
}
@@ -1,7 +1,5 @@
package jadx.core.dex.visitors;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -19,10 +17,9 @@ import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
@@ -46,6 +43,7 @@ import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.conditions.IfCondition;
@@ -116,7 +114,7 @@ public class ModVisitor extends AbstractVisitor {
break;
case SWITCH:
replaceConstKeys(parentClass, (SwitchInsn) insn);
replaceConstKeys(mth, parentClass, (SwitchInsn) insn);
break;
case NEW_ARRAY:
@@ -230,13 +228,14 @@ public class ModVisitor extends AbstractVisitor {
return result == TypeCompareEnum.NARROW; // true if use class is subclass of field class
}
private static void replaceConstKeys(ClassNode parentClass, SwitchInsn insn) {
private static void replaceConstKeys(MethodNode mth, ClassNode parentClass, SwitchInsn insn) {
int[] keys = insn.getKeys();
int len = keys.length;
for (int k = 0; k < len; k++) {
FieldNode f = parentClass.getConstField(keys[k]);
if (f != null) {
insn.modifyKey(k, f);
f.addUseIn(mth);
}
}
}
@@ -293,6 +292,13 @@ public class ModVisitor extends AbstractVisitor {
@SuppressWarnings("unchecked")
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) {
List<EncodedValue> listVal = (List<EncodedValue>) encodedValue.getValue();
if (!listVal.isEmpty()) {
@@ -322,6 +328,7 @@ public class ModVisitor extends AbstractVisitor {
InsnNode inode = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
inode.setResult(insn.getResult());
replaceInsn(mth, block, i, inode);
f.addUseIn(mth);
}
}
@@ -334,7 +341,9 @@ public class ModVisitor extends AbstractVisitor {
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg);
if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet));
if (arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet))) {
f.addUseIn(mth);
}
}
}
}
@@ -432,96 +441,39 @@ public class ModVisitor extends AbstractVisitor {
return false;
}
/**
* For args in anonymous constructor invoke apply:
* - forbid inline into constructor call
* - make variables final (compiler require this implicitly)
*/
private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) {
MethodInfo callMth = co.getCallMth();
MethodNode callMthNode = mth.root().resolveMethod(callMth);
if (callMthNode == null) {
IMethodDetails callMthDetails = mth.root().getMethodUtils().getMethodDetails(co);
if (!(callMthDetails instanceof MethodNode)) {
return;
}
ClassNode classNode = callMthNode.getParentClass();
if (!classNode.isAnonymous()) {
MethodNode callMth = (MethodNode) callMthDetails;
if (!callMth.contains(AFlag.ANONYMOUS_CONSTRUCTOR) || callMth.contains(AFlag.NO_SKIP_ARGS)) {
return;
}
if (!mth.getParentClass().getInnerClasses().contains(classNode)) {
return;
}
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(callMthNode, co);
if (argsMap.isEmpty() && !callMthNode.getArgRegs().isEmpty()) {
return;
}
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
FieldNode field = entry.getValue();
if (field == null) {
continue;
}
InsnArg arg = entry.getKey();
field.addAttr(new FieldReplaceAttr(arg));
field.add(AFlag.DONT_GENERATE);
if (arg.isRegister()) {
RegisterArg reg = (RegisterArg) arg;
SSAVar sVar = reg.getSVar();
if (sVar != null) {
sVar.getCodeVar().setFinal(true);
SkipMethodArgsAttr attr = callMth.get(AType.SKIP_MTH_ARGS);
if (attr != null) {
int argsCount = Math.min(callMth.getMethodInfo().getArgsCount(), co.getArgsCount());
for (int i = 0; i < argsCount; i++) {
if (attr.isSkip(i)) {
anonymousCallArgMod(co.getArg(i));
}
reg.add(AFlag.DONT_INLINE);
reg.add(AFlag.SKIP_ARG);
}
} else {
// additional info not available apply mods to all args (the safest solution)
co.getArguments().forEach(ModVisitor::anonymousCallArgMod);
}
}
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode callMthNode, ConstructorInsn co) {
Map<InsnArg, FieldNode> map = new LinkedHashMap<>();
MethodInfo callMth = callMthNode.getMethodInfo();
ClassNode cls = callMthNode.getParentClass();
ClassNode parentClass = cls.getParentClass();
List<RegisterArg> argList = callMthNode.getArgRegs();
int startArg = 0;
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(parentClass.getClassInfo().getType())) {
startArg = 1;
private static void anonymousCallArgMod(InsnArg arg) {
arg.add(AFlag.DONT_INLINE);
if (arg.isRegister()) {
((RegisterArg) arg).getSVar().getCodeVar().setFinal(true);
}
int argsCount = argList.size();
for (int i = startArg; i < argsCount; i++) {
RegisterArg arg = argList.get(i);
InsnNode useInsn = getParentInsnSkipMove(arg);
if (useInsn == null) {
return Collections.emptyMap();
}
FieldNode fieldNode = null;
if (useInsn.getType() == InsnType.IPUT) {
FieldInfo field = (FieldInfo) ((IndexInsnNode) useInsn).getIndex();
fieldNode = cls.searchField(field);
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
return Collections.emptyMap();
}
} else if (useInsn.getType() == InsnType.CONSTRUCTOR) {
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
if (!superConstr.isSuper()) {
return Collections.emptyMap();
}
} else {
return Collections.emptyMap();
}
map.put(co.getArg(i), fieldNode);
}
return map;
}
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar.getUseCount() != 1) {
return null;
}
RegisterArg useArg = sVar.getUseList().get(0);
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn == null) {
return null;
}
if (parentInsn.getType() == InsnType.MOVE) {
return getParentInsnSkipMove(parentInsn.getResult());
}
return parentInsn;
}
/**
@@ -575,6 +527,7 @@ public class ModVisitor extends AbstractVisitor {
if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
filledArr.addArg(InsnArg.wrapArg(fGet));
f.addUseIn(mth);
} else {
filledArr.addArg(arg);
}
@@ -52,6 +52,13 @@ public class MoveInlineVisitor extends AbstractVisitor {
if (resultArg.sameRegAndSVar(moveArg)) {
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();
if (ssaVar.isUsedInPhi()) {
return deleteMove(mth, move);
@@ -1,11 +1,11 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
@@ -17,6 +17,7 @@ import jadx.core.clsp.ClspClass;
import jadx.core.clsp.ClspMethod;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.info.AccessInfo;
@@ -32,6 +33,7 @@ import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@JadxVisitor(
name = "OverrideMethodVisitor",
@@ -45,70 +47,86 @@ public class OverrideMethodVisitor extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws JadxException {
processCls(cls);
SuperTypesData superData = collectSuperTypes(cls);
if (superData != null) {
for (MethodNode mth : cls.getMethods()) {
processMth(mth, superData);
}
}
return true;
}
private void processCls(ClassNode cls) {
List<ArgType> superTypes = collectSuperTypes(cls);
if (!superTypes.isEmpty()) {
for (MethodNode mth : cls.getMethods()) {
processMth(cls, superTypes, mth);
}
}
}
private void processMth(ClassNode cls, List<ArgType> superTypes, MethodNode mth) {
private void processMth(MethodNode mth, SuperTypesData superData) {
if (mth.isConstructor() || mth.getAccessFlags().isStatic() || mth.getAccessFlags().isPrivate()) {
return;
}
MethodOverrideAttr attr = processOverrideMethods(cls, mth, superTypes);
MethodOverrideAttr attr = processOverrideMethods(mth, superData);
if (attr != null) {
if (attr.getBaseMethods().isEmpty()) {
throw new JadxRuntimeException("No base methods for override attribute: " + attr.getOverrideList());
}
mth.addAttr(attr);
IMethodDetails baseMth = Utils.last(attr.getOverrideList());
IMethodDetails baseMth = Utils.getOne(attr.getBaseMethods());
if (baseMth != null) {
boolean updated = fixMethodReturnType(mth, baseMth, superTypes);
updated |= fixMethodArgTypes(mth, baseMth, superTypes);
if (updated && cls.root().getArgs().isRenameValid()) {
boolean updated = fixMethodReturnType(mth, baseMth, superData);
updated |= fixMethodArgTypes(mth, baseMth, superData);
if (updated) {
// check if new signature cause method collisions
fixMethodSignatureCollisions(mth);
checkMethodSignatureCollisions(mth, mth.root().getArgs().isRenameValid());
}
}
}
}
private MethodOverrideAttr processOverrideMethods(ClassNode cls, MethodNode mth, List<ArgType> superTypes) {
private MethodOverrideAttr processOverrideMethods(MethodNode mth, SuperTypesData superData) {
MethodOverrideAttr result = mth.get(AType.METHOD_OVERRIDE);
if (result != null) {
return result;
}
ClassNode cls = mth.getParentClass();
String signature = mth.getMethodInfo().makeSignature(false);
List<IMethodDetails> overrideList = new ArrayList<>();
for (ArgType superType : superTypes) {
ClassNode classNode = cls.root().resolveClass(superType);
Set<IMethodDetails> baseMethods = new HashSet<>();
for (ArgType superType : superData.getSuperTypes()) {
ClassNode classNode = mth.root().resolveClass(superType);
if (classNode != null) {
MethodNode ovrdMth = searchOverriddenMethod(classNode, signature);
if (ovrdMth != null && isMethodVisibleInCls(ovrdMth, cls)) {
overrideList.add(ovrdMth);
MethodOverrideAttr attr = ovrdMth.get(AType.METHOD_OVERRIDE);
if (attr != null) {
return buildOverrideAttr(mth, overrideList, attr);
if (ovrdMth != null) {
if (isMethodVisibleInCls(ovrdMth, cls)) {
overrideList.add(ovrdMth);
MethodOverrideAttr attr = ovrdMth.get(AType.METHOD_OVERRIDE);
if (attr != null) {
addBaseMethod(superData, overrideList, baseMethods, superType);
return buildOverrideAttr(mth, overrideList, baseMethods, attr);
}
}
}
} else {
ClspClass clsDetails = cls.root().getClsp().getClsDetails(superType);
ClspClass clsDetails = mth.root().getClsp().getClsDetails(superType);
if (clsDetails != null) {
Map<String, ClspMethod> methodsMap = clsDetails.getMethodsMap();
for (Map.Entry<String, ClspMethod> entry : methodsMap.entrySet()) {
String mthShortId = entry.getKey();
if (mthShortId.startsWith(signature)) {
overrideList.add(entry.getValue());
break;
}
}
}
}
addBaseMethod(superData, overrideList, baseMethods, superType);
}
return buildOverrideAttr(mth, overrideList, baseMethods, null);
}
private void addBaseMethod(SuperTypesData superData, List<IMethodDetails> overrideList, Set<IMethodDetails> baseMethods,
ArgType superType) {
if (superData.getEndTypes().contains(superType.getObject())) {
IMethodDetails last = Utils.last(overrideList);
if (last != null) {
baseMethods.add(last);
}
}
return buildOverrideAttr(mth, overrideList, null);
}
@Nullable
@@ -123,22 +141,24 @@ public class OverrideMethodVisitor extends AbstractVisitor {
@Nullable
private MethodOverrideAttr buildOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList,
@Nullable MethodOverrideAttr attr) {
Set<IMethodDetails> baseMethods, @Nullable MethodOverrideAttr attr) {
if (overrideList.isEmpty() && attr == null) {
return null;
}
if (attr == null) {
// traced to base method
List<IMethodDetails> cleanOverrideList = overrideList.stream().distinct().collect(Collectors.toList());
return applyOverrideAttr(mth, cleanOverrideList, false);
return applyOverrideAttr(mth, cleanOverrideList, baseMethods, false);
}
// trace stopped at already processed method -> start merging
List<IMethodDetails> mergedOverrideList = Utils.mergeLists(overrideList, attr.getOverrideList());
List<IMethodDetails> cleanOverrideList = mergedOverrideList.stream().distinct().collect(Collectors.toList());
return applyOverrideAttr(mth, cleanOverrideList, true);
Set<IMethodDetails> mergedBaseMethods = Utils.mergeSets(baseMethods, attr.getBaseMethods());
return applyOverrideAttr(mth, cleanOverrideList, mergedBaseMethods, true);
}
private MethodOverrideAttr applyOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList, boolean update) {
private MethodOverrideAttr applyOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList,
Set<IMethodDetails> baseMethods, boolean update) {
// don't rename method if override list contains not resolved method
boolean dontRename = overrideList.stream().anyMatch(m -> !(m instanceof MethodNode));
SortedSet<MethodNode> relatedMethods = null;
@@ -188,10 +208,10 @@ public class OverrideMethodVisitor extends AbstractVisitor {
continue;
}
}
mthNode.addAttr(new MethodOverrideAttr(Utils.listTail(overrideList, depth), relatedMethods));
mthNode.addAttr(new MethodOverrideAttr(Utils.listTail(overrideList, depth), relatedMethods, baseMethods));
depth++;
}
return new MethodOverrideAttr(overrideList, relatedMethods);
return new MethodOverrideAttr(overrideList, relatedMethods, baseMethods);
}
@NotNull
@@ -221,52 +241,92 @@ public class OverrideMethodVisitor extends AbstractVisitor {
return Objects.equals(superMth.getParentClass().getPackage(), cls.getPackage());
}
private List<ArgType> collectSuperTypes(ClassNode cls) {
Map<String, ArgType> superTypes = new LinkedHashMap<>();
collectSuperTypes(cls, superTypes);
if (superTypes.isEmpty()) {
return Collections.emptyList();
private static final class SuperTypesData {
private final List<ArgType> superTypes;
private final Set<String> endTypes;
private SuperTypesData(List<ArgType> superTypes, Set<String> endTypes) {
this.superTypes = superTypes;
this.endTypes = endTypes;
}
public List<ArgType> getSuperTypes() {
return superTypes;
}
public Set<String> getEndTypes() {
return endTypes;
}
return new ArrayList<>(superTypes.values());
}
private void collectSuperTypes(ClassNode cls, Map<String, ArgType> superTypes) {
@Nullable
private SuperTypesData collectSuperTypes(ClassNode cls) {
List<ArgType> superTypes = new ArrayList<>();
Set<String> endTypes = new HashSet<>();
collectSuperTypes(cls, superTypes, endTypes);
if (superTypes.isEmpty()) {
return null;
}
if (endTypes.isEmpty()) {
throw new JadxRuntimeException("No end types in class hierarchy: " + cls);
}
return new SuperTypesData(superTypes, endTypes);
}
private void collectSuperTypes(ClassNode cls, List<ArgType> superTypes, Set<String> endTypes) {
RootNode root = cls.root();
int k = 0;
ArgType superClass = cls.getSuperClass();
if (superClass != null && !Objects.equals(superClass, ArgType.OBJECT)) {
addSuperType(root, superTypes, superClass);
if (superClass != null) {
k += addSuperType(root, superTypes, endTypes, superClass);
}
for (ArgType iface : cls.getInterfaces()) {
addSuperType(root, superTypes, iface);
k += addSuperType(root, superTypes, endTypes, iface);
}
if (k == 0) {
endTypes.add(cls.getType().getObject());
}
}
private void addSuperType(RootNode root, Map<String, ArgType> superTypesMap, ArgType superType) {
superTypesMap.put(superType.getObject(), superType);
private int addSuperType(RootNode root, List<ArgType> superTypesMap, Set<String> endTypes, ArgType superType) {
if (Objects.equals(superType, ArgType.OBJECT)) {
return 0;
}
superTypesMap.add(superType);
ClassNode classNode = root.resolveClass(superType);
if (classNode == null) {
for (String superCls : root.getClsp().getSuperTypes(superType.getObject())) {
ArgType type = ArgType.object(superCls);
superTypesMap.put(type.getObject(), type);
}
} else {
collectSuperTypes(classNode, superTypesMap);
if (classNode != null) {
collectSuperTypes(classNode, superTypesMap, endTypes);
return 1;
}
ClspClass clsDetails = root.getClsp().getClsDetails(superType);
if (clsDetails != null) {
int k = 0;
for (ArgType parentType : clsDetails.getParents()) {
k += addSuperType(root, superTypesMap, endTypes, parentType);
}
if (k == 0) {
endTypes.add(superType.getObject());
}
return 1;
}
// no info found => treat as hierarchy end
endTypes.add(superType.getObject());
return 1;
}
private boolean fixMethodReturnType(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) {
private boolean fixMethodReturnType(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData) {
ArgType returnType = mth.getReturnType();
if (returnType == ArgType.VOID) {
return false;
}
boolean updated = updateReturnType(mth, baseMth, superTypes);
boolean updated = updateReturnType(mth, baseMth, superData);
if (updated) {
mth.addDebugComment("Return type fixed from '" + returnType + "' to match base method");
}
return updated;
}
private boolean updateReturnType(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) {
private boolean updateReturnType(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData) {
ArgType baseReturnType = baseMth.getReturnType();
if (mth.getReturnType().equals(baseReturnType)) {
return false;
@@ -276,7 +336,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
}
TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare();
ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType();
for (ArgType superType : superTypes) {
for (ArgType superType : superData.getSuperTypes()) {
TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls);
if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) {
ArgType targetRetType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseReturnType);
@@ -291,7 +351,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
return false;
}
private boolean fixMethodArgTypes(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) {
private boolean fixMethodArgTypes(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData) {
List<ArgType> mthArgTypes = mth.getArgTypes();
List<ArgType> baseArgTypes = baseMth.getArgTypes();
if (mthArgTypes.equals(baseArgTypes)) {
@@ -304,7 +364,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
boolean changed = false;
List<ArgType> newArgTypes = new ArrayList<>(argCount);
for (int argNum = 0; argNum < argCount; argNum++) {
ArgType newType = updateArgType(mth, baseMth, superTypes, argNum);
ArgType newType = updateArgType(mth, baseMth, superData, argNum);
if (newType != null) {
changed = true;
newArgTypes.add(newType);
@@ -318,7 +378,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
return changed;
}
private ArgType updateArgType(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes, int argNum) {
private ArgType updateArgType(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData, int argNum) {
ArgType arg = mth.getArgTypes().get(argNum);
ArgType baseArg = baseMth.getArgTypes().get(argNum);
if (arg.equals(baseArg)) {
@@ -329,7 +389,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
}
TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare();
ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType();
for (ArgType superType : superTypes) {
for (ArgType superType : superData.getSuperTypes()) {
TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls);
if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) {
ArgType targetArgType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseArg);
@@ -343,7 +403,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
return null;
}
private void fixMethodSignatureCollisions(MethodNode mth) {
private void checkMethodSignatureCollisions(MethodNode mth, boolean rename) {
String mthName = mth.getMethodInfo().getAlias();
String newSignature = MethodInfo.makeShortId(mthName, mth.getArgTypes(), null);
for (MethodNode otherMth : mth.getParentClass().getMethods()) {
@@ -351,12 +411,16 @@ public class OverrideMethodVisitor extends AbstractVisitor {
if (otherMthName.equals(mthName) && otherMth != mth) {
String otherSignature = otherMth.getMethodInfo().makeSignature(true, false);
if (otherSignature.equals(newSignature)) {
if (otherMth.contains(AFlag.DONT_RENAME) || otherMth.contains(AType.METHOD_OVERRIDE)) {
otherMth.addWarnComment("Can't rename method to resolve collision");
} else {
otherMth.getMethodInfo().setAlias(makeNewAlias(otherMth));
otherMth.addAttr(new RenameReasonAttr("avoid collision after fix types in other method"));
if (rename) {
if (otherMth.contains(AFlag.DONT_RENAME) || otherMth.contains(AType.METHOD_OVERRIDE)) {
otherMth.addWarnComment("Can't rename method to resolve collision");
} else {
otherMth.getMethodInfo().setAlias(makeNewAlias(otherMth));
otherMth.addAttr(new RenameReasonAttr("avoid collision after fix types in other method"));
}
}
otherMth.addAttr(new MethodBridgeAttr(mth));
return;
}
}
}
@@ -5,18 +5,28 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
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.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
@@ -24,6 +34,7 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnContainer;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
@@ -53,6 +64,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
if (cls.root().getArgs().isDebugInfo()) {
setClassSourceLine(cls);
}
collectFieldsUsageInAnnotations(cls);
return true;
}
@@ -69,8 +81,10 @@ public class PrepareForCodeGen extends AbstractVisitor {
checkInline(block);
removeParenthesis(block);
modifyArith(block);
checkConstUsage(block);
}
moveConstructorInConstructor(mth);
collectFieldsUsageInAnnotations(mth, mth);
}
private static void removeInstructions(BlockNode block) {
@@ -122,6 +136,38 @@ public class PrepareForCodeGen extends AbstractVisitor {
}
}
/**
* Add explicit type for non int constants
*/
private static void checkConstUsage(BlockNode block) {
for (InsnNode blockInsn : block.getInstructions()) {
blockInsn.visitInsns(insn -> {
if (forbidExplicitType(insn.getType())) {
return;
}
for (InsnArg arg : insn.getArguments()) {
if (arg.isLiteral() && arg.getType() != ArgType.INT) {
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
}
}
});
}
}
private static boolean forbidExplicitType(InsnType type) {
switch (type) {
case CONST:
case CAST:
case IF:
case FILLED_NEW_ARRAY:
case APUT:
case ARITH:
return true;
default:
return false;
}
}
private static void removeParenthesis(BlockNode block) {
for (InsnNode insn : block.getInstructions()) {
removeParenthesis(insn);
@@ -276,4 +322,61 @@ public class PrepareForCodeGen extends AbstractVisitor {
cls.setSourceLine(minLine - 1);
}
}
private void collectFieldsUsageInAnnotations(ClassNode cls) {
MethodNode useMth = cls.getDefaultConstructor();
if (useMth == null && !cls.getMethods().isEmpty()) {
useMth = cls.getMethods().get(0);
}
if (useMth == null) {
return;
}
collectFieldsUsageInAnnotations(useMth, cls);
MethodNode finalUseMth = useMth;
cls.getFields().forEach(f -> collectFieldsUsageInAnnotations(finalUseMth, f));
}
private void collectFieldsUsageInAnnotations(MethodNode mth, AttrNode attrNode) {
AnnotationsAttr annotationsList = attrNode.get(JadxAttrType.ANNOTATION_LIST);
if (annotationsList == null) {
return;
}
for (IAnnotation annotation : annotationsList.getAll()) {
if (annotation.getVisibility() == AnnotationVisibility.SYSTEM) {
continue;
}
for (Map.Entry<String, EncodedValue> entry : annotation.getValues().entrySet()) {
checkEncodedValue(mth, entry.getValue());
}
}
}
@SuppressWarnings("unchecked")
private void checkEncodedValue(MethodNode mth, EncodedValue encodedValue) {
switch (encodedValue.getType()) {
case ENCODED_FIELD:
Object fieldData = encodedValue.getValue();
FieldInfo fieldInfo;
if (fieldData instanceof IFieldRef) {
fieldInfo = FieldInfo.fromRef(mth.root(), (IFieldRef) fieldData);
} else {
fieldInfo = (FieldInfo) fieldData;
}
FieldNode fieldNode = mth.root().resolveField(fieldInfo);
if (fieldNode != null) {
fieldNode.addUseIn(mth);
}
break;
case ENCODED_ANNOTATION:
IAnnotation annotation = (IAnnotation) encodedValue.getValue();
annotation.getValues().forEach((k, v) -> checkEncodedValue(mth, v));
break;
case ENCODED_ARRAY:
List<EncodedValue> valueList = (List<EncodedValue>) encodedValue.getValue();
valueList.forEach(v -> checkEncodedValue(mth, v));
break;
}
}
}
@@ -1,11 +1,26 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
@@ -17,95 +32,267 @@ import jadx.core.utils.exceptions.JadxException;
)
public class ProcessAnonymous extends AbstractVisitor {
private boolean inlineAnonymous;
private boolean inlineAnonymousClasses;
@Override
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
public boolean visit(ClassNode cls) throws JadxException {
if (!inlineAnonymous) {
return false;
if (inlineAnonymousClasses && cls.contains(AFlag.CLASS_UNLOADED)) {
// enter only on class reload
visitClassAndInners(cls);
}
return false;
}
private void visitClassAndInners(ClassNode cls) {
markAnonymousClass(cls);
return true;
cls.getInnerClasses().forEach(this::visitClassAndInners);
}
private static void markAnonymousClass(ClassNode cls) {
if (usedOnlyOnce(cls) || isAnonymous(cls) || isLambdaCls(cls)) {
if (isStaticFieldUsedOutside(cls)) {
if (!canBeAnonymous(cls)) {
return;
}
MethodNode anonymousConstructor = checkUsage(cls);
if (anonymousConstructor == null) {
return;
}
ArgType baseType = getBaseType(cls);
if (baseType == null) {
return;
}
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
outerCls.addInlinedClass(cls);
cls.addAttr(new AnonymousClassAttr(outerCls, baseType));
cls.add(AFlag.DONT_GENERATE);
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
// force anonymous class to be processed before outer class,
// actual usage of outer class will be removed at anonymous class process,
// see ModVisitor.processAnonymousConstructor method
ClassNode topOuterCls = outerCls.getTopParentClass();
cls.removeDependency(topOuterCls);
ListUtils.safeRemove(outerCls.getUseIn(), cls);
// move dependency to codegen stage
if (cls.isTopClass()) {
topOuterCls.removeDependency(cls);
topOuterCls.addCodegenDep(cls);
}
}
private static void undoAnonymousMark(ClassNode cls) {
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
ClassNode outerCls = attr.getOuterCls();
cls.setDependencies(ListUtils.safeAdd(cls.getDependencies(), outerCls.getTopParentClass()));
outerCls.setUseIn(ListUtils.safeAdd(outerCls.getUseIn(), cls));
cls.remove(AType.ANONYMOUS_CLASS);
cls.remove(AFlag.DONT_GENERATE);
for (MethodNode mth : cls.getMethods()) {
if (mth.isConstructor()) {
mth.remove(AFlag.ANONYMOUS_CONSTRUCTOR);
}
}
cls.addDebugComment("Anonymous mark cleared");
}
private void mergeAnonymousDeps(RootNode root) {
// Collect edges to build bidirectional tree:
// inline edge: anonymous -> outer (one-to-one)
// use edges: outer -> *anonymous (one-to-many)
Map<ClassNode, ClassNode> inlineMap = new HashMap<>();
Map<ClassNode, List<ClassNode>> useMap = new HashMap<>();
for (ClassNode anonymousCls : root.getClasses()) {
AnonymousClassAttr attr = anonymousCls.get(AType.ANONYMOUS_CLASS);
if (attr != null) {
ClassNode outerCls = attr.getOuterCls();
List<ClassNode> list = useMap.get(outerCls);
if (list == null || list.isEmpty()) {
list = new ArrayList<>(2);
useMap.put(outerCls, list);
}
list.add(anonymousCls);
useMap.putIfAbsent(anonymousCls, Collections.emptyList()); // put leaf explicitly
inlineMap.put(anonymousCls, outerCls);
}
}
if (inlineMap.isEmpty()) {
return;
}
// starting from leaf process deps in nodes up to root
Set<ClassNode> added = new HashSet<>();
useMap.forEach((key, list) -> {
if (list.isEmpty()) {
added.clear();
updateDeps(key, inlineMap, added);
}
});
for (ClassNode cls : root.getClasses()) {
List<ClassNode> deps = cls.getCodegenDeps();
if (deps.size() > 1) {
// distinct sorted dep, reusing collections to reduce memory allocations :)
added.clear();
added.addAll(deps);
deps.clear();
deps.addAll(added);
Collections.sort(deps);
}
}
}
private void updateDeps(ClassNode leafCls, Map<ClassNode, ClassNode> inlineMap, Set<ClassNode> added) {
ClassNode topNode;
ClassNode current = leafCls;
while (true) {
if (!added.add(current)) {
current.addWarnComment("Loop in anonymous inline: " + current + ", path: " + added);
added.forEach(ProcessAnonymous::undoAnonymousMark);
return;
}
cls.add(AFlag.ANONYMOUS_CLASS);
cls.add(AFlag.DONT_GENERATE);
for (MethodNode mth : cls.getMethods()) {
if (mth.isConstructor()) {
mth.add(AFlag.ANONYMOUS_CONSTRUCTOR);
}
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 isStaticFieldUsedOutside(ClassNode cls) {
ClassNode topCls = cls.getTopParentClass();
for (FieldNode field : cls.getFields()) {
if (field.isStatic()) {
for (MethodNode useMth : field.getUseIn()) {
ClassNode useCls = useMth.getParentClass().getTopParentClass();
if (!useCls.equals(topCls)) {
return true;
}
}
}
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;
}
return false;
}
private static boolean usedOnlyOnce(ClassNode cls) {
if (cls.getUseIn().size() == 1 && cls.getUseInMth().size() == 1) {
// used only once
boolean synthetic = cls.getAccessFlags().isSynthetic() || cls.getClassInfo().getShortName().contains("$");
if (synthetic) {
// must have only one constructor which used only once
MethodNode ctr = null;
for (MethodNode mth : cls.getMethods()) {
if (mth.isConstructor()) {
if (ctr != null) {
ctr = null;
break;
}
ctr = mth;
}
}
return ctr != null && ctr.getUseIn().size() == 1;
}
MethodNode useMth = cls.getUseInMth().get(0);
// allow use in enum class init
return useMth.getMethodInfo().isClassInit() && useMth.getParentClass().isEnum();
}
return false;
}
private static boolean isAnonymous(ClassNode cls) {
return cls.getClassInfo().isInner()
&& Character.isDigit(cls.getClassInfo().getShortName().charAt(0))
&& cls.getMethods().stream().filter(MethodNode::isConstructor).count() == 1;
}
private static boolean isLambdaCls(ClassNode cls) {
return cls.getAccessFlags().isSynthetic()
&& cls.getAccessFlags().isFinal()
&& cls.getClassInfo().getRawName().contains(".-$$Lambda$")
&& countStaticFields(cls) == 0;
}
private static int countStaticFields(ClassNode cls) {
int c = 0;
for (FieldNode field : cls.getFields()) {
if (field.getAccessFlags().isStatic()) {
c++;
/**
* Checks:
* - class have only one constructor which used only once (allow common code for field init)
* - methods or fields not used outside (allow only nested inner classes with synthetic usage)
*
* @return anonymous constructor method
*/
private static MethodNode checkUsage(ClassNode cls) {
MethodNode ctr = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
if (ctr == null) {
return null;
}
if (ctr.getUseIn().size() != 1) {
// check if used in common field init in all constructors
if (!checkForCommonFieldInit(ctr)) {
return null;
}
}
return c;
MethodNode ctrUseMth = ctr.getUseIn().get(0);
ClassNode ctrUseCls = ctrUseMth.getParentClass();
if (ctrUseCls.equals(cls)) {
// exclude self usage
return null;
}
for (MethodNode mth : cls.getMethods()) {
if (mth == ctr) {
continue;
}
for (MethodNode useMth : mth.getUseIn()) {
if (useMth.equals(ctrUseMth)) {
continue;
}
if (badMethodUsage(cls, useMth, mth.getAccessFlags())) {
return null;
}
}
}
for (FieldNode field : cls.getFields()) {
for (MethodNode useMth : field.getUseIn()) {
if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
return null;
}
}
}
return ctr;
}
private static boolean badMethodUsage(ClassNode cls, MethodNode useMth, AccessInfo accessFlags) {
ClassNode useCls = useMth.getParentClass();
if (useCls.equals(cls)) {
return false;
}
if (accessFlags.isSynthetic()) {
// allow synthetic usage in inner class
return !useCls.getParentClass().equals(cls);
}
return true;
}
/**
* Checks:
* + all in constructors
* + all usage in one class
* - same field put (ignored: methods not loaded yet)
*/
private static boolean checkForCommonFieldInit(MethodNode ctrMth) {
List<MethodNode> ctrUse = ctrMth.getUseIn();
if (ctrUse.isEmpty()) {
return false;
}
ClassNode firstUseCls = ctrUse.get(0).getParentClass();
return ListUtils.allMatch(ctrUse, m -> m.isConstructor() && m.getParentClass().equals(firstUseCls));
}
@Nullable
private static ArgType getBaseType(ClassNode cls) {
int interfacesCount = cls.getInterfaces().size();
if (interfacesCount > 1) {
return null;
}
ArgType superCls = cls.getSuperClass();
if (superCls == null || superCls.equals(ArgType.OBJECT)) {
if (interfacesCount == 1) {
return cls.getInterfaces().get(0);
}
return ArgType.OBJECT;
}
if (interfacesCount == 0) {
return superCls;
}
// check if super class already implement that interface (weird case)
ArgType interfaceType = cls.getInterfaces().get(0);
if (cls.root().getClsp().isImplements(superCls.getObject(), interfaceType.getObject())) {
return superCls;
}
return null;
}
}

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