Compare commits

..

245 Commits

Author SHA1 Message Date
Skylot 87e0e5bf16 fix: correct inline/merge with overriden bridge method (#1580) 2022-07-20 14:49:37 +01:00
Skylot e4c2d6cf6e fix(gui): use editor font for usage label 2022-07-20 14:36:51 +01:00
Skylot fb0bdb5112 fix(gui): allow to use empty name to reset rename 2022-07-20 14:35:53 +01:00
Skylot f4b3645435 fix: ignore anonymous classes in enclosing node search (#1580) 2022-07-19 19:25:17 +01:00
Skylot c27f2badf7 fix(gui): resolve payload offset for switch insns in debug smali code (#1575) 2022-07-18 18:50:48 +01:00
Skylot 1a877d6535 fix: resolve possible decompilation double execution 2022-07-16 22:29:59 +03:00
Skylot 5ada9331b6 chore: update dependencies 2022-07-14 14:33:04 +01:00
Skylot a0f4ccb7a4 fix: update deps and fix proto resource loading (AAB) (#1129) 2022-07-14 14:33:04 +01:00
Skylot 5b5524a7dd fix: handle parent of inlined/moved classes (#1578) 2022-07-14 14:40:13 +03:00
Jan S 3cc464c9c9 fix: IndexOutOfBoundsException in JumpManager (#1576) (PR #1577) 2022-07-13 17:24:20 +01:00
Skylot 51555667cf fix: add more checks before remove or rename enum methods (#1572) 2022-07-07 16:55:32 +01:00
Skylot e01ea7010f fix: save classes with code generation error into cache (#1568) 2022-07-03 19:32:41 +01:00
Skylot 77732c83c9 fix(gui): ignore/limit waiting of canceled search task (#1568) 2022-07-01 17:57:59 +01:00
Skylot a67fc83949 fix: better dominators algorithms 2022-07-01 17:26:54 +01:00
Skylot 3d920725aa fix: check synthetic methods before remove/inline (#1560) 2022-06-29 19:19:54 +01:00
Skylot 2f2fbea558 fix(gui): check user renames (#1557) 2022-06-29 16:21:49 +01:00
Skylot e7a86a2960 fix(gui): forbid rename method args in fallback mode (#1558) 2022-06-29 15:25:23 +01:00
Skylot b282d97ffe fix(gui): set current dir directly in file chooser constructor (#1553) 2022-06-28 16:57:57 +01:00
Skylot e4ca52a95f chore: update dependencies 2022-06-28 16:23:31 +01:00
Skylot d972d9ec74 fix(gui): ignore errors on code area dispose (#1545) 2022-06-28 16:20:31 +01:00
Jan S 0721a6b050 fix(gui): QuarkReport data validation added and other minor improvements (PR #1556)
* QuarkReport: data validation added and other minor improvements
* checkStyle
2022-06-25 22:24:53 +03:00
zhongqingsong 762ee6550e fix(gui):complete Chinese translation (PR #1549)
1、Complete Chinese translation
2、Previous word polish
2022-06-25 22:15:22 +03:00
Skylot 18070eb7a6 fix(gui): allow to select file on mapping export 2022-06-20 14:19:59 +01:00
Skylot 8486891728 fix(gui): resolve various minor issues 2022-06-20 13:17:50 +01:00
Skylot 4679172d4f fix(gui): correct set highlighted text in search (#1507) 2022-06-20 13:16:42 +01:00
Skylot 92a6c333d8 fix(gui): force jadx new version check by default 2022-06-20 12:55:15 +01:00
Skylot 358adbdd65 feat(gui): allow to disable jump on double click (#1540) 2022-06-19 17:19:08 +01:00
Skylot 65f7c80222 feat(gui): add reload and live reload actions (#1537) 2022-06-18 20:20:11 +01:00
Skylot d2e6bb236e fix: use wide move for long/double store/load java opcodes 2022-06-16 16:26:14 +01:00
Skylot eaeb114258 fix: check class name collisions (#1526) 2022-06-15 18:43:33 +01:00
Skylot 1533b7fe6e fix: keep types on duplicate cast remove (#1527) 2022-06-12 21:55:12 +01:00
Julian Burner a2cd8e1ead feat(gui): support export to deobfuscation mapping file formats (#1491)(PR #1505)
* Add option to export mappings as Tiny v2 file

* Comply with JADX's import order conventions

* Only use Java 8 features

* Only use Java 8 features (2)

* Export comments to mappings file

* Method args test (doesn't work)

* Make method arg mapping exports work now

* Use `getTopParentClass()` instead of `getParentClass()`

See https://github.com/skylot/jadx/pull/1505#issuecomment-1145064865

* Remove unneeded method load call

* Small code cleanup; initial (broken) support for method vars

* Fixes regarding inner classes

* Add option to export mappings as Enigma directory

* Add option to export mappings as Enigma file/directory

Temporarily move to my mapping-io fork until this PR gets merged: https://github.com/FabricMC/mapping-io/pull/19

* Fix method vars' lv-indices

* Use correct offset value for method var mappings

* Also supply lvt-index for method var mappings

* Clarify why we're using local mapping-io fork; comment out Fabric Maven for now

* Remove unnecessary `public` modifier

* Make an `if` condition less complicated

* Move mapping export code into jadx-gui (for now)

* Make mapping export async; make export menu only clickable when everything is loaded

* Fix export mappings menu field declaration position
2022-06-11 20:19:08 +01:00
Christian Jones 4edb512121 fix(cli): allow decoding resource-only APKs (#1517)(PR #1530)
* Process resource-only inputs
* Fix error, add testing
2022-06-11 15:40:39 +01:00
Skylot 702b88228c fix(gui): resolve popup menu action run (#1514, #1529) 2022-06-11 15:08:28 +01:00
Skylot 14fd88b2f8 fix(gui): add volatile and update sync for decompiler field in wrapper (#1518) 2022-06-08 21:06:57 +01:00
Skylot 20657e8bb5 doc(cli): improve plugins section formatting 2022-06-06 19:55:58 +01:00
Skylot 93d3194e3b doc: remove incorrect tokei badge 2022-06-06 19:54:15 +01:00
Skylot 39331d9120 fix: remove deprecated --deobf-rewrite-cfg (#1513) 2022-06-06 19:53:47 +01:00
Skylot b4fa6644bc doc: add more badges 2022-06-05 16:06:19 +01:00
Skylot 0b2e2ed034 fix: improve class search for super call (#1512) 2022-06-05 14:49:34 +01:00
Skylot 81231206f3 fix(gui): reset disk cache on new jadx version 2022-06-04 23:26:25 +01:00
Skylot 49d0e76272 fix: support all-catch in multi-catch (#1510) 2022-06-04 23:25:52 +01:00
CmP-lt 0809993b37 fix(gui): improve restoration of windows saved state (PR #1511)
* Fix restoration of windows saved state
* Don't skip restoration of window saved bounds when they intersect with screen bounds.
* Restore saved bounds of main window regardless of it's saved extended state (fixes divider location of main split pane being restored incorrectly when saved state of main window is maximized).
* Add handling for out-of-screen(s) window bounds
2022-06-04 17:41:00 +01:00
Skylot 0c3afcc24c fix(gui): try to prevent jadx node leaks in UI objects 2022-06-03 16:15:14 +01:00
Skylot d6c851eed4 test: fix method code extract 2022-06-02 19:33:16 +01:00
Skylot dcf4a7c4e3 fix(gui): try to resolve some causes of memory leak 2022-06-01 19:48:51 +01:00
Skylot 9ba07b986b fix(gui): reduce usage of nullable decompiler field in jadx wrapper (#1506) 2022-06-01 16:36:30 +01:00
Skylot e6b6b93cbb fix: improve blocks tree compare for finally extract (#1501) 2022-05-31 20:57:34 +01:00
Skylot fcd58ae76f fix: remap class names for store in disk cache (#1503) 2022-05-30 18:16:05 +01:00
Skylot df380dea27 chore: update dependencies 2022-05-30 20:06:23 +03:00
CKCat 9d88592391 fix(res): ignore version in AndroidManifest.xml (#1502)(PR #1504) 2022-05-30 14:10:10 +01:00
dependabot[bot] c906c11b0f build(deps): bump github/codeql-action from 1 to 2 (PR #1500)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1 to 2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-28 18:04:08 +01:00
Skylot 4fbc56cdb0 build: add unstable win-with-jre bundle 2022-05-28 17:46:48 +01:00
Skylot 98c0416b20 fix(gui): correct close and reopen for decompiler and cache 2022-05-28 16:41:37 +03:00
ZachQin fa41874e30 feat(gui): add parameters logging in Frida code snippet (#1497)(PR #1498) 2022-05-28 14:39:02 +01:00
Skylot 2aa6c99c90 fix: skip dex files with parsing errors (#1495) 2022-05-28 13:27:29 +01:00
Skylot 5f60c0f1bb build: fix google-java-format for all java versions 2022-05-27 22:51:52 +01:00
Skylot cb741db623 fix: improve usage search, refactor java nodes creation (#1489) 2022-05-27 17:56:08 +01:00
Skylot 1df217c4a0 fix: save cache dir for reuse on project save/reopen 2022-05-27 16:50:13 +01:00
Skylot 81f209ba9e fix: check if directory exists before delete (#1493) 2022-05-26 19:23:59 +03:00
zhongqingsong 34a31aa7df fix(gui): complete Chinese Translation (PR #1492)
1. Complete translation of Chinese
2. Polish up part of the translation
3. Restore all text for subsequent translation
2022-05-24 19:09:11 +01:00
Skylot 5099e02c9b fix(gui): correct merge for plugin options from command line (#1490) 2022-05-23 19:44:49 +01:00
Skylot f364b39b29 fix(gui): save full type info in metadata (#1487) 2022-05-22 16:18:38 +01:00
Skylot 4cd4746f9a fix(gui): save variable name to show in tooltip (#1487) 2022-05-22 15:34:09 +01:00
Skylot 6448f0e32b fix: use variable length encoding instead short for offsets (can overflow) (#1489) 2022-05-22 14:23:15 +01:00
Skylot e07332d49a fix(gui): resolve cast exception for variable reference (#1489) 2022-05-21 21:33:58 +01:00
Skylot bd8a44c4c9 fix(gui): correct handle of selected file in save dialog 2022-05-21 21:31:47 +01:00
Skylot 21e94d8d5c fix(gui): use alias for types in tooltips (#1487) 2022-05-20 22:09:39 +01:00
Skylot 7b1c7b967a fix: use alias for variable names (#1487) 2022-05-20 22:09:39 +01:00
Skylot e4b19ab560 fix(gui): add missing Use debug info option 2022-05-20 22:09:39 +01:00
Skylot 49137c9751 fix(cli): don't ignore critical errors (#1150)
Thrown java.lang.Error was ignored and not logged.
2022-05-19 23:12:19 +01:00
skylot 0606c90f22 feat(gui): disk code cache and search rewrite (PR #1483)
* feat: implement disk code cache
* feat: rewrite code metadata handling, remove code index
* feat: rewrite search
* fix: code cleanup and fixes for previous commits
* feat: run code search in parallel
* fix: reset code strings cache on low memory, code cleanup
* fix: include input files timestamp into code hash
2022-05-18 15:19:31 +01:00
Jan S 65ade379a6 fix(gui): escape class- method and field names in frida code snippet (PR #1480) 2022-05-10 19:43:15 +01:00
Skylot a06df187c9 fix(gui): ask for project file path on exit (#1474) 2022-05-08 14:55:25 +03:00
Jan S e784c7f7df fix(gui): editor theme loading and error/fallback handling improved (#1476)(PR #1478)
* fix(gui): editor theme loading and error/fallback handling improved
* Update jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
2022-05-08 12:53:48 +01:00
Skylot a717392379 fix: workaround to prevent incorrect order after move inline (#1472) 2022-04-29 15:37:07 +01:00
Skylot a71b3a71d8 fix: better code styling for if-else blocks (#1455) 2022-04-26 20:18:06 +01:00
Skylot 3366bf3dec chore: update dependencies 2022-04-26 20:48:18 +03:00
Jan S a505534197 fix(gui): fix IndexOutOfBoundsException when switching between tabs via mouse wheel (#1456)(PR #1469) 2022-04-26 17:27:39 +01:00
Nelson Gregório 357706b070 feat: allow to include/exclude dependencies when saving with class filter (#1466)(PR #1467)
* feat: Add option to include/exclude dependencies when saving
* fix save skip for class depencencies

Co-authored-by: Skylot <skylot@gmail.com>
2022-04-26 17:18:51 +01:00
Skylot e02434d135 fix(gui): confirm directory loading on file open (#1462) 2022-04-25 14:32:00 +03:00
Jan S 4586015fc0 fix(gui): resolve NPE on project save (#1463)(PR #1464)
* fix(gui): NullPointerException on project save

* chore(gui): ensure MainWindow.project is never null
* ensure `files` in `ProjectData` in not null

Co-authored-by: Skylot <skylot@gmail.com>
2022-04-24 10:32:25 +01:00
Skylot 1832f2aee3 feat: allow to load custom input (#1457) 2022-04-21 13:21:13 +01:00
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
556 changed files with 23763 additions and 7329 deletions
+7
View File
@@ -0,0 +1,7 @@
version: 2
updates:
# Set update schedule for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
+86
View File
@@ -0,0 +1,86 @@
name: Build Artifacts
on:
push:
branches: [ master, build-test ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: 8
- name: Set jadx version
run: |
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- uses: burrunan/gradle-cache-action@v1
name: Build with Gradle
env:
TERM: dumb
with:
arguments: clean dist copyExe
- name: Save bundle artifact
uses: actions/upload-artifact@v3
with:
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
# Upload unpacked files for now
path: build/jadx/**/*
if-no-files-found: error
retention-days: 30
- name: Save exe artifact
uses: actions/upload-artifact@v3
with:
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
path: build/*.exe
if-no-files-found: error
retention-days: 30
build-win-bundle:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK
uses: oracle-actions/setup-java@v1 # set latest java version by default
- name: Print Java version
shell: bash
run: java -version
- name: Set jadx version
shell: bash
run: |
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- uses: gradle/gradle-build-action@v2
name: Build with Gradle
env:
TERM: dumb
with:
arguments: clean dist -PbundleJRE=true
- name: Save exe bundle artifact
uses: actions/upload-artifact@v3
with:
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
path: jadx-gui/build/*-with-jre-win/*
if-no-files-found: error
retention-days: 30
+28
View File
@@ -0,0 +1,28 @@
name: Build Test
on:
push:
branches: [ master, build-test ]
pull_request:
branches: [ master ]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: 8
- uses: burrunan/gradle-cache-action@v1
name: Build with Gradle
env:
TERM: dumb
with:
arguments: clean build dist copyExe --warning-mode=all
-52
View File
@@ -1,52 +0,0 @@
name: Build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 8
- name: Set jadx version
run: |
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- uses: burrunan/gradle-cache-action@v1
name: Build with Gradle
env:
TERM: dumb
TEST_INPUT_PLUGIN: dx
with:
arguments: clean build dist copyExe --warning-mode=all
- name: Save bundle artifact
if: success() && github.event_name == 'push'
uses: actions/upload-artifact@v2
with:
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
# Upload unpacked files for now
path: build/jadx/**/*
if-no-files-found: error
- name: Save exe artifact
if: success() && github.event_name == 'push'
uses: actions/upload-artifact@v2
with:
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
path: build/*.exe
if-no-files-found: error
+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@v2
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@v2
+3
View File
@@ -34,3 +34,6 @@ jadx-output/
*.cfg
*.orig
quark.json
cliff.toml
jadx-gui/src/main/resources/logback.xml
+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
+50 -14
View File
@@ -3,14 +3,19 @@
## JADX
[![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)
![GitHub contributors](https://img.shields.io/github/contributors/skylot/jadx)
![GitHub all releases](https://img.shields.io/github/downloads/skylot/jadx/total)
![GitHub release (latest by SemVer)](https://img.shields.io/github/downloads/skylot/jadx/latest/total)
![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)
[![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,15 +26,18 @@ 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)
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
### Download
- release from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
- latest [unstable build](https://nightly.link/skylot/jadx/workflows/build/master)
- release
from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
- latest [unstable build ![GitHub commits since tagged version (branch)](https://img.shields.io/github/commits-since/skylot/jadx/latest/master)](https://nightly.link/skylot/jadx/workflows/build-artifacts/master)
After download unpack zip file go to `bin` directory and run:
- `jadx` - command line version
@@ -37,17 +45,24 @@ 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
1. Arch linux ![Arch Linux package](https://img.shields.io/archlinux/v/community/any/jadx?label=)
```bash
sudo pacman -S jadx
sudo pacman -S jadx
```
2. macOS
2. macOS ![homebrew version](https://img.shields.io/homebrew/v/jadx?label=)
```bash
brew install jadx
brew install jadx
```
3. [Flathub ![Flathub](https://img.shields.io/flathub/v/com.github.skylot.jadx?label=)](https://flathub.org/apps/details/com.github.skylot.jadx)
```bash
flatpak install flathub com.github.skylot.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 +86,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 +109,14 @@ 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-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 +126,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
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
2) java-convert: Convert .class, .jar and .aar files to dex
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
- java-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 -38
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.8.0'
}
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -10,12 +10,10 @@ println("jadx version: ${jadxVersion}")
allprojects {
apply plugin: 'java'
apply plugin: 'checkstyle'
apply plugin: 'maven-publish'
version = jadxVersion
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
compileJava {
options.encoding = "UTF-8"
@@ -27,38 +25,35 @@ 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.6.1'
testImplementation 'org.assertj:assertj-core:3.23.1'
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 {
mavenLocal()
mavenCentral()
google()
// Commented out for now since we're using a local mapping-io fork atm.
// maven {
// name 'FabricMC'
// url 'https://maven.fabricmc.net/'
// }
}
}
@@ -73,12 +68,7 @@ spotless {
importOrderFile 'config/code-formatter/eclipse.importorder'
eclipse().configFile 'config/code-formatter/eclipse.xml'
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')
}
removeUnusedImports()
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8")
@@ -135,19 +125,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 +135,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
}
+10 -1
View File
@@ -1,2 +1,11 @@
org.gradle.daemon=false
org.gradle.warning.mode=all
org.gradle.parallel=true
# Flags for google-java-format (optimize imports by spotless) for Java >= 16.
# Java < 9 will ignore unsupported flags (thanks to -XX:+IgnoreUnrecognizedVMOptions)
org.gradle.jvmargs=-XX:+IgnoreUnrecognizedVMOptions \
--add-exports='jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' \
--add-exports='jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED' \
--add-exports='jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED' \
--add-exports='jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED' \
--add-exports='jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
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 {
@@ -8,6 +8,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
@@ -17,6 +18,12 @@ 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;
import jadx.core.utils.Utils;
public class JCommanderWrapper<T> {
private final JCommander jc;
@@ -45,12 +52,24 @@ public class JCommanderWrapper<T> {
if (parameter.isAssigned()) {
// copy assigned field value to obj
Parameterized parameterized = parameter.getParameterized();
Object val = parameterized.get(parameter.getObject());
parameterized.set(obj, val);
Object providedValue = parameterized.get(parameter.getObject());
Object newValue = mergeValues(parameterized.getType(), providedValue, () -> parameterized.get(obj));
parameterized.set(obj, newValue);
}
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static Object mergeValues(Class<?> type, Object value, Supplier<Object> prevValueProvider) {
if (type.isAssignableFrom(Map.class)) {
// merge maps instead replacing whole map
Map prevMap = (Map) prevValueProvider.get();
return Utils.mergeMaps(prevMap, (Map) value); // value map will override keys in prevMap
}
// simple override
return value;
}
public void printUsage() {
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
@@ -70,40 +89,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 +168,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());
for (OptionDescription desc : descs) {
StringBuilder opt = new StringBuilder();
opt.append(" - ").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;
}
}
+46 -11
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;
@@ -20,8 +21,8 @@ public class JadxCLI {
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
result = 1;
} catch (Exception e) {
LOG.error("jadx error: {}", e.getMessage(), e);
} catch (Throwable 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,36 @@ public class JadxCLI {
}
return 0;
}
private static boolean checkForErrors(JadxDecompiler jadx) {
if (jadx.getRoot().getClasses().isEmpty()) {
if (jadx.getArgs().isSkipResources()) {
LOG.error("Load failed! No classes for decompile!");
return true;
}
if (!jadx.getArgs().isSkipSources()) {
LOG.warn("No classes to decompile; decoding resources only");
jadx.getArgs().setSkipSources(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");
}
}
}
+125 -14
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,8 +112,16 @@ public class JadxCLIArgs {
)
protected String deobfuscationMapFile;
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "force to ignore and overwrite deobfuscation map file")
protected boolean deobfuscationForceSave = false;
@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-use-sourcename" }, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = false;
@@ -101,6 +129,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 +157,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 +189,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 +227,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 +244,24 @@ 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);
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 +273,8 @@ public class JadxCLIArgs {
args.setRenameFlags(renameFlags);
args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel);
args.setUseDxInput(useDx);
args.setPluginOptions(pluginOptions);
return args;
}
@@ -250,6 +294,14 @@ public class JadxCLIArgs {
return outDirRes;
}
public String getSingleClass() {
return singleClass;
}
public String getSingleClassOutput() {
return singleClassOutput;
}
public boolean isSkipResources() {
return skipResources;
}
@@ -266,6 +318,14 @@ public class JadxCLIArgs {
return fallbackMode;
}
public boolean isUseDx() {
return useDx;
}
public DecompilationMode getDecompilationMode() {
return decompilationMode;
}
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
@@ -306,8 +366,8 @@ public class JadxCLIArgs {
return deobfuscationMapFile;
}
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
return deobfuscationMapFileMode;
}
public boolean isDeobfuscationUseSourceNameAsAlias() {
@@ -318,6 +378,10 @@ public class JadxCLIArgs {
return deobfuscationParseKotlinMetadata;
}
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public boolean isEscapeUnicode() {
return escapeUnicode;
}
@@ -362,6 +426,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 +476,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));
@@ -1,9 +1,14 @@
package jadx.cli;
import java.util.Collections;
import java.util.Map;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.utils.Utils.newConstStringMap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -47,6 +52,40 @@ public class JadxCLIArgsTest {
assertThat(override(args, "").isUseImports(), is(false));
}
@Test
public void testPluginOptionsOverride() {
// add key to empty base map
checkPluginOptionsMerge(
Collections.emptyMap(),
"-Poption=otherValue",
newConstStringMap("option", "otherValue"));
// override one key
checkPluginOptionsMerge(
newConstStringMap("option", "value"),
"-Poption=otherValue",
newConstStringMap("option", "otherValue"));
// merge different keys
checkPluginOptionsMerge(
Collections.singletonMap("option1", "value1"),
"-Poption2=otherValue2",
newConstStringMap("option1", "value1", "option2", "otherValue2"));
// merge and override
checkPluginOptionsMerge(
newConstStringMap("option1", "value1", "option2", "value2"),
"-Poption2=otherValue2",
newConstStringMap("option1", "value1", "option2", "otherValue2"));
}
private void checkPluginOptionsMerge(Map<String, String> baseMap, String providedArgs, Map<String, String> expectedMap) {
JadxCLIArgs args = new JadxCLIArgs();
args.pluginOptions = baseMap;
Map<String, String> resultMap = override(args, providedArgs).getPluginOptions();
assertThat(resultMap, Matchers.equalTo(expectedMap));
}
private JadxCLIArgs parse(String... args) {
return parse(new JadxCLIArgs(), args);
}
@@ -44,6 +44,33 @@ public class TestInput {
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
}
@Test
public void testResourceOnly() throws Exception {
decode("resourceOnly", "samples/resources-only.apk");
}
private void decode(String tmpDirName, String apkSample) throws URISyntaxException, IOException {
List<String> args = new ArrayList<>();
Path tempDir = FileUtils.createTempDir(tmpDirName);
args.add("-v");
args.add("-d");
args.add(tempDir.toAbsolutePath().toString());
URL resource = getClass().getClassLoader().getResource(apkSample);
assertThat(resource).isNotNull();
String sampleFile = resource.toURI().getRawPath();
args.add(sampleFile);
int result = JadxCLI.execute(args.toArray(new String[0]));
assertThat(result).isEqualTo(0);
List<Path> files = Files.find(
tempDir,
3,
(file, attr) -> file.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"))
.collect(Collectors.toList());
assertThat(files.isEmpty()).isFalse();
}
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
List<String> args = new ArrayList<>();
Path tempDir = FileUtils.createTempDir(tmpDirName);
+9 -9
View File
@@ -1,26 +1,26 @@
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.android.tools.build:aapt2-proto:4.2.1-7147631'
constraints {
// Force protobuf version to prevent Java-7 issue
implementation 'com.google.protobuf:protobuf-java:3.11.4'
}
implementation 'com.google.code.gson:gson:2.9.0'
// TODO: move resources decoding to separate plugin module
implementation 'com.android.tools.build:aapt2-proto:7.2.1-7984345'
implementation 'com.google.protobuf:protobuf-java:3.21.2' // forcing latest version
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
testImplementation(project(':jadx-plugins:jadx-dex-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
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.30.0'
testImplementation 'tools.profiler:async-profiler:1.8.3'
}
test {
@@ -1,65 +0,0 @@
package jadx.api;
public final class CodePosition {
private final int line;
private final int offset;
private final int pos;
public CodePosition(int line, int offset, int pos) {
this.line = line;
this.offset = offset;
this.pos = pos;
}
public CodePosition(int line) {
this(line, 0, -1);
}
@Deprecated
public CodePosition(int line, int offset) {
this(line, offset, -1);
}
public int getPos() {
return pos;
}
public int getLine() {
return line;
}
public int getOffset() {
return offset;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CodePosition that = (CodePosition) o;
return line == that.line && offset == that.offset;
}
@Override
public int hashCode() {
return line + 31 * offset;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(line);
if (offset != 0) {
sb.append(':').append(offset);
}
if (pos > 0) {
sb.append('@').append(pos);
}
return sb.toString();
}
}
@@ -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
}
@@ -1,13 +1,21 @@
package jadx.api;
import java.io.Closeable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface ICodeCache {
public interface ICodeCache extends Closeable {
void add(String clsFullName, ICodeInfo codeInfo);
void remove(String clsFullName);
@Nullable
@NotNull
ICodeInfo get(String clsFullName);
@Nullable
String getCode(String clsFullName);
boolean contains(String clsFullName);
}
@@ -1,8 +1,7 @@
package jadx.api;
import java.util.Map;
import jadx.api.impl.SimpleCodeInfo;
import jadx.api.metadata.ICodeMetadata;
public interface ICodeInfo {
@@ -10,7 +9,7 @@ public interface ICodeInfo {
String getCodeStr();
Map<Integer, Integer> getLineMapping();
ICodeMetadata getCodeMetadata();
Map<CodePosition, Object> getAnnotations();
boolean hasMetadata();
}
@@ -2,7 +2,10 @@ package jadx.api;
import java.util.Map;
import jadx.core.dex.attributes.ILineAttributeNode;
import org.jetbrains.annotations.ApiStatus;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
public interface ICodeWriter {
String NL = System.getProperty("line.separator");
@@ -38,13 +41,21 @@ public interface ICodeWriter {
void setIndent(int indent);
/**
* Return current line (only if metadata is supported)
*/
int getLine();
void attachDefinition(ILineAttributeNode obj);
/**
* Return start line position (only if metadata is supported)
*/
int getLineStartPos();
void attachAnnotation(Object obj);
void attachDefinition(ICodeNodeRef obj);
void attachLineAnnotation(Object obj);
void attachAnnotation(ICodeAnnotation obj);
void attachLineAnnotation(ICodeAnnotation obj);
void attachSourceLine(int sourceLine);
@@ -56,5 +67,6 @@ public interface ICodeWriter {
StringBuilder getRawBuf();
Map<CodePosition, Object> getRawAnnotations();
@ApiStatus.Internal
Map<Integer, ICodeAnnotation> getRawAnnotations();
}
+142 -11
View File
@@ -1,19 +1,28 @@
package jadx.api;
import java.io.File;
import java.nio.charset.StandardCharsets;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.data.ICodeData;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache;
import jadx.core.utils.files.FileUtils;
public class JadxArgs {
private static final Logger LOG = LoggerFactory.getLogger(JadxArgs.class);
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
@@ -35,7 +44,6 @@ public class JadxArgs {
private boolean cfgOutput = false;
private boolean rawCFGOutput = false;
private boolean fallbackMode = false;
private boolean showInconsistentCode = false;
private boolean useImports = true;
@@ -53,12 +61,18 @@ public class JadxArgs {
*/
private Predicate<String> classFilter = null;
/**
* Save dependencies for classes accepted by {@code classFilter}
*/
private boolean includeDependencies = false;
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 +95,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
}
@@ -95,6 +126,19 @@ public class JadxArgs {
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
}
public void close() {
try {
inputFiles = null;
if (codeCache != null) {
codeCache.close();
}
} catch (Exception e) {
LOG.error("Failed to close JadxArgs", e);
} finally {
codeCache = null;
}
}
public List<File> getInputFiles() {
return inputFiles;
}
@@ -136,7 +180,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 +200,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() {
@@ -235,6 +285,14 @@ public class JadxArgs {
this.skipSources = skipSources;
}
public void setIncludeDependencies(boolean includeDependencies) {
this.includeDependencies = includeDependencies;
}
public boolean isIncludeDependencies() {
return includeDependencies;
}
public Predicate<String> getClassFilter() {
return classFilter;
}
@@ -251,12 +309,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 +461,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 +501,53 @@ 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;
}
/**
* Hash of all options that can change result code
*/
public String makeCodeArgsHash() {
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
+ inlineAnonymousClasses + inlineMethods
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
+ parseKotlinMetadata + useKotlinMethodsForVarNames
+ insertDebugLines + extractFinally
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
+ commentsLevel + useDxInput + pluginOptions;
return FileUtils.md5Sum(argStr.getBytes(StandardCharsets.US_ASCII));
}
@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -430,18 +555,20 @@ 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
+ ", includeDependencies=" + includeDependencies
+ ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationMapFile=" + deobfuscationMapFile
+ ", deobfuscationForceSave=" + deobfuscationForceSave
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
+ ", insertDebugLines=" + insertDebugLines
+ ", extractFinally=" + extractFinally
+ ", deobfuscationMinLength=" + deobfuscationMinLength
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
+ ", escapeUnicode=" + escapeUnicode
@@ -454,6 +581,10 @@ public class JadxArgs {
+ ", commentsLevel=" + commentsLevel
+ ", codeCache=" + codeCache
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
+ ", useDxInput=" + useDxInput
+ ", pluginOptions=" + pluginOptions
+ ", cfgOutput=" + cfgOutput
+ ", rawCFGOutput=" + rawCFGOutput
+ '}';
}
}
@@ -13,8 +13,9 @@ public class JadxArgsValidator {
private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class);
public static void validate(JadxArgs args) {
checkInputFiles(args);
public static void validate(JadxDecompiler jadx) {
JadxArgs args = jadx.getArgs();
checkInputFiles(jadx, args);
validateOutDirs(args);
if (LOG.isDebugEnabled()) {
@@ -22,9 +23,9 @@ public class JadxArgsValidator {
}
}
private static void checkInputFiles(JadxArgs args) {
private static void checkInputFiles(JadxDecompiler jadx, JadxArgs args) {
List<File> inputFiles = args.getInputFiles();
if (inputFiles.isEmpty()) {
if (inputFiles.isEmpty() && jadx.getCustomLoads().isEmpty()) {
throw new JadxArgsValidateException("Please specify input file");
}
for (File inputFile : inputFiles) {
@@ -66,19 +67,22 @@ public class JadxArgsValidator {
@NotNull
private static File makeDirFromInput(JadxArgs args) {
File outDir;
String outDirName;
File file = args.getInputFiles().get(0);
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1) {
outDirName = name.substring(0, pos);
List<File> inputFiles = args.getInputFiles();
if (inputFiles.isEmpty()) {
outDirName = JadxArgs.DEFAULT_OUT_DIR;
} else {
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
File file = inputFiles.get(0);
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1) {
outDirName = name.substring(0, pos);
} else {
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
}
}
LOG.info("output directory: {}", outDirName);
outDir = new File(outDirName);
return outDir;
return new File(outDirName);
}
private static void checkFile(File file) {
@@ -25,14 +25,18 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.data.annotations.VarRef;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.metadata.annotations.VarRef;
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.nodes.LineAttrNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
@@ -93,7 +97,9 @@ public final class JadxDecompiler implements Closeable {
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
private final List<ILoadResult> customLoads = new ArrayList<>();
public JadxDecompiler() {
this(new JadxArgs());
@@ -105,8 +111,9 @@ public final class JadxDecompiler implements Closeable {
public void load() {
reset();
JadxArgsValidator.validate(args);
JadxArgsValidator.validate(this);
LOG.info("loading ...");
loadPlugins(args);
loadInputFiles();
root = new RootNode(args);
@@ -121,12 +128,25 @@ 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);
}
}
loadedInputs.addAll(customLoads);
if (LOG.isDebugEnabled()) {
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
}
}
public void addCustomLoad(ILoadResult customLoad) {
customLoads.add(customLoad);
}
public List<ILoadResult> getCustomLoads() {
return customLoads;
}
private void reset() {
@@ -139,8 +159,13 @@ public final class JadxDecompiler implements Closeable {
classesMap.clear();
methodsMap.clear();
fieldsMap.clear();
}
@Override
public void close() {
reset();
closeInputs();
args.close();
}
private void closeInputs() {
@@ -154,11 +179,28 @@ public final class JadxDecompiler implements Closeable {
loadedInputs.clear();
}
@Override
public void close() {
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);
}
}
}
}
@SuppressWarnings("unused")
public void registerPlugin(JadxPlugin plugin) {
pluginManager.register(plugin);
}
@@ -200,6 +242,7 @@ public final class JadxDecompiler implements Closeable {
save(false, true);
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private void save(boolean saveSources, boolean saveResources) {
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
ex.shutdown();
@@ -272,6 +315,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
@@ -288,20 +334,31 @@ public final class JadxDecompiler implements Closeable {
List<JavaClass> classes = getClasses();
List<JavaClass> processQueue = new ArrayList<>(classes.size());
for (JavaClass cls : classes) {
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
ClassNode clsNode = cls.getClassNode();
if (clsNode.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (classFilter != null && !classFilter.test(cls.getFullName())) {
if (classFilter != null && !classFilter.test(clsNode.getClassInfo().getFullName())) {
if (!args.isIncludeDependencies()) {
clsNode.add(AFlag.DONT_GENERATE);
}
continue;
}
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 {
ICodeInfo code = cls.getCodeInfo();
SaveCode.save(outDir, cls.getClassNode(), code);
ClassNode clsNode = cls.getClassNode();
ICodeInfo code = clsNode.getCode();
SaveCode.save(outDir, clsNode, code);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls, e);
}
@@ -315,14 +372,14 @@ public final class JadxDecompiler implements Closeable {
return Collections.emptyList();
}
if (classes == null) {
List<ClassNode> classNodeList = root.getClasses(false);
List<ClassNode> classNodeList = root.getClasses();
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
classesMap.clear();
for (ClassNode classNode : classNodeList) {
if (!classNode.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(classNode, this);
clsList.add(javaClass);
classesMap.put(classNode, javaClass);
if (classNode.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (!classNode.getClassInfo().isInner()) {
clsList.add(convertClassNode(classNode));
}
}
classes = Collections.unmodifiableList(clsList);
@@ -330,6 +387,10 @@ public final class JadxDecompiler implements Closeable {
return classes;
}
public List<JavaClass> getClassesWithInners() {
return Utils.collectionMap(root.getClasses(), this::convertClassNode);
}
public List<ResourceFile> getResources() {
if (resources == null) {
if (root == null) {
@@ -387,6 +448,7 @@ public final class JadxDecompiler implements Closeable {
/**
* Internal API. Not Stable!
*/
@ApiStatus.Internal
public RootNode getRoot() {
return root;
}
@@ -405,19 +467,10 @@ public final class JadxDecompiler implements Closeable {
return protoXmlParser;
}
private void loadJavaClass(JavaClass javaClass) {
javaClass.getMethods().forEach(mth -> methodsMap.put(mth.getMethodNode(), mth));
javaClass.getFields().forEach(fld -> fieldsMap.put(fld.getFieldNode(), fld));
for (JavaClass innerCls : javaClass.getInnerClasses()) {
classesMap.put(innerCls.getClassNode(), innerCls);
loadJavaClass(innerCls);
}
}
/**
* Get JavaClass by ClassNode without loading and decompilation
*/
@ApiStatus.Internal
JavaClass convertClassNode(ClassNode cls) {
return classesMap.compute(cls, (node, prevJavaCls) -> {
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) {
@@ -431,82 +484,20 @@ public final class JadxDecompiler implements Closeable {
});
}
@Nullable("For not generated classes")
@ApiStatus.Internal
public JavaClass getJavaClassByNode(ClassNode cls) {
JavaClass javaClass = classesMap.get(cls);
if (javaClass != null && javaClass.getClassNode() == cls) {
return javaClass;
}
// load parent class if inner
ClassNode parentClass = cls.getTopParentClass();
if (parentClass.contains(AFlag.DONT_GENERATE)) {
return null;
}
if (parentClass != cls) {
JavaClass parentJavaClass = classesMap.get(parentClass);
if (parentJavaClass == null) {
getClasses();
parentJavaClass = classesMap.get(parentClass);
}
loadJavaClass(parentJavaClass);
javaClass = classesMap.get(cls);
if (javaClass != null) {
return javaClass;
}
}
// class or parent classes can be excluded from generation
if (cls.hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaClass not found by ClassNode: " + cls);
JavaField convertFieldNode(FieldNode field) {
return fieldsMap.computeIfAbsent(field, fldNode -> {
JavaClass parentCls = convertClassNode(fldNode.getParentClass());
return new JavaField(parentCls, fldNode);
});
}
@Nullable
private JavaMethod getJavaMethodByNode(MethodNode mth) {
JavaMethod javaMethod = methodsMap.get(mth);
if (javaMethod != null && javaMethod.getMethodNode() == mth) {
return javaMethod;
}
if (mth.contains(AFlag.DONT_GENERATE)) {
return null;
}
// parent class not loaded yet
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
if (javaClass == null) {
return null;
}
loadJavaClass(javaClass);
javaMethod = methodsMap.get(mth);
if (javaMethod != null) {
return javaMethod;
}
if (mth.getParentClass().hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
}
@Nullable
private JavaField getJavaFieldByNode(FieldNode fld) {
JavaField javaField = fieldsMap.get(fld);
if (javaField != null && javaField.getFieldNode() == fld) {
return javaField;
}
// parent class not loaded yet
JavaClass javaClass = getJavaClassByNode(fld.getParentClass().getTopParentClass());
if (javaClass == null) {
return null;
}
loadJavaClass(javaClass);
javaField = fieldsMap.get(fld);
if (javaField != null) {
return javaField;
}
if (fld.getParentClass().hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
@ApiStatus.Internal
JavaMethod convertMethodNode(MethodNode method) {
return methodsMap.computeIfAbsent(method, mthNode -> {
ClassNode parentCls = mthNode.getParentClass();
return new JavaMethod(convertClassNode(parentCls), mthNode);
});
}
@Nullable
@@ -514,7 +505,7 @@ public final class JadxDecompiler implements Closeable {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
.findFirst()
.map(this::getJavaClassByNode)
.map(this::convertClassNode)
.orElse(null);
}
@@ -535,9 +526,9 @@ public final class JadxDecompiler implements Closeable {
.orElse(null);
if (node != null) {
if (node.contains(AFlag.DONT_GENERATE)) {
return getJavaClassByNode(node.getTopParentClass());
return convertClassNode(node.getTopParentClass());
} else {
return getJavaClassByNode(node);
return convertClassNode(node);
}
}
return null;
@@ -548,86 +539,92 @@ public final class JadxDecompiler implements Closeable {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
.findFirst()
.map(this::getJavaClassByNode)
.map(this::convertClassNode)
.orElse(null);
}
@Nullable
JavaNode convertNode(Object obj) {
if (obj instanceof VarRef) {
VarRef varRef = (VarRef) obj;
MethodNode mthNode = varRef.getMth();
JavaMethod mth = getJavaMethodByNode(mthNode);
if (mth == null) {
public JavaNode getJavaNodeByRef(ICodeNodeRef ann) {
return getJavaNodeByCodeAnnotation(null, ann);
}
@Nullable
public JavaNode getJavaNodeByCodeAnnotation(@Nullable ICodeInfo codeInfo, @Nullable ICodeAnnotation ann) {
if (ann == null) {
return null;
}
switch (ann.getAnnType()) {
case CLASS:
return convertClassNode((ClassNode) ann);
case METHOD:
return convertMethodNode((MethodNode) ann);
case FIELD:
return convertFieldNode((FieldNode) ann);
case DECLARATION:
return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode());
case VAR:
return resolveVarNode((VarNode) ann);
case VAR_REF:
return resolveVarRef(codeInfo, (VarRef) ann);
case OFFSET:
// offset annotation don't have java node object
return null;
default:
throw new JadxRuntimeException("Unknown annotation type: " + ann.getAnnType() + ", class: " + ann.getClass());
}
}
@Nullable
private JavaVariable resolveVarNode(VarNode varNode) {
MethodNode mthNode = varNode.getMth();
JavaMethod mth = convertMethodNode(mthNode);
if (mth == null) {
return null;
}
return new JavaVariable(mth, varNode);
}
@Nullable
private JavaVariable resolveVarRef(ICodeInfo codeInfo, VarRef varRef) {
if (codeInfo == null) {
throw new JadxRuntimeException("Missing code info for resolve VarRef: " + varRef);
}
ICodeAnnotation varNodeAnn = codeInfo.getCodeMetadata().getAt(varRef.getRefPos());
if (varNodeAnn != null && varNodeAnn.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) {
ICodeNodeRef nodeRef = ((NodeDeclareRef) varNodeAnn).getNode();
if (nodeRef.getAnnType() == ICodeAnnotation.AnnType.VAR) {
return resolveVarNode((VarNode) nodeRef);
}
return new JavaVariable(mth, varRef);
}
if (!(obj instanceof LineAttrNode)) {
return null;
}
LineAttrNode node = (LineAttrNode) obj;
if (node.contains(AFlag.DONT_GENERATE)) {
return null;
}
if (obj instanceof ClassNode) {
return convertClassNode((ClassNode) obj);
}
if (obj instanceof MethodNode) {
return getJavaMethodByNode(((MethodNode) obj));
}
if (obj instanceof FieldNode) {
return getJavaFieldByNode((FieldNode) obj);
}
throw new JadxRuntimeException("Unexpected node type: " + obj);
return null;
}
// TODO: make interface for all nodes in code annotations and add common method instead this
Object getInternalNode(JavaNode javaNode) {
if (javaNode instanceof JavaClass) {
return ((JavaClass) javaNode).getClassNode();
}
if (javaNode instanceof JavaMethod) {
return ((JavaMethod) javaNode).getMethodNode();
}
if (javaNode instanceof JavaField) {
return ((JavaField) javaNode).getFieldNode();
}
if (javaNode instanceof JavaVariable) {
return ((JavaVariable) javaNode).getVarRef();
}
throw new JadxRuntimeException("Unexpected node type: " + javaNode);
}
List<JavaNode> convertNodes(Collection<?> nodesList) {
List<JavaNode> convertNodes(Collection<? extends ICodeNodeRef> nodesList) {
return nodesList.stream()
.map(this::convertNode)
.map(this::getJavaNodeByRef)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@Nullable
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
Map<CodePosition, Object> map = codeInfo.getAnnotations();
if (map.isEmpty()) {
return null;
}
Object obj = map.get(new CodePosition(line, offset));
if (obj == null) {
return null;
}
return convertNode(obj);
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int pos) {
ICodeAnnotation ann = codeInfo.getCodeMetadata().getAt(pos);
return getJavaNodeByCodeAnnotation(codeInfo, ann);
}
@Nullable
public CodePosition getDefinitionPosition(JavaNode javaNode) {
JavaClass jCls = javaNode.getTopParentClass();
jCls.decompile();
int defLine = javaNode.getDecompiledLine();
if (defLine == 0) {
public JavaNode getClosestJavaNode(ICodeInfo codeInfo, int pos) {
ICodeAnnotation ann = codeInfo.getCodeMetadata().getClosestUp(pos);
return getJavaNodeByCodeAnnotation(codeInfo, ann);
}
@Nullable
public JavaNode getEnclosingNode(ICodeInfo codeInfo, int pos) {
ICodeNodeRef obj = codeInfo.getCodeMetadata().getNodeAt(pos);
if (obj == null) {
return null;
}
return new CodePosition(defLine, 0, javaNode.getDefPos());
return getJavaNodeByRef(obj);
}
public void reloadCodeData() {
+135 -86
View File
@@ -8,21 +8,32 @@ import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.InlinedAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.ListUtils;
public final class JavaClass implements JavaNode {
private static final Logger LOG = LoggerFactory.getLogger(JavaClass.class);
private final JadxDecompiler decompiler;
private final ClassNode cls;
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;
@@ -43,24 +54,24 @@ public final class JavaClass implements JavaNode {
}
public String getCode() {
ICodeInfo code = getCodeInfo();
if (code == null) {
return "";
}
return code.getCodeStr();
return getCodeInfo().getCodeStr();
}
public ICodeInfo getCodeInfo() {
public @NotNull ICodeInfo getCodeInfo() {
ICodeInfo code = load();
if (code != null) {
return code;
}
return cls.decompile();
}
public void decompile() {
cls.decompile();
load();
}
public synchronized void reload() {
public synchronized ICodeInfo reload() {
listsLoaded = false;
cls.reloadCode();
return cls.reloadCode();
}
public void unload() {
@@ -68,10 +79,26 @@ public final class JavaClass implements JavaNode {
cls.unloadCode();
}
public boolean isNoCode() {
return cls.contains(AFlag.DONT_GENERATE);
}
public boolean isInner() {
return cls.isInner();
}
public synchronized String getSmali() {
return cls.getDisassembledCode();
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
if (ann.getAnnType() == ICodeAnnotation.AnnType.CLASS) {
return ann.equals(cls);
}
return false;
}
/**
* Internal API. Not Stable!
*/
@@ -80,34 +107,56 @@ public final class JavaClass implements JavaNode {
return cls;
}
private synchronized void loadLists() {
/**
* Decompile class and loads internal lists of fields, methods, etc.
* Do nothing if already loaded.
*
* @return code info if decompilation was executed, null otherwise
*/
private synchronized @Nullable ICodeInfo load() {
if (listsLoaded) {
return;
return null;
}
listsLoaded = true;
decompile();
JadxDecompiler rootDecompiler = getRootDecompiler();
ICodeInfo code;
if (cls.getState().isProcessComplete()) {
// already decompiled -> class internals loaded
code = null;
} else {
code = cls.decompile();
}
JadxDecompiler rootDecompiler = getRootDecompiler();
int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists();
javaClass.load();
list.add(javaClass);
}
}
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.load();
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);
flds.add(javaField);
flds.add(rootDecompiler.convertFieldNode(f));
}
}
this.fields = Collections.unmodifiableList(flds);
@@ -118,65 +167,56 @@ public final class JavaClass implements JavaNode {
List<JavaMethod> mths = new ArrayList<>(methodsCount);
for (MethodNode m : cls.getMethods()) {
if (!m.contains(AFlag.DONT_GENERATE)) {
JavaMethod javaMethod = new JavaMethod(this, m);
mths.add(javaMethod);
mths.add(rootDecompiler.convertMethodNode(m));
}
}
mths.sort(Comparator.comparing(JavaMethod::getName));
this.methods = Collections.unmodifiableList(mths);
}
return code;
}
protected JadxDecompiler getRootDecompiler() {
JadxDecompiler getRootDecompiler() {
if (parent != null) {
return parent.getRootDecompiler();
}
return decompiler;
}
public Map<CodePosition, Object> getCodeAnnotations() {
ICodeInfo code = getCodeInfo();
if (code == null) {
return Collections.emptyMap();
}
return code.getAnnotations();
public ICodeAnnotation getAnnotationAt(int pos) {
return getCodeInfo().getCodeMetadata().getAt(pos);
}
public Object getAnnotationAt(CodePosition pos) {
return getCodeAnnotations().get(pos);
}
public Map<CodePosition, JavaNode> getUsageMap() {
Map<CodePosition, Object> map = getCodeAnnotations();
public Map<Integer, JavaNode> getUsageMap() {
Map<Integer, ICodeAnnotation> map = getCodeInfo().getCodeMetadata().getAsMap();
if (map.isEmpty() || decompiler == null) {
return Collections.emptyMap();
}
Map<CodePosition, JavaNode> resultMap = new HashMap<>(map.size());
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
JavaNode node = getRootDecompiler().convertNode(obj);
if (node != null) {
resultMap.put(codePosition, node);
Map<Integer, JavaNode> resultMap = new HashMap<>(map.size());
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
int codePosition = entry.getKey();
ICodeAnnotation obj = entry.getValue();
if (obj instanceof ICodeNodeRef) {
JavaNode node = getRootDecompiler().getJavaNodeByRef((ICodeNodeRef) obj);
if (node != null) {
resultMap.put(codePosition, node);
}
}
}
return resultMap;
}
public List<CodePosition> getUsageFor(JavaNode javaNode) {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty() || decompiler == null) {
public List<Integer> getUsePlacesFor(ICodeInfo codeInfo, JavaNode javaNode) {
if (!codeInfo.hasMetadata()) {
return Collections.emptyList();
}
Object internalNode = getRootDecompiler().getInternalNode(javaNode);
List<CodePosition> result = new ArrayList<>();
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
if (internalNode.equals(obj)) {
result.add(codePosition);
List<Integer> result = new ArrayList<>();
codeInfo.getCodeMetadata().searchDown(0, (pos, ann) -> {
if (javaNode.isOwnCodeAnnotation(ann)) {
result.add(pos);
}
}
return null;
});
return result;
}
@@ -185,20 +225,8 @@ public final class JavaClass implements JavaNode {
return getRootDecompiler().convertNodes(cls.getUseIn());
}
@Nullable
@Deprecated
public JavaNode getJavaNodeAtPosition(int line, int offset) {
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
}
@Nullable
@Deprecated
public CodePosition getDefinitionPosition() {
return getRootDecompiler().getDefinitionPosition(this);
}
public Integer getSourceLine(int decompiledLine) {
return getCodeInfo().getLineMapping().get(decompiledLine);
return getCodeInfo().getCodeMetadata().getLineMapping().get(decompiledLine);
}
@Override
@@ -224,25 +252,37 @@ public final class JavaClass implements JavaNode {
return parent;
}
@Override
public JavaClass getTopParentClass() {
if (cls.contains(AFlag.ANONYMOUS_CLASS)) {
// moved to usage class
return getParentForAnonymousClass();
}
return parent == null ? this : parent.getTopParentClass();
public JavaClass getOriginalTopParentClass() {
return parent == null ? this : parent.getOriginalTopParentClass();
}
private JavaClass getParentForAnonymousClass() {
List<JavaNode> useIn = getUseIn();
if (useIn.isEmpty()) {
return this;
/**
* Return top parent class which contains code of this class.
* Code parent can be different from original parent after move or inline
*
* @return this if already a top class
*/
@Override
public JavaClass getTopParentClass() {
JavaClass codeParent = getCodeParent();
return codeParent == null ? this : codeParent.getTopParentClass();
}
/**
* Return parent class which contains code of this class.
* Code parent can be different for original parent after move or inline
*/
public @Nullable JavaClass getCodeParent() {
AnonymousClassAttr anonymousClsAttr = cls.get(AType.ANONYMOUS_CLASS);
if (anonymousClsAttr != null) {
// moved to usage class
return getRootDecompiler().convertClassNode(anonymousClsAttr.getOuterCls());
}
JavaNode useNode = useIn.get(0);
if (useNode.equals(this)) {
return this;
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
if (inlinedAttr != null) {
return getRootDecompiler().convertClassNode(inlinedAttr.getInlineCls());
}
return useNode.getTopParentClass();
return parent;
}
public AccessInfo getAccessInfo() {
@@ -250,17 +290,22 @@ public final class JavaClass implements JavaNode {
}
public List<JavaClass> getInnerClasses() {
loadLists();
load();
return innerClasses;
}
public List<JavaClass> getInlinedClasses() {
load();
return inlinedClasses;
}
public List<JavaField> getFields() {
loadLists();
load();
return fields;
}
public List<JavaMethod> getMethods() {
loadLists();
load();
return methods;
}
@@ -270,7 +315,16 @@ public final class JavaClass implements JavaNode {
if (methodNode == null) {
return null;
}
return new JavaMethod(this, methodNode);
return getRootDecompiler().convertMethodNode(methodNode);
}
public List<JavaClass> getDependencies() {
JadxDecompiler d = getRootDecompiler();
return ListUtils.map(cls.getDependencies(), d::convertClassNode);
}
public int getTotalDepsCount() {
return cls.getTotalDepsCount();
}
@Override
@@ -278,11 +332,6 @@ public final class JavaClass implements JavaNode {
this.cls.getClassInfo().removeAlias();
}
@Override
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
@Override
public int getDefPos() {
return cls.getDefPosition();
@@ -4,6 +4,7 @@ import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import jadx.api.metadata.ICodeAnnotation;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
@@ -13,7 +14,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 +29,10 @@ public final class JavaField implements JavaNode {
return parent.getFullName() + '.' + getName();
}
public String getRawName() {
return field.getName();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
@@ -46,11 +51,6 @@ public final class JavaField implements JavaNode {
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
}
@Override
public int getDecompiledLine() {
return field.getDecompiledLine();
}
@Override
public int getDefPos() {
return field.getDefPosition();
@@ -66,6 +66,14 @@ public final class JavaField implements JavaNode {
this.field.getFieldInfo().removeAlias();
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
if (ann.getAnnType() == ICodeAnnotation.AnnType.FIELD) {
return ann.equals(field);
}
return false;
}
/**
* Internal API. Not Stable!
*/
@@ -2,10 +2,14 @@ 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.api.metadata.ICodeAnnotation;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.AccessInfo;
@@ -14,6 +18,8 @@ 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 +79,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 = decompiler.convertMethodNode(m);
if (javaMth == null) {
LOG.warn("Failed convert to java method: {}", m);
}
return javaMth;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@@ -85,11 +98,6 @@ public final class JavaMethod implements JavaNode {
return mth.getMethodInfo().isClassInit();
}
@Override
public int getDecompiledLine() {
return mth.getDecompiledLine();
}
@Override
public int getDefPos() {
return mth.getDefPosition();
@@ -100,6 +108,14 @@ public final class JavaMethod implements JavaNode {
this.mth.getMethodInfo().removeAlias();
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
if (ann.getAnnType() == ICodeAnnotation.AnnType.METHOD) {
return ann.equals(mth);
}
return false;
}
/**
* Internal API. Not Stable!
*/
@@ -2,6 +2,8 @@ package jadx.api;
import java.util.List;
import jadx.api.metadata.ICodeAnnotation;
public interface JavaNode {
String getName();
@@ -12,12 +14,12 @@ public interface JavaNode {
JavaClass getTopParentClass();
int getDecompiledLine();
int getDefPos();
List<JavaNode> getUseIn();
default void removeAlias() {
}
boolean isOwnCodeAnnotation(ICodeAnnotation ann);
}
@@ -5,6 +5,8 @@ import java.util.List;
import org.jetbrains.annotations.NotNull;
import jadx.api.metadata.ICodeAnnotation;
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
private final String name;
private final List<JavaClass> classes;
@@ -39,11 +41,6 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return null;
}
@Override
public int getDecompiledLine() {
return 0;
}
@Override
public int getDefPos() {
return 0;
@@ -54,6 +51,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return Collections.emptyList();
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
return false;
}
@Override
public int compareTo(@NotNull JavaPackage o) {
return name.compareTo(o.name);
@@ -4,17 +4,20 @@ import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.data.annotations.VarRef;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.metadata.annotations.VarRef;
import jadx.core.dex.instructions.args.ArgType;
public class JavaVariable implements JavaNode {
private final JavaMethod mth;
private final VarRef varRef;
private final VarNode varNode;
public JavaVariable(JavaMethod mth, VarRef varRef) {
public JavaVariable(JavaMethod mth, VarNode varNode) {
this.mth = mth;
this.varRef = varRef;
this.varNode = varNode;
}
public JavaMethod getMth() {
@@ -22,26 +25,30 @@ public class JavaVariable implements JavaNode {
}
public int getReg() {
return varRef.getReg();
return varNode.getReg();
}
public int getSsa() {
return varRef.getSsa();
return varNode.getSsa();
}
@Override
public String getName() {
return varRef.getName();
public @Nullable String getName() {
return varNode.getName();
}
@ApiStatus.Internal
public VarRef getVarRef() {
return varRef;
public VarNode getVarNode() {
return varNode;
}
@Override
public String getFullName() {
return varRef.getType() + " " + varRef.getName() + " (r" + varRef.getReg() + "v" + varRef.getSsa() + ")";
return varNode.getType() + " " + varNode.getName() + " (r" + varNode.getReg() + "v" + varNode.getSsa() + ")";
}
public ArgType getType() {
return ArgType.tryToResolveClassAlias(mth.getMethodNode().root(), varNode.getType());
}
@Override
@@ -54,20 +61,9 @@ public class JavaVariable implements JavaNode {
return mth.getTopParentClass();
}
@Override
public int getDecompiledLine() {
if (varRef instanceof VarDeclareRef) {
return ((VarDeclareRef) varRef).getDecompiledLine();
}
return 0;
}
@Override
public int getDefPos() {
if (varRef instanceof VarDeclareRef) {
return ((VarDeclareRef) varRef).getDefPosition();
}
return 0;
return varNode.getDefPosition();
}
@Override
@@ -75,9 +71,18 @@ public class JavaVariable implements JavaNode {
return Collections.singletonList(mth);
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
if (ann.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
VarRef varRef = (VarRef) ann;
return varRef.getRefPos() == getDefPos();
}
return false;
}
@Override
public int hashCode() {
return varRef.hashCode();
return varNode.hashCode();
}
@Override
@@ -88,6 +93,6 @@ public class JavaVariable implements JavaNode {
if (!(o instanceof JavaVariable)) {
return false;
}
return varRef.equals(((JavaVariable) o).varRef);
return varNode.equals(((JavaVariable) o).varNode);
}
}
@@ -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;
}
}
@@ -1,5 +0,0 @@
package jadx.api.data.annotations;
public interface ICodeRawOffset {
int getOffset();
}
@@ -1,57 +0,0 @@
package jadx.api.data.annotations;
import jadx.core.dex.attributes.ILineAttributeNode;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.nodes.MethodNode;
public class VarDeclareRef extends VarRef implements ILineAttributeNode {
public static VarDeclareRef get(MethodNode mth, CodeVar codeVar) {
VarDeclareRef ref = new VarDeclareRef(mth, codeVar);
codeVar.setCachedVarRef(ref);
return ref;
}
private int sourceLine;
private int decompiledLine;
private int defPosition;
private VarDeclareRef(MethodNode mth, CodeVar codeVar) {
super(mth, codeVar.getAnySsaVar());
}
@Override
public int getSourceLine() {
return sourceLine;
}
@Override
public void setSourceLine(int sourceLine) {
this.sourceLine = sourceLine;
}
@Override
public int getDecompiledLine() {
return decompiledLine;
}
@Override
public void setDecompiledLine(int decompiledLine) {
this.decompiledLine = decompiledLine;
}
@Override
public int getDefPosition() {
return defPosition;
}
@Override
public void setDefPosition(int pos) {
this.defPosition = pos;
}
@Override
public String toString() {
return "VarDeclareRef{r" + getReg() + 'v' + getSsa() + '}';
}
}
@@ -1,98 +0,0 @@
package jadx.api.data.annotations;
import org.jetbrains.annotations.Nullable;
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.MethodNode;
public class VarRef {
@Nullable
public static VarRef get(MethodNode mth, RegisterArg reg) {
SSAVar ssaVar = reg.getSVar();
if (ssaVar == null) {
return null;
}
CodeVar codeVar = ssaVar.getCodeVar();
VarRef cachedVarRef = codeVar.getCachedVarRef();
if (cachedVarRef != null) {
if (cachedVarRef.getName() == null) {
cachedVarRef.setName(codeVar.getName());
}
return cachedVarRef;
}
VarRef newVarRef = new VarRef(mth, ssaVar);
codeVar.setCachedVarRef(newVarRef);
return newVarRef;
}
private final MethodNode mth;
private final int reg;
private final int ssa;
private final ArgType type;
private String name;
protected VarRef(MethodNode mth, SSAVar ssaVar) {
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
}
private VarRef(MethodNode mth, int reg, int ssa, ArgType type, String name) {
this.mth = mth;
this.reg = reg;
this.ssa = ssa;
this.type = type;
this.name = name;
}
public MethodNode getMth() {
return mth;
}
public int getReg() {
return reg;
}
public int getSsa() {
return ssa;
}
public ArgType getType() {
return type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof VarRef)) {
return false;
}
VarRef other = (VarRef) o;
return getReg() == other.getReg()
&& getSsa() == other.getSsa()
&& getMth().equals(other.getMth());
}
@Override
public int hashCode() {
return 31 * getReg() + getSsa();
}
@Override
public String toString() {
return "VarUseRef{r" + reg + 'v' + ssa + '}';
}
}
@@ -3,7 +3,7 @@ package jadx.api.data.impl;
import jadx.api.JavaVariable;
import jadx.api.data.CodeRefType;
import jadx.api.data.IJavaCodeRef;
import jadx.api.data.annotations.VarRef;
import jadx.api.metadata.annotations.VarNode;
public class JadxCodeRef implements IJavaCodeRef {
@@ -23,8 +23,8 @@ public class JadxCodeRef implements IJavaCodeRef {
return forVar(javaVariable.getReg(), javaVariable.getSsa());
}
public static JadxCodeRef forVar(VarRef varRef) {
return forVar(varRef.getReg(), varRef.getSsa());
public static JadxCodeRef forVar(VarNode varNode) {
return forVar(varNode.getReg(), varNode.getSsa());
}
public static JadxCodeRef forCatch(int handlerOffset) {
@@ -2,23 +2,19 @@ package jadx.api.impl;
import java.util.Map;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeMetadata;
import jadx.api.metadata.impl.CodeMetadataStorage;
public class AnnotatedCodeInfo implements ICodeInfo {
private final String code;
private final Map<Integer, Integer> lineMapping;
private final Map<CodePosition, Object> annotations;
private final ICodeMetadata metadata;
public AnnotatedCodeInfo(ICodeInfo codeInfo) {
this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations());
}
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<CodePosition, Object> annotations) {
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<Integer, ICodeAnnotation> annotations) {
this.code = code;
this.lineMapping = lineMapping;
this.annotations = annotations;
this.metadata = CodeMetadataStorage.build(lineMapping, annotations);
}
@Override
@@ -27,13 +23,13 @@ public class AnnotatedCodeInfo implements ICodeInfo {
}
@Override
public Map<Integer, Integer> getLineMapping() {
return lineMapping;
public ICodeMetadata getCodeMetadata() {
return metadata;
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return annotations;
public boolean hasMetadata() {
return metadata != ICodeMetadata.EMPTY;
}
@Override
@@ -5,18 +5,20 @@ import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.ILineAttributeNode;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.api.metadata.annotations.VarRef;
import jadx.core.utils.StringUtils;
public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter {
private int line = 1;
private int offset;
private Map<CodePosition, Object> annotations = Collections.emptyMap();
private Map<Integer, ICodeAnnotation> annotations = Collections.emptyMap();
private Map<Integer, Integer> lineMap = Collections.emptyMap();
public AnnotatedCodeWriter() {
@@ -59,19 +61,17 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
@Override
public ICodeWriter add(ICodeWriter cw) {
if ((!(cw instanceof AnnotatedCodeWriter))) {
if (!cw.isMetadataSupported()) {
buf.append(cw.getCodeStr());
return this;
}
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
line--;
int startLine = line;
int startPos = getLength();
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
CodePosition codePos = entry.getKey();
int newLine = startLine + codePos.getLine();
int newPos = startPos + codePos.getPos();
attachAnnotation(entry.getValue(), new CodePosition(newLine, codePos.getOffset(), newPos));
for (Map.Entry<Integer, ICodeAnnotation> entry : code.annotations.entrySet()) {
int pos = entry.getKey();
int newPos = startPos + pos;
attachAnnotation(entry.getValue(), newPos);
}
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
attachSourceLine(line + entry.getKey(), entry.getValue());
@@ -101,44 +101,36 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
return line;
}
private static final class DefinitionWrapper {
private final ILineAttributeNode node;
private DefinitionWrapper(ILineAttributeNode node) {
this.node = node;
}
public ILineAttributeNode getNode() {
return node;
}
@Override
public int getLineStartPos() {
return getLength() - offset;
}
@Override
public void attachDefinition(ILineAttributeNode obj) {
public void attachDefinition(ICodeNodeRef obj) {
if (obj == null) {
return;
}
attachAnnotation(obj);
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset, getLength()));
attachAnnotation(new NodeDeclareRef(obj));
}
@Override
public void attachAnnotation(Object obj) {
public void attachAnnotation(ICodeAnnotation obj) {
if (obj == null) {
return;
}
attachAnnotation(obj, new CodePosition(line, offset + 1, getLength()));
attachAnnotation(obj, getLength());
}
@Override
public void attachLineAnnotation(Object obj) {
public void attachLineAnnotation(ICodeAnnotation obj) {
if (obj == null) {
return;
}
attachAnnotation(obj, new CodePosition(line, 0, getLength() - offset));
attachAnnotation(obj, getLineStartPos());
}
private void attachAnnotation(Object obj, CodePosition pos) {
private void attachAnnotation(ICodeAnnotation obj, int pos) {
if (annotations.isEmpty()) {
annotations = new HashMap<>();
}
@@ -164,29 +156,39 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
public ICodeInfo finish() {
removeFirstEmptyLine();
processDefinitionAnnotations();
validateAnnotations();
String code = buf.toString();
buf = null;
return new AnnotatedCodeInfo(code, lineMap, annotations);
}
@Override
public Map<CodePosition, Object> getRawAnnotations() {
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
return annotations;
}
private void processDefinitionAnnotations() {
if (!annotations.isEmpty()) {
annotations.entrySet().removeIf(entry -> {
Object v = entry.getValue();
if (v instanceof DefinitionWrapper) {
ILineAttributeNode l = ((DefinitionWrapper) v).getNode();
CodePosition codePos = entry.getKey();
l.setDecompiledLine(codePos.getLine());
l.setDefPosition(codePos.getPos());
return true;
annotations.forEach((k, v) -> {
if (v instanceof NodeDeclareRef) {
NodeDeclareRef declareRef = (NodeDeclareRef) v;
declareRef.setDefPos(k);
declareRef.getNode().setDefPosition(k);
}
return false;
});
}
}
private void validateAnnotations() {
if (annotations.isEmpty()) {
return;
}
annotations.values().removeIf(v -> {
if (v.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
VarRef varRef = (VarRef) v;
return varRef.getRefPos() == 0;
}
return false;
});
}
}
@@ -0,0 +1,49 @@
package jadx.api.impl;
import java.io.IOException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
public abstract class DelegateCodeCache implements ICodeCache {
protected final ICodeCache backCache;
public DelegateCodeCache(ICodeCache backCache) {
this.backCache = backCache;
}
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
backCache.add(clsFullName, codeInfo);
}
@Override
public void remove(String clsFullName) {
backCache.remove(clsFullName);
}
@Override
public @NotNull ICodeInfo get(String clsFullName) {
return backCache.get(clsFullName);
}
@Override
@Nullable
public String getCode(String clsFullName) {
return backCache.getCode(clsFullName);
}
@Override
public boolean contains(String clsFullName) {
return backCache.contains(clsFullName);
}
@Override
public void close() throws IOException {
backCache.close();
}
}
@@ -1,8 +1,10 @@
package jadx.api.impl;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
@@ -22,9 +24,33 @@ public class InMemoryCodeCache implements ICodeCache {
storage.remove(clsFullName);
}
@NotNull
@Override
public @Nullable ICodeInfo get(String clsFullName) {
return storage.get(clsFullName);
public ICodeInfo get(String clsFullName) {
ICodeInfo codeInfo = storage.get(clsFullName);
if (codeInfo == null) {
return ICodeInfo.EMPTY;
}
return codeInfo;
}
@Override
public @Nullable String getCode(String clsFullName) {
ICodeInfo codeInfo = storage.get(clsFullName);
if (codeInfo == null) {
return null;
}
return codeInfo.getCodeStr();
}
@Override
public boolean contains(String clsFullName) {
return storage.containsKey(clsFullName);
}
@Override
public void close() throws IOException {
storage.clear();
}
@Override
@@ -1,5 +1,6 @@
package jadx.api.impl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
@@ -7,6 +8,8 @@ import jadx.api.ICodeInfo;
public class NoOpCodeCache implements ICodeCache {
public static final NoOpCodeCache INSTANCE = new NoOpCodeCache();
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
// do nothing
@@ -18,10 +21,26 @@ public class NoOpCodeCache implements ICodeCache {
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
@NotNull
public ICodeInfo get(String clsFullName) {
return ICodeInfo.EMPTY;
}
@Override
public @Nullable String getCode(String clsFullName) {
return null;
}
@Override
public boolean contains(String clsFullName) {
return false;
}
@Override
public void close() {
// do nothing
}
@Override
public String toString() {
return "NoOpCodeCache";
@@ -1,10 +1,7 @@
package jadx.api.impl;
import java.util.Collections;
import java.util.Map;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.metadata.ICodeMetadata;
public class SimpleCodeInfo implements ICodeInfo {
@@ -20,13 +17,13 @@ public class SimpleCodeInfo implements ICodeInfo {
}
@Override
public Map<Integer, Integer> getLineMapping() {
return Collections.emptyMap();
public ICodeMetadata getCodeMetadata() {
return ICodeMetadata.EMPTY;
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return Collections.emptyMap();
public boolean hasMetadata() {
return false;
}
@Override
@@ -6,11 +6,11 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.ILineAttributeNode;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.utils.Utils;
/**
@@ -193,17 +193,22 @@ public class SimpleCodeWriter implements ICodeWriter {
}
@Override
public void attachDefinition(ILineAttributeNode obj) {
public int getLineStartPos() {
return 0;
}
@Override
public void attachDefinition(ICodeNodeRef obj) {
// no op
}
@Override
public void attachAnnotation(Object obj) {
public void attachAnnotation(ICodeAnnotation obj) {
// no op
}
@Override
public void attachLineAnnotation(Object obj) {
public void attachLineAnnotation(ICodeAnnotation obj) {
// no op
}
@@ -238,7 +243,7 @@ public class SimpleCodeWriter implements ICodeWriter {
}
@Override
public Map<CodePosition, Object> getRawAnnotations() {
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
return Collections.emptyMap();
}
@@ -0,0 +1,17 @@
package jadx.api.metadata;
public interface ICodeAnnotation {
enum AnnType {
CLASS,
FIELD,
METHOD,
VAR,
VAR_REF,
DECLARATION,
OFFSET,
END // class or method body end
}
AnnType getAnnType();
}
@@ -0,0 +1,59 @@
package jadx.api.metadata;
import java.util.Map;
import java.util.function.BiFunction;
import org.jetbrains.annotations.Nullable;
import jadx.api.metadata.impl.CodeMetadataStorage;
public interface ICodeMetadata {
ICodeMetadata EMPTY = CodeMetadataStorage.empty();
@Nullable
ICodeAnnotation getAt(int position);
@Nullable
ICodeAnnotation getClosestUp(int position);
@Nullable
ICodeAnnotation searchUp(int position, ICodeAnnotation.AnnType annType);
@Nullable
ICodeAnnotation searchUp(int position, int limitPos, ICodeAnnotation.AnnType annType);
/**
* Iterate code annotations from {@code startPos} to smaller positions.
*
* @param visitor
* return not null value to stop iterations
*/
@Nullable
<T> T searchUp(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor);
/**
* Iterate code annotations from {@code startPos} to higher positions.
*
* @param visitor
* return not null value to stop iterations
*/
@Nullable
<T> T searchDown(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor);
/**
* Get current node at position (can be enclosing class or method)
*/
@Nullable
ICodeNodeRef getNodeAt(int position);
/**
* Any definition of class or method below position
*/
@Nullable
ICodeNodeRef getNodeBelow(int position);
Map<Integer, ICodeAnnotation> getAsMap();
Map<Integer, Integer> getLineMapping();
}
@@ -0,0 +1,7 @@
package jadx.api.metadata;
public interface ICodeNodeRef extends ICodeAnnotation {
int getDefPosition();
void setDefPosition(int pos);
}
@@ -1,11 +1,12 @@
package jadx.api.data.annotations;
package jadx.api.metadata.annotations;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
import jadx.api.metadata.ICodeAnnotation;
import jadx.core.dex.nodes.InsnNode;
public class InsnCodeOffset implements ICodeRawOffset {
public class InsnCodeOffset implements ICodeAnnotation {
public static void attach(ICodeWriter code, InsnNode insn) {
if (insn == null) {
@@ -40,11 +41,15 @@ public class InsnCodeOffset implements ICodeRawOffset {
this.offset = offset;
}
@Override
public int getOffset() {
return offset;
}
@Override
public AnnType getAnnType() {
return AnnType.OFFSET;
}
@Override
public String toString() {
return "offset=" + offset;
@@ -0,0 +1,55 @@
package jadx.api.metadata.annotations;
import java.util.Objects;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
public class NodeDeclareRef implements ICodeAnnotation {
private final ICodeNodeRef node;
private int defPos;
public NodeDeclareRef(ICodeNodeRef node) {
this.node = Objects.requireNonNull(node);
}
public ICodeNodeRef getNode() {
return node;
}
public int getDefPos() {
return defPos;
}
public void setDefPos(int defPos) {
this.defPos = defPos;
}
@Override
public AnnType getAnnType() {
return AnnType.DECLARATION;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof NodeDeclareRef)) {
return false;
}
return node.equals(((NodeDeclareRef) o).node);
}
@Override
public int hashCode() {
return node.hashCode();
}
@Override
public String toString() {
return "NodeDeclareRef{" + node + '}';
}
}
@@ -0,0 +1,21 @@
package jadx.api.metadata.annotations;
import jadx.api.metadata.ICodeAnnotation;
public class NodeEnd implements ICodeAnnotation {
public static final NodeEnd VALUE = new NodeEnd();
private NodeEnd() {
}
@Override
public AnnType getAnnType() {
return AnnType.END;
}
@Override
public String toString() {
return "END";
}
}
@@ -0,0 +1,147 @@
package jadx.api.metadata.annotations;
import org.jetbrains.annotations.Nullable;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
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.MethodNode;
/**
* Variable info
*/
public class VarNode implements ICodeNodeRef {
@Nullable
public static VarNode get(MethodNode mth, RegisterArg reg) {
SSAVar ssaVar = reg.getSVar();
if (ssaVar == null) {
return null;
}
return get(mth, ssaVar);
}
@Nullable
public static VarNode get(MethodNode mth, CodeVar codeVar) {
return get(mth, codeVar.getAnySsaVar());
}
@Nullable
public static VarNode get(MethodNode mth, SSAVar ssaVar) {
CodeVar codeVar = ssaVar.getCodeVar();
if (codeVar.isThis()) {
return null;
}
VarNode cachedVarNode = codeVar.getCachedVarNode();
if (cachedVarNode != null) {
return cachedVarNode;
}
VarNode newVarNode = new VarNode(mth, ssaVar);
codeVar.setCachedVarNode(newVarNode);
return newVarNode;
}
@Nullable
public static ICodeAnnotation getRef(MethodNode mth, RegisterArg reg) {
VarNode varNode = get(mth, reg);
if (varNode == null) {
return null;
}
return varNode.getVarRef();
}
private final MethodNode mth;
private final int reg;
private final int ssa;
private final ArgType type;
private @Nullable String name;
private int defPos;
private final VarRef varRef;
protected VarNode(MethodNode mth, SSAVar ssaVar) {
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
}
public VarNode(MethodNode mth, int reg, int ssa, ArgType type, String name) {
this.mth = mth;
this.reg = reg;
this.ssa = ssa;
this.type = type;
this.name = name;
this.varRef = VarRef.fromVarNode(this);
}
public MethodNode getMth() {
return mth;
}
public int getReg() {
return reg;
}
public int getSsa() {
return ssa;
}
public ArgType getType() {
return type;
}
@Nullable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public VarRef getVarRef() {
return varRef;
}
@Override
public int getDefPosition() {
return defPos;
}
@Override
public void setDefPosition(int pos) {
this.defPos = pos;
}
@Override
public AnnType getAnnType() {
return AnnType.VAR;
}
@Override
public int hashCode() {
int h = 31 * getReg() + getSsa();
return 31 * h + mth.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof VarNode)) {
return false;
}
VarNode other = (VarNode) o;
return getReg() == other.getReg()
&& getSsa() == other.getSsa()
&& getMth().equals(other.getMth());
}
@Override
public String toString() {
return "VarNode{r" + reg + 'v' + ssa + '}';
}
}
@@ -0,0 +1,68 @@
package jadx.api.metadata.annotations;
import jadx.api.metadata.ICodeAnnotation;
/**
* Variable reference by position of VarNode in code metadata.
* <br>
* Because on creation position not yet known,
* VarRef created using VarNode as a source of ref pos during serialization.
* <br>
* On metadata deserialization created with ref pos directly.
*/
public abstract class VarRef implements ICodeAnnotation {
public static VarRef fromPos(int refPos) {
if (refPos == 0) {
throw new IllegalArgumentException("Zero refPos");
}
return new FixedVarRef(refPos);
}
public static VarRef fromVarNode(VarNode varNode) {
return new RelatedVarRef(varNode);
}
public abstract int getRefPos();
@Override
public AnnType getAnnType() {
return AnnType.VAR_REF;
}
public static final class FixedVarRef extends VarRef {
private final int refPos;
public FixedVarRef(int refPos) {
this.refPos = refPos;
}
@Override
public int getRefPos() {
return refPos;
}
}
public static final class RelatedVarRef extends VarRef {
private final VarNode varNode;
public RelatedVarRef(VarNode varNode) {
this.varNode = varNode;
}
@Override
public int getRefPos() {
return varNode.getDefPosition();
}
@Override
public String toString() {
return "VarRef{" + varNode + ", name=" + varNode.getName() + ", mth=" + varNode.getMth() + '}';
}
}
@Override
public String toString() {
return "VarRef{" + getRefPos() + '}';
}
}
@@ -0,0 +1,151 @@
package jadx.api.metadata.impl;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.function.BiFunction;
import org.jetbrains.annotations.Nullable;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeAnnotation.AnnType;
import jadx.api.metadata.ICodeMetadata;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.core.utils.Utils;
public class CodeMetadataStorage implements ICodeMetadata {
public static ICodeMetadata build(Map<Integer, Integer> lines, Map<Integer, ICodeAnnotation> map) {
if (map.isEmpty() && lines.isEmpty()) {
return ICodeMetadata.EMPTY;
}
Comparator<Integer> reverseCmp = Comparator.comparingInt(Integer::intValue).reversed();
NavigableMap<Integer, ICodeAnnotation> navMap = new TreeMap<>(reverseCmp);
navMap.putAll(map);
return new CodeMetadataStorage(lines, navMap);
}
public static ICodeMetadata empty() {
return new CodeMetadataStorage(Collections.emptyMap(), Collections.emptyNavigableMap());
}
private final Map<Integer, Integer> lines;
private final NavigableMap<Integer, ICodeAnnotation> navMap;
private CodeMetadataStorage(Map<Integer, Integer> lines, NavigableMap<Integer, ICodeAnnotation> navMap) {
this.lines = lines;
this.navMap = navMap;
}
@Override
public ICodeAnnotation getAt(int position) {
return navMap.get(position);
}
@Override
public @Nullable ICodeAnnotation getClosestUp(int position) {
Map.Entry<Integer, ICodeAnnotation> entryBefore = navMap.higherEntry(position);
return entryBefore != null ? entryBefore.getValue() : null;
}
@Override
public @Nullable ICodeAnnotation searchUp(int position, AnnType annType) {
for (ICodeAnnotation v : navMap.tailMap(position, true).values()) {
if (v.getAnnType() == annType) {
return v;
}
}
return null;
}
@Override
public @Nullable ICodeAnnotation searchUp(int position, int limitPos, AnnType annType) {
for (ICodeAnnotation v : navMap.subMap(position, true, limitPos, true).values()) {
if (v.getAnnType() == annType) {
return v;
}
}
return null;
}
@Override
public <T> @Nullable T searchUp(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor) {
for (Map.Entry<Integer, ICodeAnnotation> entry : navMap.tailMap(startPos, true).entrySet()) {
T value = visitor.apply(entry.getKey(), entry.getValue());
if (value != null) {
return value;
}
}
return null;
}
@Override
public <T> @Nullable T searchDown(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor) {
NavigableMap<Integer, ICodeAnnotation> map = navMap.headMap(startPos, true).descendingMap();
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
T value = visitor.apply(entry.getKey(), entry.getValue());
if (value != null) {
return value;
}
}
return null;
}
@Override
public ICodeNodeRef getNodeAt(int position) {
int nesting = 0;
for (ICodeAnnotation ann : navMap.tailMap(position, true).values()) {
switch (ann.getAnnType()) {
case END:
nesting++;
break;
case DECLARATION:
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
AnnType nodeType = node.getAnnType();
if (nodeType == AnnType.CLASS || nodeType == AnnType.METHOD) {
if (nesting == 0) {
return node;
}
nesting--;
}
break;
}
}
return null;
}
@Override
public ICodeNodeRef getNodeBelow(int position) {
for (ICodeAnnotation ann : navMap.headMap(position, true).descendingMap().values()) {
if (ann.getAnnType() == AnnType.DECLARATION) {
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
AnnType nodeType = node.getAnnType();
if (nodeType == AnnType.CLASS || nodeType == AnnType.METHOD) {
return node;
}
}
}
return null;
}
@Override
public NavigableMap<Integer, ICodeAnnotation> getAsMap() {
return navMap;
}
@Override
public Map<Integer, Integer> getLineMapping() {
return lines;
}
@Override
public String toString() {
return "CodeMetadata{\nlines=" + lines
+ "\nannotations=\n " + Utils.listToString(navMap.descendingMap().entrySet(), "\n ") + "\n}";
}
}
@@ -0,0 +1,38 @@
package jadx.api.utils;
import jadx.api.ICodeWriter;
public class CodeUtils {
public static String getLineForPos(String code, int pos) {
int start = getLineStartForPos(code, pos);
int end = getLineEndForPos(code, pos);
return code.substring(start, end);
}
public static int getLineStartForPos(String code, int pos) {
String newLine = ICodeWriter.NL;
int start = code.lastIndexOf(newLine, pos);
return start == -1 ? 0 : start + newLine.length();
}
public static int getLineEndForPos(String code, int pos) {
int end = code.indexOf(ICodeWriter.NL, pos);
return end == -1 ? code.length() : end;
}
public static int getLineNumForPos(String code, int pos) {
String newLine = ICodeWriter.NL;
int newLineLen = newLine.length();
int line = 1;
int prev = 0;
while (true) {
int next = code.indexOf(newLine, prev);
if (next >= pos) {
return line;
}
prev = next + newLineLen;
line++;
}
}
}
@@ -7,6 +7,7 @@ public class Consts {
public static final boolean DEBUG_TYPE_INFERENCE = false;
public static final boolean DEBUG_OVERLOADED_CASTS = false;
public static final boolean DEBUG_EXC_HANDLERS = false;
public static final boolean DEBUG_FINALLY = false;
public static final String CLASS_OBJECT = "java.lang.Object";
public static final String CLASS_STRING = "java.lang.String";
+96 -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,77 @@ 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) {
version = searchJadxVersion();
}
return version;
}
public static boolean isDevVersion() {
return getVersion().equals(VERSION_DEV);
}
private static String searchJadxVersion() {
try {
ClassLoader classLoader = Jadx.class.getClassLoader();
if (classLoader != null) {
@@ -188,6 +266,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,23 @@ 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);
}
}
if (!usedInDeps.isEmpty()) {
// process current class before its usage
if (cls.contains(AFlag.DONT_GENERATE)) {
process(cls, false);
for (ClassNode depCls : usedInDeps) {
process(depCls, false);
return ICodeInfo.EMPTY;
}
for (ClassNode depCls : cls.getDependencies()) {
process(depCls, false);
}
if (!cls.getCodegenDeps().isEmpty()) {
process(cls, false);
for (ClassNode codegenDep : cls.getCodegenDeps()) {
process(codegenDep, false);
}
}
ICodeInfo code = process(cls, true);
@@ -120,4 +121,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;
}
}
@@ -203,6 +203,9 @@ public class ClspGraph {
if (isNew) {
addSuperTypes(parentCls, result);
}
} else {
// parent type is unknown
result.add(parentType.getObject());
}
}
}
@@ -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;
@@ -19,6 +19,7 @@ import jadx.api.CommentsLevel;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.metadata.annotations.NodeEnd;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
@@ -256,6 +257,7 @@ public class ClassGen {
addInnerClsAndMethods(clsCode);
clsCode.decIndent();
clsCode.startLine('}');
clsCode.attachAnnotation(NodeEnd.VALUE);
}
private void addInnerClsAndMethods(ICodeWriter clsCode) {
@@ -285,7 +287,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 +358,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);
@@ -369,6 +371,7 @@ public class ClassGen {
mthGen.addInstructions(code);
code.decIndent();
code.startLine('}');
code.attachAnnotation(NodeEnd.VALUE);
}
}
@@ -459,7 +462,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 +529,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 +589,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,7 +614,7 @@ public class ClassGen {
return fullName;
}
String shortName = extClsInfo.getAliasShortName();
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
if (useCls.equals(extClsInfo)) {
return shortName;
}
if (isClassInnerFor(useCls, extClsInfo)) {
@@ -599,16 +623,21 @@ public class ClassGen {
if (extClsInfo.isInner()) {
return expandInnerClassName(useCls, extClsInfo);
}
if (checkInnerCollision(cls.root(), useCls, extClsInfo)
|| checkInPackageCollision(cls.root(), useCls, extClsInfo)) {
return fullName;
}
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
return shortName;
}
// don't add import for top classes from 'java.lang' package (subpackages excluded)
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
return shortName;
}
// don't add import if this class from same package
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;
@@ -685,7 +714,7 @@ public class ClassGen {
return false;
}
private static boolean searchCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
private static boolean checkInnerCollision(RootNode root, @Nullable ClassInfo useCls, ClassInfo searchCls) {
if (useCls == null) {
return false;
}
@@ -702,7 +731,20 @@ public class ClassGen {
}
}
}
return searchCollision(root, useCls.getParentClass(), searchCls);
return checkInnerCollision(root, useCls.getParentClass(), searchCls);
}
/**
* Check if class with same name exists in current package
*/
private static boolean checkInPackageCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
String currentPkg = useCls.getAliasPkg();
if (currentPkg.equals(searchCls.getAliasPkg())) {
// search class already from current package
return false;
}
String shortName = searchCls.getAliasShortName();
return root.getClsp().isClsKnown(currentPkg + '.' + shortName);
}
private void insertRenameInfo(ICodeWriter code, ClassNode cls) {
@@ -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);
}
@@ -11,16 +11,15 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.data.annotations.VarRef;
import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode;
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 +50,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;
@@ -108,11 +108,11 @@ public class InsnGen {
if (arg.isRegister()) {
RegisterArg reg = (RegisterArg) arg;
if (code.isMetadataSupported()) {
code.attachAnnotation(VarRef.get(mth, reg));
code.attachAnnotation(VarNode.getRef(mth, reg));
}
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 +122,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)) {
@@ -152,10 +161,18 @@ public class InsnGen {
}
useType(code, codeVar.getType());
code.add(' ');
defVar(code, codeVar);
}
/**
* Variable definition without type, only var name
*/
private void defVar(ICodeWriter code, CodeVar codeVar) {
String varName = mgen.getNameGen().assignArg(codeVar);
if (code.isMetadataSupported()) {
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
code.attachDefinition(VarNode.get(mth, codeVar));
}
code.add(mgen.getNameGen().assignArg(codeVar));
code.add(varName);
}
private String lit(LiteralArg arg) {
@@ -164,7 +181,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 +219,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 +526,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 +547,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 +702,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 +747,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 +761,23 @@ public class InsnGen {
ctor.add(AFlag.DONT_GENERATE);
}
}
code.attachDefinition(cls);
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 +793,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,22 +801,16 @@ 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++;
break;
case SUPER:
ClassInfo superCallCls = getClassForSuperCall(code, callMth);
if (superCallCls != null) {
useClass(code, superCallCls);
code.add('.');
}
// use 'super' instead 'this' in 0 arg
code.add("super").add('.');
k++;
callSuper(code, callMth);
k++; // use 'super' instead 'this' in 0 arg
code.add('.');
break;
case STATIC:
@@ -792,13 +824,29 @@ public class InsnGen {
}
if (callMthNode != null) {
code.attachAnnotation(callMthNode);
code.add(callMthNode.getAlias());
}
if (insn.contains(AFlag.FORCE_RAW_NAME)) {
code.add(callMth.getName());
} else {
code.add(callMth.getAlias());
}
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);
@@ -896,7 +944,7 @@ public class InsnGen {
code.add(", ");
}
CodeVar argCodeVar = callArgs.get(i).getSVar().getCodeVar();
code.add(nameGen.assignArg(argCodeVar));
defVar(code, argCodeVar);
}
}
// force set external arg names into call method args
@@ -904,7 +952,8 @@ public class InsnGen {
int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg
for (int i = startArg; i < extArgsCount; i++) {
RegisterArg extArg = (RegisterArg) customNode.getArg(i);
callArgs.get(i).setName(extArg.getName());
RegisterArg callRegArg = callArgs.get(i);
callRegArg.getSVar().setCodeVar(extArg.getSVar().getCodeVar());
}
code.add(" -> {");
code.incIndent();
@@ -914,34 +963,43 @@ public class InsnGen {
code.startLine('}');
}
@Nullable
private ClassInfo getClassForSuperCall(ICodeWriter code, MethodInfo callMth) {
ClassNode useCls = mth.getParentClass();
ClassInfo insnCls = useCls.getClassInfo();
ClassInfo declClass = callMth.getDeclClass();
if (insnCls.equals(declClass)) {
return null;
private void callSuper(ICodeWriter code, MethodInfo callMth) {
ClassInfo superCallCls = getClassForSuperCall(callMth);
if (superCallCls == null) {
// unknown class, add comment to keep that info
code.add("super/*").add(callMth.getDeclClass().getFullName()).add("*/");
return;
}
ClassNode topClass = useCls.getTopParentClass();
if (topClass.getClassInfo().equals(declClass)) {
return declClass;
ClassInfo curClass = mth.getParentClass().getClassInfo();
if (superCallCls.equals(curClass)) {
code.add("super");
return;
}
// search call class
ClassNode nextParent = useCls;
do {
ClassInfo nextClsInfo = nextParent.getClassInfo();
if (nextClsInfo.equals(declClass)
|| ArgType.isInstanceOf(mth.root(), nextClsInfo.getType(), declClass.getType())) {
if (nextParent == useCls) {
return null;
}
return nextClsInfo;
}
nextParent = nextParent.getParentClass();
} while (nextParent != null && nextParent != topClass);
// use custom class
useClass(code, superCallCls);
code.add(".super");
}
// search failed, just return parent class
return useCls.getParentClass().getClassInfo();
/**
* Search call class in super types of this
* and all parent classes (needed for inlined synthetic calls)
*/
@Nullable
private ClassInfo getClassForSuperCall(MethodInfo callMth) {
ArgType declClsType = callMth.getDeclClass().getType();
ClassNode parentNode = mth.getParentClass();
while (true) {
ClassInfo parentCls = parentNode.getClassInfo();
if (ArgType.isInstanceOf(root, parentCls.getType(), declClsType)) {
return parentCls;
}
ClassNode nextParent = parentNode.getParentClass();
if (nextParent == parentNode) {
// no parent, class not found
return null;
}
parentNode = nextParent;
}
}
void generateMethodArguments(ICodeWriter code, BaseInvokeNode insn, int startArgNum,
@@ -1016,7 +1074,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 +1083,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.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.JadxArgs;
import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.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;
@@ -24,6 +26,7 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IfNode;
@@ -32,13 +35,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 +115,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());
@@ -130,8 +145,9 @@ public class MethodGen {
} else {
classGen.useType(code, mth.getReturnType());
code.add(' ');
code.attachDefinition(mth);
code.add(mth.getAlias());
MethodNode defMth = getMethodForDefinition();
code.attachDefinition(defMth);
code.add(defMth.getAlias());
}
code.add('(');
@@ -164,12 +180,20 @@ public class MethodGen {
return true;
}
private MethodNode getMethodForDefinition() {
MethodReplaceAttr replaceAttr = mth.get(AType.METHOD_REPLACE);
if (replaceAttr != null) {
return replaceAttr.getReplaceMth();
}
return mth;
}
private void addOverrideAnnotation(ICodeWriter code, MethodNode mth) {
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr == null) {
return;
}
if (!overrideAttr.isAtBaseMth()) {
if (!overrideAttr.getBaseMethods().contains(mth)) {
code.startLine("@Override");
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
code.add(" // ");
@@ -184,7 +208,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 +251,11 @@ public class MethodGen {
classGen.useType(code, argType);
}
code.add(' ');
if (code.isMetadataSupported() && ssaVar != null) {
code.attachDefinition(VarDeclareRef.get(mth, var));
String varName = nameGen.assignArg(var);
if (code.isMetadataSupported() && ssaVar != null /* for fallback mode */) {
code.attachDefinition(VarNode.get(mth, var));
}
code.add(nameGen.assignArg(var));
code.add(varName);
i++;
if (it.hasNext()) {
@@ -240,12 +265,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 +308,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 +408,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 +435,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 +552,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);
}
}
@@ -7,6 +7,7 @@ import java.util.Set;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
@@ -162,8 +163,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;
}
}
@@ -174,7 +174,7 @@ public class NameGen {
private String makeNameForType(ArgType type) {
if (type.isPrimitive()) {
return makeNameForPrimitive(type);
return type.getPrimitiveType().getShortName().toLowerCase();
}
if (type.isArray()) {
return makeNameForType(type.getArrayRootElement()) + "Arr";
@@ -182,10 +182,6 @@ public class NameGen {
return makeNameForObject(type);
}
private static String makeNameForPrimitive(ArgType type) {
return type.getPrimitiveType().getShortName().toLowerCase();
}
private String makeNameForObject(ArgType type) {
if (type.isGenericType()) {
return StringUtils.escape(type.getObject().toLowerCase());
@@ -195,19 +191,32 @@ public class NameGen {
if (alias != null) {
return alias;
}
ClassInfo extClsInfo = ClassInfo.fromType(mth.root(), type);
String shortName = extClsInfo.getShortName();
String vName = fromName(shortName);
if (vName != null) {
return vName;
}
if (shortName != null) {
return StringUtils.escape(shortName.toLowerCase());
}
return makeNameForCheckedClass(ClassInfo.fromType(mth.root(), type));
}
return StringUtils.escape(type.toString());
}
private String makeNameForCheckedClass(ClassInfo classInfo) {
String shortName = classInfo.getAliasShortName();
String vName = fromName(shortName);
if (vName != null) {
return vName;
}
String lower = StringUtils.escape(shortName.toLowerCase());
if (shortName.equals(lower)) {
return lower + "Var";
}
return lower;
}
private String makeNameForClass(ClassInfo classInfo) {
String alias = getAliasForObject(classInfo.getFullName());
if (alias != null) {
return alias;
}
return makeNameForCheckedClass(classInfo);
}
private static String fromName(String name) {
if (name == null || name.isEmpty()) {
return null;
@@ -238,7 +247,12 @@ public class NameGen {
case CONSTRUCTOR:
ConstructorInsn co = (ConstructorInsn) insn;
return makeNameForObject(co.getClassType().getType());
MethodNode callMth = mth.root().getMethodUtils().resolveMethod(co);
if (callMth != null && callMth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
// don't use name of anonymous class
return null;
}
return makeNameForClass(co.getClassType());
case ARRAY_LENGTH:
return "length";
@@ -264,18 +278,22 @@ public class NameGen {
}
private String makeNameFromInvoke(MethodInfo callMth) {
String name = callMth.getName();
String name = callMth.getAlias();
ClassInfo declClass = callMth.getDeclClass();
if ("getInstance".equals(name)) {
// e.g. Cipher.getInstance
return makeNameForClass(declClass);
}
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);
return makeNameForClass(declClass);
}
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
if ("forName".equals(name) && declClass.getType().equals(ArgType.CLASS)) {
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
}
if (name.startsWith("to")) {
@@ -10,8 +10,8 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.dex.attributes.AFlag;
@@ -26,6 +26,7 @@ import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
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.FieldNode;
import jadx.core.dex.nodes.IBlock;
@@ -161,7 +162,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 +214,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(");");
@@ -346,11 +347,11 @@ public class RegionGen extends InsnGen {
if (arg == null) {
code.add("unknown"); // throwing exception is too late at this point
} else if (arg instanceof RegisterArg) {
CodeVar codeVar = ((RegisterArg) arg).getSVar().getCodeVar();
SSAVar ssaVar = ((RegisterArg) arg).getSVar();
if (code.isMetadataSupported()) {
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
code.attachDefinition(VarNode.get(mth, ssaVar));
}
code.add(mgen.getNameGen().assignArg(codeVar));
code.add(mgen.getNameGen().assignArg(ssaVar.getCodeVar()));
} else if (arg instanceof NamedArg) {
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
} else {
@@ -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;
}
}
@@ -12,13 +12,13 @@ import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.SimpleCodeWriter;
import jadx.api.metadata.ICodeMetadata;
import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.core.codegen.ClassGen;
import jadx.core.codegen.MethodGen;
import jadx.core.codegen.json.cls.JsonClass;
@@ -180,24 +180,27 @@ public class JsonCodeGen {
}
String[] lines = codeStr.split(ICodeWriter.NL);
Map<Integer, Integer> lineMapping = code.getLineMapping();
Map<CodePosition, Object> annotations = code.getAnnotations();
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
ICodeMetadata metadata = code.getCodeMetadata();
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
int linesCount = lines.length;
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
int lineStartPos = 0;
int newLineLen = ICodeWriter.NL.length();
for (int i = 0; i < linesCount; i++) {
String codeLine = lines[i];
int line = i + 2;
JsonCodeLine jsonCodeLine = new JsonCodeLine();
jsonCodeLine.setCode(codeLine);
jsonCodeLine.setSourceLine(lineMapping.get(line));
Object obj = annotations.get(new CodePosition(line));
Object obj = metadata.getAt(lineStartPos);
if (obj instanceof InsnCodeOffset) {
long offset = ((InsnCodeOffset) obj).getOffset();
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
}
codeLines.add(jsonCodeLine);
lineStartPos += codeLine.length() + newLineLen;
}
return codeLines;
}
@@ -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);
}
}
@@ -25,6 +25,8 @@ public enum AFlag {
HIDDEN, // instruction used inside other instruction but not listed in args
DONT_RENAME, // do not rename during deobfuscation
FORCE_RAW_NAME, // force use of raw name instead alias
ADDED_TO_REGION,
EXC_TOP_SPLITTER,
@@ -33,8 +35,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 +78,14 @@ 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,
USE_LINES_HINTS, // source lines info in methods can be trusted
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<>();
@@ -5,10 +5,6 @@ public interface ILineAttributeNode {
void setSourceLine(int sourceLine);
int getDecompiledLine();
void setDecompiledLine(int line);
int getDefPosition();
void setDefPosition(int pos);
@@ -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(':');
@@ -7,21 +7,11 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
private int sourceLine;
private int decompiledLine;
// the position exactly where a node declared at in decompiled java code.
/**
* Position where a node declared at in decompiled code
*/
private int defPosition;
@Override
public int getDefPosition() {
return this.defPosition;
}
@Override
public void setDefPosition(int defPosition) {
this.defPosition = defPosition;
}
@Override
public int getSourceLine() {
return sourceLine;
@@ -33,13 +23,13 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
}
@Override
public int getDecompiledLine() {
return decompiledLine;
public int getDefPosition() {
return this.defPosition;
}
@Override
public void setDecompiledLine(int decompiledLine) {
this.decompiledLine = decompiledLine;
public void setDefPosition(int defPosition) {
this.defPosition = defPosition;
}
public void addSourceLineFrom(LineAttrNode lineAttrNode) {
@@ -50,6 +40,6 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
public void copyLines(LineAttrNode lineAttrNode) {
setSourceLine(lineAttrNode.getSourceLine());
setDecompiledLine(lineAttrNode.getDecompiledLine());
setDefPosition(lineAttrNode.getDefPosition());
}
}
@@ -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;
}
}

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