Compare commits

...

326 Commits

Author SHA1 Message Date
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
skylot 445e91e6b5 docs: update readme 2021-11-20 18:04:08 +03:00
Skylot 9daf386d66 build: bundle JRE with jadx-gui 2021-11-20 14:17:05 +00:00
Skylot 49b4079cd8 chore: update dependencies 2021-11-20 16:35:23 +03:00
Jan Peter Stotz 0ffa1838a2 chore: Updated German translation 2021-11-20 16:02:39 +03:00
Jan S 0efca29e95 fix: configured resource indexing size limit is now correctly considered (PR #1278) 2021-11-18 18:58:20 +03:00
Skylot 0ab933efff perf: cache 'implements' list (heavily used in type inference) 2021-11-15 21:03:54 +00:00
Skylot 4ee4a34323 fix: check if inner classes for missing R class already exist (#1269) 2021-11-15 16:17:38 +00:00
Yotam 985ccd6bba feat: save jobf when decompiling to Java (#1274) (PR #1275)
* Save jobf when decompiling to Java through the cli
* Skip jobf saving if it's empty
* Update jadx-core/src/main/java/jadx/core/deobf/DeobfPresets.java

Co-authored-by: Yotam Nachum <me@yotam.net>
Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
2021-11-14 23:49:27 +03:00
Skylot 570e7528a7 fix(gui): use correct definition position on jump after code reload (#1273) 2021-11-14 13:05:22 +00:00
Skylot 918585968d perf(gui): on rename unload dependent classes instead recompile 2021-11-13 14:53:51 +00:00
Skylot cf918a897f fix(gui): collect FlatLaf themes without reflection 2021-11-12 18:01:52 +00:00
Skylot 5fc27c1136 perf(gui): improve decompilation speed (#1269)
- use index only in one thread to reduce synchronization locks
- collect usage info on request, remove global collection
- adjust decompilation order to reduce locks, improve memory usage
- prefill cache of super types in clsp graph to remove locks
2021-11-12 13:54:56 +00:00
Skylot 6bcc48c462 chore: update gradle and dependencies 2021-11-11 11:12:21 +00:00
Skylot 4dc368c7d0 fix: save resources before decompilation (#1270) 2021-11-11 10:54:17 +00:00
Skylot 17f99ed928 fix: adjust class processing order for correct methods inline (#1264) 2021-11-10 15:27:25 +00:00
Skylot 954d239b52 fix: resolve methods collisions after type fix (#1263) 2021-11-08 16:59:41 +00:00
Skylot ea167cbefc fix(gui): resolve NPE in resource index for single dex, other minor issues 2021-11-08 15:18:30 +00:00
Skylot 90a436236d fix(gui): wrap long array data (workaround for RSTA hang) (#1266) 2021-11-06 17:35:26 +00:00
Skylot 4479a3fbd5 fix(gui): restore resource tabs on project open 2021-10-29 15:23:22 +01:00
Skylot f5216b77f8 fix(gui): resolve some minor rename issues
- correct variable definition in method arguments
- refresh current class if rename interface method
2021-10-28 18:38:53 +01:00
Skylot 39dc288978 feat(gui): save open tabs in project file 2021-10-27 21:28:18 +01:00
Skylot f37005958f fix(gui): sort results in usage dialog (#1104) 2021-10-27 15:22:33 +01:00
Skylot dfdc14ea86 feat: rename without deobfuscation, save renames in project (#1076 #1022) 2021-10-26 20:23:21 +01:00
Skylot 82712776cc feat(gui): add issues panel and summary report (#986) 2021-10-23 16:03:06 +01:00
Skylot 439446816c fix: update Quark report format parsing 2021-10-22 17:07:45 +01:00
Jan S 940108661c fix(gui): "Always Select Opened File/Class" was not syncing upon activation (PR #1261) 2021-10-22 15:17:27 +03:00
Skylot 0423f33e93 fix: use correct iteration over code points in string 2021-10-21 18:24:57 +01:00
Jan Peter Stotz c2a4a7a6c2 fix: "rename to make printable" option was renaming fully printable class names 2021-10-21 15:51:22 +03:00
Skylot ff4e3dd976 fix: show cause exception if class codegen failed (#1258) 2021-10-20 19:21:49 +01:00
Skylot 94b00b4e7a feat(gui): add option to change line numbers mode (#1223) 2021-10-20 18:42:15 +01:00
Skylot 48252c3c3d feat: add option for code comments levels (#998) 2021-10-19 16:47:20 +01:00
Skylot 37adce2efb chore: update dependencies 2021-10-17 19:53:01 +01:00
Skylot 358cddd9a7 fix: support dynamic strings concat (#1250) 2021-10-17 19:43:51 +01:00
Skylot 418df2fd93 tests: allow to set target java version, use D8 as fallback converter 2021-10-17 19:43:51 +01:00
Skylot cd153c76f2 feat: add raung input plugin, use raung in tests 2021-10-12 19:17:04 +01:00
Skylot f30c14b277 feat(gui): don't run full decompilation for usage search
New approach will run partial decompilation for classes
from usage info collected on file load (pre-decompilation stage).
2021-09-30 16:23:09 +01:00
Skylot 3a29812241 fix(gui): resolve NPE during index (#1254) 2021-09-30 14:07:19 +01:00
Muntashir Al-Islam 67e6b647a2 fix(build): force protobuf version to prevent Java-7 issue (PR #1255)
Signed-off-by: Muntashir Al-Islam <muntashirakon@riseup.net>
2021-09-30 15:53:28 +03:00
Skylot ea0795c8a9 fix: restore fields order if init use other fields (#1235) 2021-09-15 22:27:25 +01:00
Skylot 099acfca1d fix: don't add parenthesis for field init code 2021-09-15 21:55:24 +01:00
Skylot 8969d11a22 fix: restore fields order on init code move (#678) 2021-09-15 21:55:24 +01:00
Skylot eedf32d197 fix: support nested try/finally blocks (#1249) 2021-09-09 18:44:24 +01:00
Skylot 9c8642593c fix: don't visit inner classes twice in pre-processing 2021-09-08 19:07:25 +01:00
MrIkso 8e89a2ef1b feat(gui): added option to always select opened file/class 2021-09-03 19:12:29 +03:00
MrIkso 316c2fdd4d fix(gui): updated SearchBar in code viewer 2021-09-03 19:12:29 +03:00
Skylot 6bbf1d0996 build: update gradle wrapper to 7.2 (#1244) 2021-09-03 16:38:37 +01:00
Skylot e854ba3c44 build: upgrade gradle to 7.2 (#1244) 2021-08-31 17:20:04 +01:00
Nico Mexis f681c8963d fix: use maven-publish for JitPack and other fixes (PR #1242)
* Update dependencies
* Fix spaces in file paths
* Update Gradle for LGTM
* Update spotless
* Fix Jitpack

Co-authored-by: Skylot <skylot@gmail.com>
2021-08-27 19:05:52 +03:00
green9317 8d5f22e43d fix(xml): handle incorrect android manifest namespace chunks (#1232) (PR #1243)
* allow for handling of incorrect android manifest namespace chunks
* Update BinaryXMLParser.java
2021-08-27 17:40:08 +03:00
Skylot a62de839be fix: handle unbound type variables in type inference (#1237) 2021-08-24 13:54:32 +01:00
Skylot 5af60b2ff4 fix(gui): improve constructors and classes usage list 2021-08-23 17:10:43 +01:00
Skylot c8d7fce938 fix(gui): use correct type formatter in class tree 2021-08-22 18:53:12 +01:00
Skylot 90fbc790d9 fix(gui): exclude declaration from usage list (#1110) 2021-08-22 18:03:15 +01:00
Skylot 1ce3fc972a fix: improve disassemble view for java-input 2021-08-22 16:53:54 +01:00
Skylot 9ea3f0f240 fix: support 'swap' and 'wide' opcodes, other fixes for java-input 2021-08-20 20:59:30 +03:00
Skylot 868fa90097 feat: allow to load directories 2021-08-15 14:44:55 +01:00
Skylot 55bb20cf29 fix: prevent collisions in method ids for java-input 2021-08-13 23:07:33 +03:00
Skylot 7c0671c81b feat: rewrite try-catch processing 2021-08-13 23:07:33 +03:00
Skylot 12a66bd83e refactor: remove samples module 2021-08-13 23:07:33 +03:00
Skylot 1efdcd7b10 feat: input plugin for java bytecode 2021-08-13 23:07:29 +03:00
Hen Ry 2d9bcdb87a fix(gui): update Messages_de_DE.properties (PR #1230)
* fix(gui): update Messages_de_DE.properties

* #-fix(gui): update Messages_de_DE.properties
2021-08-12 18:25:36 +03:00
Hen Ry ac9cace8f6 fix(gui): update Messages_de_DE.properties (PR #1228)
* Update Messages_de_DE.properties
* Update Messages_de_DE.properties

Fix
* uncomment translated lines

Co-authored-by: Skylot <skylot@gmail.com>
2021-08-12 17:12:31 +03:00
Yaroslav f9bf27579e fix: additional checks for export to gradle (#1222) (PR #1224) 2021-08-05 15:16:05 +03:00
Skylot 667cae2e62 chore: use SVG icon for Quark (thanks @MrIkso) 2021-08-04 19:18:03 +01:00
Skylot e8e0491cb5 chore: fix code formatting and resolve PR issues 2021-08-04 20:41:17 +03:00
Yaroslav ee12f0bd18 feat(gui): use SVG icons, xml resources impovements (PR #1221)
* fix(xml): add more file based resources type to skip
* fix(res): fix #1060, styles might contain dots in name
* fix(res): use lowercase name on deobfuscated\renamed resources names and id in hex format
* feat(gui): update gui under FlatLaf
* fix(gui): use FlatSVGIcon to fix icons brightness difference
* fix(gui): use source lines only decompiled java code
Co-authored-by: MrIkso <mrkso821@gmail.com>
2021-08-04 20:40:49 +03:00
Skylot 5f24193c49 chore: update dependencies 2021-08-02 18:44:21 +03:00
Skylot dd29d37154 feat(gui): use FlatLaf for themes support 2021-08-02 18:32:35 +03:00
Jan S b63e3aca00 feat: add origin file info (code comment for classes, tooltip in tree) (PR #1219)
* chore: make escapeHtml also replace close angle brackets
* chore: if multiple files are loaded, show their path as tooltip
* feat: add comment on classes that contains the dex file name it has been loaded from
* fix: expected line numbers in unit test fixed
* fix: delete comments from generated code as it may contain a colon
* chore: comment removing wasn't able to handle Linux paths with slash
2021-08-01 18:15:05 +03:00
Skylot 859674ce7e fix: keep lambda classes if static field used outside (#1215) 2021-07-25 15:10:34 +01:00
Jan S ea8b9ce462 fix(xml): reversed XML attribute name decoding priority (#1208)(PR #1214) 2021-07-24 17:13:27 +03:00
Skylot b5720bd14e fix(gui): improve Quark tasks scheduling and report viewer (#1119) 2021-07-02 21:32:57 +01:00
Shaun Dang cc99409a7e feat(gui): improvements of Quark integration (#1119) (PR #1199)
* Add quark installation
* add error/warning dialog
* change Quark task to background task
* fix missing the last line of input stream
2021-06-30 18:04:50 +03:00
Skylot fef3e21c70 fix: resolve type vars from outer class (#1192) 2021-06-19 13:44:15 +01:00
Skylot f3d76c433a fix: prevent StackOverflowError in MarkFinallyVisitor (#1191) 2021-06-18 17:34:21 +01:00
nitram84 31d5715723 fix: prevent duplicated override annotations and minor optimization (#1188)(PR #1190)
* Handle explicit override annotations
* Skip override checks for private methods
2021-06-18 00:40:31 +03:00
Skylot 592eef3cda fix: resolve NPE in enum processing 2021-06-04 20:21:38 +01:00
Skylot 0541748e5f fix: resolve type variables from super types (#870) 2021-06-04 19:31:47 +01:00
Skylot cf1d9e8372 fix: allow to reuse enum fields in static fields (#1019) 2021-06-01 20:57:48 +01:00
Skylot b096d8869e fix: support branched object construction (#1019) 2021-06-01 15:58:08 +01:00
Skylot 2acc14b04a fix: resolve generic type vars for instance field get instruction (#918) 2021-05-30 10:18:35 +01:00
Skylot 1f1efb0e17 fix: allow local variables have name same as instance fields (#1183) 2021-05-29 20:18:49 +01:00
Skylot 1c08d854fb fix(gui): add memory limit checks to export and load tasks (#1181) 2021-05-29 17:11:42 +01:00
Skylot 9c252fb226 fix(gui): add memory and time limits for decompile task (#1181) 2021-05-28 17:52:52 +01:00
Skylot 4bda3b9e9b build: exclude exe build on not Windows (#1180) 2021-05-27 15:41:34 +01:00
Skylot 21da3c8602 fix: reword rename flags in cli and gui (#1178) 2021-05-25 10:10:16 +01:00
Skylot 7ec43776ae chore: update gradle and dependencies 2021-05-21 19:06:30 +01:00
Skylot 07d7e68dc2 fix: format Android resources ids as hex (#1171) 2021-05-20 18:41:46 +01:00
Skylot 8785c33d06 feat: add option to disable methods inline (#1170) 2021-05-18 10:39:30 +01:00
Skylot 661ebe439d fix: inline class as anonymous if it used only once (#1168) 2021-05-11 15:33:13 +01:00
Skylot 4732fa36a6 fix(gui): improve code area performance and line numbers repaint (#1167) 2021-05-07 17:36:06 +01:00
LBJ-the-GOAT 8dad158ae6 fix: resolve LGTM alerts (PR #1162)
* fix LGTM alerts
* Update jadx-gui/src/main/java/jadx/gui/device/debugger/BreakpointManager.java
* Update Smali.java

Co-authored-by: tobias <tobias.hotmail.com>
Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
2021-04-25 23:55:47 +03:00
LBJ-the-GOAT bfc343d1ee fix(gui): correct port retry in smali debugger (#1136) (PR #1160)
Co-authored-by: tobias <tobias.hotmail.com>
2021-04-25 20:26:46 +03:00
bagipro ca723c3b47 fix(res): fix invalid XML NS names (PR #1158)
* Fix issue in invalid XML NS names
* fix: replace methods not available in Java 8

Co-authored-by: bagipro <bugi@MacBook-Pro-2.local>
Co-authored-by: Skylot <skylot@gmail.com>
2021-04-24 13:21:09 +03:00
Jan S b6657351fc fix(res): fix XML attribute decoding (#1156) (PR #1157) 2021-04-23 12:48:52 +03:00
Jan S f26032ed7d fix(gui): small search dialog optimizations (PR #1143)
* avoid extra vertical space below search options when dialog is wide
* make sure the search dialog has the correct size and the options are aligned properly
* regex search: make searchField background red in case of invalid regex entered
2021-04-23 12:33:52 +03:00
Skylot 012f7665aa chore: update gradle to 7.0, update dependencies, fix some build warnings 2021-04-22 19:42:01 +01:00
Skylot c28e8142f4 chore: fix warnings reported by snyk 2021-04-21 11:29:46 +01:00
Skylot 1462acbb92 chore: remove not needed file 2021-04-18 22:22:27 +01:00
Skylot c52c659b94 fix: correct inline flag for variables used in anonymous classes (#1154) 2021-04-18 19:10:59 +01:00
LBJ-the-GOAT 6bf358fc66 feat(gui): improve exclude package feature (#1151) (PR #1152)
* include & exclude multiple packages at the same time
* use to tree instead of list to display packages.

Co-authored-by: tobias <tobias.hotmail.com>
2021-04-16 13:37:11 +03:00
Skylot e8f57d3ace fix: prevent infinite loop in block tree mod for loops (#1147) 2021-04-08 19:01:18 +01:00
Skylot 766e7193b9 fix(gui): use correct offset for code line (#1141) 2021-04-01 21:15:17 +03:00
Choiman1559 6fe762aa7b fix(gui): update Korean translation (PR #1140)
* Update Messages_ko_KR.properties
* remove empty line insertion

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
2021-03-31 18:00:04 +03:00
Skylot 7065b1b3ba fix: remove method with more than 255 args (#1026) 2021-03-29 20:46:18 +01:00
Skylot 72b812acad fix: don't unload annotation attributes (#1089) 2021-03-29 21:35:50 +03:00
Skylot d7ffa21fbe chore: forbid use DebugUtils class with checkstyle 2021-03-29 19:35:43 +01:00
Skylot c95d64909a feat(cli): add decompilation progress 2021-03-29 14:56:40 +03:00
skylot a5b2b04317 docs: add smali debugger to readme 2021-03-28 13:54:38 +03:00
LBJ-the-GOAT 4705194a1d feat(gui): add a smali debugger (#1136) (PR #1137)
* add a smali debugger
* debugger: support android 11, support 9(may be) & 10 if debug_info available, add rerun.
* debugger: support get/set fields of this, change icons, fix bugs.
* debugger: add timeout to attach

Co-authored-by: tobias <tobias.hotmail.com>
2021-03-28 13:23:07 +03:00
Skylot 19572a674e fix: improve deobfuscation performance for overridden methods (#1133) 2021-03-20 15:48:56 +00:00
Skylot a1247f4d96 chore: update dependencies 2021-03-17 13:51:30 +00:00
Skylot 52412dfe31 fix(gui): resolve potential command injection, fix other code style issues (#1119) 2021-03-12 14:54:15 +00:00
Shaun Dang ab02e6e7c3 feat(gui): add Quark-Engine integration (#1119) (PR #1135) 2021-03-12 16:44:42 +03:00
bagipro 9ef99a2b92 feat: implement Android App Bundle support (#1129) (PR #1131)
* Implement proto parse
* fix code formatting
* fix tests with empty input
* revert not needed code style changes
* Implement parse of resources.pb for AAB

Co-authored-by: bagipro <bugi@MacBook-Pro-2.local>
Co-authored-by: Skylot <skylot@gmail.com>
2021-03-08 21:34:52 +03:00
skylot 4e5fac4b88 feat(gui): add code comments (#359) (PR #1127)
* feat(gui): add code comments (#359)
* refactor: replace instanceof search with method dispatch in RegionGen
* fix: various bug fixes and improvements for code comments
* fix(gui): support multiline code comments
* fix: resolve code differences after class reload
* fix(gui): add search for comments, allow search in active tab only
* fix: correct search for inner classes
* fix(gui): run full index on search dialog open
2021-03-04 17:45:48 +03:00
Skylot 7a14aaa17e fix: resolve variable name shadowing in anonymous classes (#1124) 2021-03-02 18:43:52 +00:00
LBJ-the-GOAT 650863836c feat(gui): improve smali printer to show bytecode (#1114) (PR #1126)
* improve smali printer to show bytecode
* set insnStart position before start decoding
* swithed line 62 and line 63, to get the proper bytes, insnStart must to be set before start to decode.

Co-authored-by: tobias <tobias.hotmail.com>
2021-03-02 16:02:56 +03:00
Skylot 3a69ac23c0 fix: restore enums with removed fields (#926) 2021-02-23 16:59:33 +00:00
Skylot b873c6ae4d refactor: use interface for CodeWriter
Details:
- add simple and annotated code writers to allow
   skip code annotations processing in jadx-cli and other places
- add annotated code info to use only than needed
- allow to set provider for codewriter in JadxArgs
- add JadxArgs argument to constructor to allow change output
- add cli option to insert debug line numbers as code comments
   (example for previous change)
2021-02-21 17:34:33 +03:00
Surendrajat 4835b1b897 fix(gui): compact TabComponent labels and TabToolTip (#1120) (PR #1121)
* Compact CodePanel labels and TabToolTip
* Remove top padding from tab title
2021-02-21 15:01:10 +03:00
Skylot 67def6319e feat(cli): add option to change deobfuscation map file (#1117)
Signed-off-by: Skylot <skylot@gmail.com>
2021-02-13 14:22:22 +00:00
Choiman1559 c56d9ac7ce fix(gui): updated korean translation (PR #1118) 2021-02-13 15:49:42 +03:00
Skylot e6588c4307 fix(gui): correct font save with '-' in name (#1116) 2021-02-12 17:17:41 +00:00
Skylot 712389ab24 build: add windows artifact, use nightly.link for download unstable build (#1113) 2021-02-06 12:43:49 +00:00
Skylot 5f1be38490 build: upload unstable binaries as build artifact (#1113) 2021-02-05 21:33:04 +00:00
Skylot 7982592c6e build: remove Travis and Bintray, disable codecov and sonarqube (#1113) 2021-02-04 12:14:39 +00:00
Skylot 69574918b5 fix: allow constructor invoke as lambda 2021-02-02 18:27:36 +00:00
Skylot f6783e8f5e fix: implement 'copy' and 'isSame' methods in InvokeCustomNode 2021-02-02 16:27:45 +00:00
Skylot 913b00a4d4 build: setup simple test build using Github Actions 2021-02-01 19:37:19 +00:00
Skylot 22fa132110 fix: support instance invoke for 'invoke-custom' instruction (#384) 2021-02-01 19:02:31 +00:00
Skylot 5a30fc0300 fix: improve const inlining in finally blocks (#917) 2021-01-30 19:44:38 +03:00
LBJ-the-GOAT c774ffc979 feat(gui): search in resource files (#347) (#1032) (PR #1108)
Co-authored-by: tobias <tobias.hotmail.com>
2021-01-30 19:34:20 +03:00
Skylot c93e7fb9cd fix: detect loaded class duplication (#1107) 2021-01-29 11:31:00 +00:00
Surendrajat 3437888964 fix(gui): use common keyboard shortcuts for navigation (#1085) (PR #1106) 2021-01-27 21:37:13 +03:00
Surendrajat b314e0bdda fix(gui): improve color schemes (#1101) (PR #1105)
* do not use hardcoded color for highlighting
* add a new theme: druid
2021-01-27 21:02:16 +03:00
LBJ-the-GOAT 2bdde6a528 fix(gui): fix variable usage & caret position after rename (#1099) (PR #1103)
Co-authored-by: tobias <tobias.hotmail.com>
2021-01-27 19:23:07 +03:00
LBJ-the-GOAT c61cb80a8b feat(gui): rename local variables (#1023) (#1084) (PR #1098)
Co-authored-by: tobias <tobias.hotmail.com>
2021-01-27 14:58:57 +03:00
Fi5t 4217aab933 fix: new gradle export (#1095) (PR #1097)
* Update export of gradle project

* Fix hardcoded index

* Add versionCode and versionName to the export template
2021-01-26 21:31:12 +03:00
LBJ-the-GOAT ffb2956d90 fix(gui): fix caret positions of search/usage/goto decl, add search to popup menu (#1093) (PR #1094)
* fix caret positions of search/usage/goto decl to matched place & add menu items for search

* Remove static field for main window

Co-authored-by: tobias <tobias.hotmail.com>
2021-01-25 14:49:21 +03:00
Skylot 9744547fab fix(gui): correct line numbers with enabled line wrap (#1092) 2021-01-24 16:10:37 +00:00
Choiman1559 b580d1cf5b fix(gui): update korean translation (PR #1091) 2021-01-23 20:24:09 +03:00
LBJ-the-GOAT 855c7b608e feat(gui): add shortcuts to TabbedPanel and enhance JumpPosition (#1085) (PR #1090)
* Add shortcuts to TabbedPanel & enhance JumpPosition

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

Co-authored-by: tobias <tobias.hotmail.com>
2021-01-23 14:20:08 +03:00
Shatyuka 707ed9a828 fix(gui): codearea popup menu always disabled in macos (#1052) (PR #1086) 2021-01-18 19:03:32 +03:00
alienhe a3ea514521 fix: elemSize=0 fill_array_data_payload insn obfuscation (PR #1082)
Co-authored-by: hexun <hexun@fenbi.com>
2021-01-15 12:10:33 +03:00
Skylot 3dfaec5033 feat: initial support for 'invoke-custom' instruction (#384) 2021-01-14 20:15:23 +00:00
Skylot 778106c41b chore: update gradle and java dependencies 2021-01-14 19:58:01 +00:00
Skylot c47e9cdde4 fix: allow to load Spring Boot jar (#1066) 2021-01-04 20:31:17 +03:00
Skylot 8dd76420c8 fix(deobf): complete disable rename if all rename options unchecked (#1076) 2021-01-02 22:22:45 +03:00
Skylot dfe026ac2d test(gui): fix localization test 2021-01-01 18:04:42 +03:00
Choiman1559 f0849d0ed1 feat(gui): added Korean translation (PR #1074)
* Added korean translation properties

* Added korean translation

* Update Messages_ko_KR.properties

* Update NLS.java
2021-01-01 18:00:10 +03:00
Skylot b7ca898b77 perf: improve processing of override related methods (#1072) 2020-12-31 13:33:18 +03:00
green9317 1b8b377f90 feat(gui): allow use regex in the search dialog (PR #1069)
* Implements the option to use Regex on the Search Dialog

* Updated the way search works to pass a search settings class with options set rather than method arguments

* Fixing style issues

* Updating Style Fix

* Cleaning code

* Updating code to combine SearchSettings and Search Impl as well as efficiency improvements.

* Fixing bug caused from moving code in the searchImpl class

* Fixing a minor bug

* adding style fixes
2020-12-29 22:12:20 +03:00
Skylot 7227f1ac78 fix: don't skip method instructions in fallback mode (#1063) 2020-12-24 12:58:15 +00:00
Jan S 23f088105e fix(gui): synchronized conditional usageList remove method added (PR #1065) 2020-12-24 15:42:52 +03:00
Skylot 3bbb6b1058 fix: rename all related overridden methods in deobf map file (#1058) 2020-12-21 14:47:57 +00:00
Skylot 3a4895b21c test: check code after reload 2020-12-21 17:38:29 +03:00
Jan S 4e6afe9b64 fix(gui): do not show empty rename dialog if user chooses not to change DeobfuscationForceSave settings (PR #1061) 2020-12-21 16:23:37 +03:00
Alisson Lauffer dd4c20249f fix(gui): increase settings vertical scroll increment (PR #1059) 2020-12-21 15:14:19 +03:00
AdamN b54b2d47e9 fix(res): use lowercase for resource filename and only use underscore for compatibility with newer android studio (#1043, PR #1057) 2020-12-20 19:14:09 +03:00
Skylot 64fb587d0f fix(gui): improve rename for overridden methods 2020-12-19 18:07:51 +03:00
Skylot 2ca3c65300 fix(deobf): don't rename unresolved or classpath overridden methods
Change details:
- use common code for process override methods in deobfuscator (move OverrideMethodVisitor to pre-decompile stage)
- add all public methods to jadx class set to allow search of override base method
- add don't rename flag if override hierarchy have unresolved methods
2020-12-19 18:07:51 +03:00
Jan S 549f346d5e fix: prevent NullPointerException and ConcurrentModificationException when renaming something (PR #1055) 2020-12-18 17:12:32 +03:00
Jonas Konrad 80a879bddf fix: properly transform array creation with constant field length to filled-array (PR #1054) 2020-12-16 19:12:11 +03:00
Skylot 13c17a000a fix: force code inline after new array creation resugar (#1048) 2020-12-12 20:12:42 +00:00
Jonas Konrad 96dea75bc8 fix: preserve original method details in inlined invocation (PR #1049) 2020-12-12 22:08:50 +03:00
Skylot 035fce6191 fix: improve error reporting for instruction decode failure (#1046) 2020-12-11 22:06:08 +03:00
Jonas Konrad 2f5dd171d0 fix: do not remove method start block when it is referenced from dead code (PR #1044) 2020-12-09 23:19:21 +03:00
Skylot e7598d4340 fix: don't add region on exit block (#1040) 2020-12-03 21:45:21 +03:00
Skylot 76d0a39a0f fix: handle empty loop body (#1040) 2020-12-03 21:45:21 +03:00
Jonas Konrad 3f25f072c6 fix: properly traverse methods with synchronize blocks that have no clear exit (PR #1041) 2020-12-03 17:15:49 +03:00
Jonas Konrad 5c75f249c7 fix: do not count nop instructions when considering methods for fallback mode printing (#1038) (PR #1039) 2020-12-01 13:39:48 +03:00
Skylot 02bfe63245 fix: support AAR files as input (#1034) 2020-11-30 17:00:25 +03:00
Jonas Konrad faa205a486 fix: process exception handler when handler block is start of a new try block (PR #1036) 2020-11-30 15:46:18 +03:00
Jan S 3a6d645ea3 fix(res): do not rename resources names for building res-map.txt (PR #1035)
fix: do not rename resources names for building res-map.txt
allow loading of resources.arsc from android.jar files
res-map.txt bases on resources.arsc from API 3, 4, 7-30 (taken from https://github.com/Sable/android-platforms)
2020-11-30 14:00:58 +03:00
Skylot e65468b97a fix(gui): proper reference highlighter remove (#1031) 2020-11-24 12:24:15 +00:00
Skylot edbe6015f6 fix(res): unescape new line symbol in string resources (#1030) 2020-11-23 16:35:05 +00:00
bagipro f642e11a7a fix(res): fixes ns value (PR #1029)
* Fixes ns value
* fix formatting

Co-authored-by: bagipro <bugi@MacBook-Pro.local>
Co-authored-by: Skylot <skylot@gmail.com>
2020-11-23 00:54:03 +03:00
Skylot fdc87fe296 fix: update class set data to Android API 30 2020-11-22 18:43:49 +00:00
Skylot 7396c7595f fix: resolve type variables in invoke from arg types 2020-11-22 18:41:38 +00:00
Skylot d39849ad00 fix(res): update android resources to API 30 2020-11-21 23:24:18 +03:00
Skylot d65ee902f7 fix: load android res map in getter 2020-11-21 20:24:11 +00:00
Skylot eada4b0fc3 fix: don't add 'default' for static methods in interfaces 2020-11-21 23:04:55 +03:00
bagipro 6f9619126a fix(res): rename invalid res keys (PR #1027)
* Renames invalid res keys
* perf: store compiled resource name pattern for better performance

Co-authored-by: bagipro <bugi@MacBook-Pro.local>
Co-authored-by: Skylot <skylot@gmail.com>
2020-11-21 23:02:47 +03:00
Skylot 4bc6007a4d fix: error loading resource map file from bundled jar (#1020) 2020-11-19 16:09:12 +03:00
Skylot d3f5154c19 fix: use text file for store android resource mapping (#1020)
Signed-off-by: Skylot <skylot@gmail.com>
2020-11-19 10:23:53 +00:00
Skylot 71aa29cc71 docs: remove pyjadx link (#1024) 2020-11-18 19:30:18 +00:00
Skylot 98d8015015 refactor: split field init attribute 2020-11-16 21:09:38 +00:00
Skylot 42a44f210d feat: concat constant strings (#1014) 2020-11-16 19:29:08 +03:00
Skylot 29ff86b74f fix: don't unload attributes added to class at initial load (#1010) 2020-11-15 17:54:42 +00:00
1016 changed files with 73941 additions and 11523 deletions
-14
View File
@@ -1,14 +0,0 @@
coverage:
precision: 2
round: down
range: "50...100"
status:
project:
default: on
patch:
default: on
changes:
default: off
comment: false
@@ -8,7 +8,6 @@ assignees: ''
--- ---
**Checks before report** **Checks before report**
- check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki - check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
- search existing issues by exception message - search existing issues by exception message
+52
View File
@@ -0,0 +1,52 @@
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@v1
with:
queries: +security-extended
languages: ${{ matrix.language }}
# Don't build tests in jadx-core also skip tests execution and checkstyle tasks
- run: |
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+3
View File
@@ -33,3 +33,6 @@ jadx-output/
*.log *.log
*.cfg *.cfg
*.orig *.orig
quark.json
cliff.toml
+3 -3
View File
@@ -11,14 +11,14 @@ stages:
java-8: java-8:
stage: test stage: test
image: openjdk:8 image: openjdk:8
script: ./gradlew clean build dist script: ./gradlew clean build dist copyExe --warning-mode=all
java-11: java-11:
stage: test stage: test
image: openjdk:11 image: openjdk:11
script: ./gradlew clean build dist script: ./gradlew clean build dist copyExe --warning-mode=all
java-latest: java-latest:
stage: test stage: test
image: openjdk:latest image: openjdk:latest
script: java -version && ./gradlew clean build dist script: java -version && ./gradlew clean build dist --warning-mode=all
-37
View File
@@ -1,37 +0,0 @@
branches:
- release
verifyConditions:
- '@semantic-release/github'
prepare:
- path: '@semantic-release/exec'
cmd: "JADX_VERSION=${nextRelease.version} ./gradlew clean dist"
preset: "angular"
generateNotes:
- path: '@semantic-release/release-notes-generator'
writerOpts:
groupBy: "type"
commitGroupsSort:
- "feat"
- "perf"
- "fix"
commitsSort: "header"
publish:
- path: '@semantic-release/exec'
cmd: "JADX_VERSION=${nextRelease.version} BINTRAY_PACKAGE=releases bash scripts/bintray-upload.sh"
- path: '@semantic-release/github'
assets:
- path: 'build/*.zip'
- path: 'build/*.exe'
success:
- path: '@semantic-release/github'
successComment: false
fail:
- path: '@semantic-release/github'
failComment: false
-46
View File
@@ -1,46 +0,0 @@
language: java
os: linux
dist: trusty
# don't build on tag push
if: tag IS blank
git:
depth: false
before_install:
- chmod +x gradlew
# override install to skip 'gradle assemble'
install: true
env:
global:
- TERM=dumb
- JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
- JADX_VERSION="${JADX_LAST_TAG:1}-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
jdk:
- openjdk8
- openjdk11
script: ./gradlew clean build
jobs:
include:
- stage: checks
jdk: openjdk11
if: branch = master AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-checks.sh
- stage: deploy-unstable
jdk: openjdk8
if: branch = master AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-master.sh
- stage: deploy-release
language: node_js
jdk: openjdk8
node_js: 11
if: branch = release AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-release.sh
-1
View File
@@ -5,7 +5,6 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
## Open Issue ## Open Issue
1. Before proceed please do: 1. Before proceed please do:
- check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki - check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
- search existing issues by exception message - search existing issues by exception message
+53 -25
View File
@@ -2,19 +2,20 @@
## JADX ## JADX
[![Build Status](https://travis-ci.com/skylot/jadx.svg?branch=master)](https://travis-ci.com/skylot/jadx) [![Build status](https://github.com/skylot/jadx/workflows/Build/badge.svg)](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
[![Code Coverage](https://codecov.io/gh/skylot/jadx/branch/master/graph/badge.svg)](https://codecov.io/gh/skylot/jadx)
[![Alerts from lgtm.com](https://img.shields.io/lgtm/alerts/g/skylot/jadx.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/skylot/jadx/alerts/) [![Alerts from lgtm.com](https://img.shields.io/lgtm/alerts/g/skylot/jadx.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/skylot/jadx/alerts/)
[![SonarQube Bugs](https://sonarcloud.io/api/project_badges/measure?project=jadx&metric=bugs)](https://sonarcloud.io/dashboard?id=jadx)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
[![Maven Central](https://img.shields.io/maven-central/v/io.github.skylot/jadx-core)](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
**jadx** - Dex to Java decompiler **jadx** - Dex to Java decompiler
Command line and GUI tools for producing Java source code from Android Dex and Apk files Command line and GUI tools for producing Java source code from Android Dex and Apk files
:exclamation::exclamation::exclamation: Please note that in most cases **jadx** can't decompile all 100% of the code, so errors will occur. Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds
**Main features:** **Main features:**
- decompile Dalvik bytecode to java classes from APK, dex, aar and zip files - decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
- decode `AndroidManifest.xml` and other resources from `resources.arsc` - decode `AndroidManifest.xml` and other resources from `resources.arsc`
- deobfuscator included - deobfuscator included
@@ -23,25 +24,25 @@ Command line and GUI tools for producing Java source code from Android Dex and A
- jump to declaration - jump to declaration
- find usage - find usage
- full text search - full text search
- smali debugger, check [wiki page](https://github.com/skylot/jadx/wiki/Smali-debugger) for setup and usage
Jadx-gui key bindings can be found [here](https://github.com/skylot/jadx/wiki/JADX-GUI-Key-bindings)
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview) See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
![jadx-gui screenshot](https://i.imgur.com/h917IBZ.png)
### Download ### Download
- latest [unstable build: ![Download](https://api.bintray.com/packages/skylot/jadx/unstable/images/download.svg) ](https://bintray.com/skylot/jadx/unstable/_latestVersion#files)
- release from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest) - release from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
- release from [bintray: ![Download](https://api.bintray.com/packages/skylot/jadx/releases/images/download.svg) ](https://bintray.com/skylot/jadx/releases/_latestVersion#files) - latest [unstable build](https://nightly.link/skylot/jadx/workflows/build/master)
After download unpack zip file go to `bin` directory and run: After download unpack zip file go to `bin` directory and run:
- `jadx` - command line version - `jadx` - command line version
- `jadx-gui` - UI version - `jadx-gui` - UI version
On Windows run `.bat` files with double-click\ On Windows run `.bat` files with double-click\
**Note:** ensure you have installed Java 8 or later 64-bit version. **Note:** ensure you have installed Java 11 or later 64-bit version.
For windows you can download it from [adoptopenjdk.net](https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot#x64_win) (select "Install JRE"). For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
### Install ### Install
1. Arch linux 1. Arch linux
@@ -53,6 +54,9 @@ For windows you can download it from [adoptopenjdk.net](https://adoptopenjdk.net
brew install jadx brew install jadx
``` ```
### Use jadx as a library
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
### Build from source ### Build from source
JDK 8 or higher must be installed: JDK 8 or higher must be installed:
``` ```
@@ -68,44 +72,71 @@ and also packed to `build/jadx-<version>.zip`
### Usage ### Usage
``` ```
jadx[-gui] [options] <input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc) jadx[-gui] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab)
options: options:
-d, --output-dir - output directory -d, --output-dir - output directory
-ds, --output-dir-src - output directory for sources -ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources -dr, --output-dir-res - output directory for resources
-r, --no-res - do not decode resources -r, --no-res - do not decode resources
-s, --no-src - do not decompile source code -s, --no-src - do not decompile source code
--single-class - decompile a single class --single-class - decompile a single class, full name, raw or alias
--single-class-output - file or dir for write if decompile a single class
--output-format - can be 'java' or 'json', default: java --output-format - can be 'java' or 'json', default: java
-e, --export-gradle - save as android gradle project -e, --export-gradle - save as android gradle project
-j, --threads-count - processing threads count, default: 4 -j, --threads-count - processing threads count, default: 4
--show-bad-code - show inconsistent code (incorrectly decompiled) --show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name --no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info --no-debug-info - disable debug info
--add-debug-lines - add comments with debug line numbers if available
--no-inline-anonymous - disable anonymous classes inline --no-inline-anonymous - disable anonymous classes inline
--no-inline-methods - disable methods inline
--no-replace-consts - don't replace constant value with matching constant field --no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u) --escape-unicode - escape non latin characters in strings (with \u)
--respect-bytecode-access-modifiers - don't change original access modifiers --respect-bytecode-access-modifiers - don't change original access modifiers
--deobf - activate deobfuscation --deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter, default: 3 --deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64 --deobf-max - max length of name, renamed if longer, default: 64
--deobf-rewrite-cfg - force to save deobfuscation map --deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension
--deobf-cfg-file-mode - set mode for handle deobfuscation map file:
'read' - read if found, don't save (default)
'read-or-save' - read if found, save otherwise (don't overwrite)
'overwrite' - don't read, always save
'ignore' - don't read and don't save
--deobf-rewrite-cfg - set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)
--deobf-use-sourcename - use source file name as class name alias --deobf-use-sourcename - use source file name as class name alias
--rename-flags - what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all' (default) --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,
'printable' - remove non-printable chars from identifiers,
or single 'none' - to disable all renames
or single 'all' - to enable all (default)
--fs-case-sensitive - treat filesystem as case sensitive, false by default --fs-case-sensitive - treat filesystem as case sensitive, false by default
--cfg - save methods control flow graph to dot file --cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions) --raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc) -f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
--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) -v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET) -q, --quiet - turn off output (set --log-level to QUIET)
--log-level - set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG, default: PROGRESS
--version - print jadx version --version - print jadx version
-h, --help - print this help -h, --help - print this help
Example:
jadx -d out classes.dex Plugin options (-P<name>=<value>):
jadx --rename-flags "none" classes.dex 1) dex-input (Load .dex and .apk files)
jadx --rename-flags "valid,printable" classes.dex -Pdex-input.verify-checksum - Verify dex file checksum before load, values: [yes, no], default: yes
jadx --log-level error app.apk 2) java-convert (Convert .jar and .class files to dex)
-Pjava-convert.mode - Convert mode, values: [dx, d8, both], default: both
-Pjava-convert.d8-desugar - Use desugar in d8, values: [yes, no], default: no
Examples:
jadx -d out classes.dex
jadx --rename-flags "none" classes.dex
jadx --rename-flags "valid, printable" classes.dex
jadx --log-level ERROR app.apk
jadx -Pdex-input.verify-checksum=no app.apk
``` ```
These options also worked on jadx-gui running from command line and override options from preferences dialog These options also worked on jadx-gui running from command line and override options from preferences dialog
@@ -118,8 +149,5 @@ To support this project you can:
- Submit decompilation issues, please read before proceed: [Open issue](CONTRIBUTING.md#Open-Issue) - Submit decompilation issues, please read before proceed: [Open issue](CONTRIBUTING.md#Open-Issue)
- Open pull request, please follow these rules: [Pull Request Process](CONTRIBUTING.md#Pull-Request-Process) - Open pull request, please follow these rules: [Pull Request Process](CONTRIBUTING.md#Pull-Request-Process)
### Related projects:
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
--------------------------------------- ---------------------------------------
*Licensed under the Apache 2.0 License* *Licensed under the Apache 2.0 License*
+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.
+59 -68
View File
@@ -1,7 +1,6 @@
plugins { plugins {
id 'org.sonarqube' version '3.0' id 'com.github.ben-manes.versions' version '0.42.0'
id 'com.github.ben-manes.versions' version '0.33.0' id 'com.diffplug.spotless' version '6.3.0'
id "com.diffplug.spotless" version "5.5.1"
} }
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev" ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -10,7 +9,6 @@ println("jadx version: ${jadxVersion}")
allprojects { allprojects {
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'jacoco'
apply plugin: 'checkstyle' apply plugin: 'checkstyle'
version = jadxVersion version = jadxVersion
@@ -18,12 +16,6 @@ allprojects {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
tasks.withType(JavaCompile) {
if (!"$it".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
}
compileJava { compileJava {
options.encoding = "UTF-8" options.encoding = "UTF-8"
} }
@@ -35,19 +27,18 @@ allprojects {
} }
dependencies { dependencies {
implementation 'org.slf4j:slf4j-api:1.7.30' implementation 'org.slf4j:slf4j-api:1.7.36'
compileOnly 'org.jetbrains:annotations:20.1.0' compileOnly 'org.jetbrains:annotations:23.0.0'
testImplementation 'ch.qos.logback:logback-classic:1.2.3' testImplementation 'ch.qos.logback:logback-classic:1.2.11'
testImplementation 'org.hamcrest:hamcrest-library:2.2' testImplementation 'org.hamcrest:hamcrest-library:2.2'
testImplementation 'org.mockito:mockito-core:3.5.10' testImplementation 'org.mockito:mockito-core:4.4.0'
testImplementation 'org.assertj:assertj-core:3.17.2' testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1' testCompileOnly 'org.jetbrains:annotations:23.0.0'
testCompileOnly 'org.jetbrains:annotations:20.1.0'
} }
test { test {
@@ -57,31 +48,8 @@ allprojects {
repositories { repositories {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
jcenter()
google() google()
} }
jacoco {
toolVersion = "0.8.6"
}
jacocoTestReport {
reports {
xml.enabled = true
html.enabled = true
}
}
checkstyleMain {
// exclude all sources in samples module
exclude '**/samples/**'
}
}
sonarqube {
properties {
property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*'
property 'sonar.coverage.exclusions', '**/jadx/gui/**/*'
}
} }
spotless { spotless {
@@ -95,7 +63,13 @@ spotless {
importOrderFile 'config/code-formatter/eclipse.importorder' importOrderFile 'config/code-formatter/eclipse.importorder'
eclipse().configFile 'config/code-formatter/eclipse.xml' eclipse().configFile 'config/code-formatter/eclipse.xml'
removeUnusedImports() if (JavaVersion.current() < JavaVersion.VERSION_16) {
removeUnusedImports()
} else {
// google-format on Java 16+ issue: https://github.com/diffplug/spotless/issues/834
println('Warning! Unused imports remove is disabled for Java 16+'
+ ' (use workaround from https://github.com/diffplug/spotless/tree/main/plugin-gradle#google-java-format)')
}
lineEndings(com.diffplug.spotless.LineEnding.UNIX) lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8") encoding("UTF-8")
@@ -128,36 +102,55 @@ dependencyUpdates {
} }
} }
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) { task copyArtifacts(type: Copy) {
destinationDir file("$buildDir/jadx") from tasks.getByPath(":jadx-cli:installDist")
['jadx-cli', 'jadx-gui'].each { from tasks.getByPath(":jadx-gui:installDist")
from tasks.getByPath(":${it}:installDist").destinationDir into layout.buildDirectory.dir("jadx")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
task pack(type: Zip) {
from copyArtifacts
archiveFileName = "jadx-${jadxVersion}.zip"
destinationDirectory = layout.buildDirectory
}
task copyExe(type: Copy) {
group 'jadx'
description = 'Copy exe to build dir'
mustRunAfter pack // not needed, but gradle throws warning because of same output dir
from tasks.getByPath('jadx-gui:createExe')
include '*.exe'
into layout.buildDirectory
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
task distWinBundle(type: Copy, dependsOn: 'jadx-gui:distWinWithJre') {
group 'jadx'
description = 'Copy bundle to build dir'
destinationDir buildDir
from(tasks.getByPath('jadx-gui:distWinWithJre').outputs) {
include '*.zip'
} }
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy = DuplicatesStrategy.EXCLUDE
} }
task pack(type: Zip, dependsOn: copyArtifacts) { task dist {
destinationDirectory = buildDir
archiveFileName = "jadx-${jadxVersion}.zip"
from copyArtifacts.destinationDir
}
task copyExe(type: Copy, dependsOn: 'jadx-gui:createExe') {
group 'jadx'
description = 'Copy exe to build dir'
destinationDir buildDir
from tasks.getByPath('jadx-gui:createExe').outputs
include '*.exe'
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
task dist(dependsOn: [pack, copyExe]) {
group 'jadx' group 'jadx'
description = 'Build jadx distribution zip' description = 'Build jadx distribution zip'
}
task samples(dependsOn: 'jadx-samples:samples') { dependsOn(pack)
group 'jadx'
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) { task cleanBuildDir(type: Delete) {
@@ -165,6 +158,4 @@ task cleanBuildDir(type: Delete) {
delete buildDir delete buildDir
} }
test.dependsOn(samples)
clean.dependsOn(cleanBuildDir) clean.dependsOn(cleanBuildDir)
+3
View File
@@ -0,0 +1,3 @@
plugins {
id 'groovy-gradle-plugin'
}
@@ -0,0 +1,78 @@
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 {
sign publishing.publications.mavenJava
}
javadoc {
if (JavaVersion.current().isJava9Compatible()) {
options.addBooleanOption('html5', true)
}
// disable 'missing' warnings
options.addStringOption('Xdoclint:all,-missing', '-quiet')
}
+11
View File
@@ -119,6 +119,17 @@
<module name="OuterTypeNumber"/> <module name="OuterTypeNumber"/>
<module name="SuppressWarningsHolder"/> <module name="SuppressWarningsHolder"/>
<module name="IllegalType"/>
<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>
<module name="NewlineAtEndOfFile"/> <module name="NewlineAtEndOfFile"/>
+1
View File
@@ -0,0 +1 @@
SmaliTokenMaker.java
+5
View File
@@ -0,0 +1,5 @@
Refer to the following instructions to modify and use to generate SmaliTokenMarker
```shell
jflex SmaliTokenMaker.flex --skel skeleton.default
```
+681
View File
@@ -0,0 +1,681 @@
/*
* Generated on 11/22/21, 8:58 PM
*/
package jadx.gui.ui.codeearea;
import java.io.*;
import javax.swing.text.Segment;
import org.fife.ui.rsyntaxtextarea.*;
/**
* 用于Smali代码高亮
* MartinKay@qq.com
*/
%%
%public
%class SmaliTokenMaker
%extends AbstractJFlexCTokenMaker
%unicode
/* Case sensitive */
%type org.fife.ui.rsyntaxtextarea.Token
%{
/**
* Constructor. This must be here because JFlex does not generate a
* no-parameter constructor.
*/
public SmaliTokenMaker() {
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
* @see #addToken(int, int, int)
*/
private void addHyperlinkToken(int start, int end, int tokenType) {
int so = start + offsetShift;
addToken(zzBuffer, start,end, tokenType, so, true);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
*/
private void addToken(int tokenType) {
addToken(zzStartRead, zzMarkedPos-1, tokenType);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
* @see #addHyperlinkToken(int, int, int)
*/
private void addToken(int start, int end, int tokenType) {
int so = start + offsetShift;
addToken(zzBuffer, start,end, tokenType, so, false);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param array The character array.
* @param start The starting offset in the array.
* @param end The ending offset in the array.
* @param tokenType The token's type.
* @param startOffset The offset in the document at which this token
* occurs.
* @param hyperlink Whether this token is a hyperlink.
*/
public void addToken(char[] array, int start, int end, int tokenType,
int startOffset, boolean hyperlink) {
super.addToken(array, start,end, tokenType, startOffset, hyperlink);
zzStartRead = zzMarkedPos;
}
/**
* {@inheritDoc}
*/
public String[] getLineCommentStartAndEnd(int languageIndex) {
return new String[] { "#", null };
}
/**
* Returns the first token in the linked list of tokens generated
* from <code>text</code>. This method must be implemented by
* subclasses so they can correctly implement syntax highlighting.
*
* @param text The text from which to get tokens.
* @param initialTokenType The token type we should start with.
* @param startOffset The offset into the document at which
* <code>text</code> starts.
* @return The first <code>Token</code> in a linked list representing
* the syntax highlighted text.
*/
public Token getTokenList(Segment text, int initialTokenType, int startOffset) {
resetTokenList();
this.offsetShift = -text.offset + startOffset;
// Start off in the proper state.
int state = Token.NULL;
switch (initialTokenType) {
/* No multi-line comments */
/* No documentation comments */
default:
state = Token.NULL;
}
s = text;
try {
yyreset(zzReader);
yybegin(state);
return yylex();
} catch (IOException ioe) {
ioe.printStackTrace();
return new TokenImpl();
}
}
/**
* Refills the input buffer.
*
* @return <code>true</code> if EOF was reached, otherwise
* <code>false</code>.
*/
private boolean zzRefill() {
return zzCurrentPos>=s.offset+s.count;
}
/**
* Resets the scanner to read from a new input stream.
* Does not close the old reader.
*
* All internal variables are reset, the old input stream
* <b>cannot</b> be reused (internal buffer is discarded and lost).
* Lexical state is set to <tt>YY_INITIAL</tt>.
*
* @param reader the new input stream
*/
public final void yyreset(Reader reader) {
// 's' has been updated.
zzBuffer = s.array;
/*
* We replaced the line below with the two below it because zzRefill
* no longer "refills" the buffer (since the way we do it, it's always
* "full" the first time through, since it points to the segment's
* array). So, we assign zzEndRead here.
*/
//zzStartRead = zzEndRead = s.offset;
zzStartRead = s.offset;
zzEndRead = zzStartRead + s.count - 1;
zzCurrentPos = zzMarkedPos = zzPushbackPos = s.offset;
zzLexicalState = YYINITIAL;
zzReader = reader;
zzAtBOL = true;
zzAtEOF = false;
}
%}
Letter = [A-Za-z]
LetterOrUnderscore = ({Letter}|"_")
NonzeroDigit = [1-9]
Digit = ("0"|{NonzeroDigit})
HexDigit = ({Digit}|[A-Fa-f])
OctalDigit = ([0-7])
AnyCharacterButApostropheOrBackSlash = ([^\\'])
AnyCharacterButDoubleQuoteOrBackSlash = ([^\\\"\n])
EscapedSourceCharacter = ("u"{HexDigit}{HexDigit}{HexDigit}{HexDigit})
Escape = ("\\"(([btnfr\"'\\])|([0123]{OctalDigit}?{OctalDigit}?)|({OctalDigit}{OctalDigit}?)|{EscapedSourceCharacter}))
NonSeparator = ([^\t\f\r\n\ \(\)\{\}\[\]\;\,\.\=\>\<\!\~\?\:\+\-\*\/\&\|\^\%\"\']|"#"|"\\")
IdentifierStart = ({LetterOrUnderscore}|"$")
IdentifierPart = ({IdentifierStart}|{Digit}|("\\"{EscapedSourceCharacter}))
LineTerminator = (\n)
WhiteSpace = ([ \t\f]+)
CharLiteral = ([\']({AnyCharacterButApostropheOrBackSlash}|{Escape})[\'])
UnclosedCharLiteral = ([\'][^\'\n]*)
ErrorCharLiteral = ({UnclosedCharLiteral}[\'])
StringLiteral = ([\"]({AnyCharacterButDoubleQuoteOrBackSlash}|{Escape})*[\"])
UnclosedStringLiteral = ([\"]([\\].|[^\\\"])*[^\"]?)
ErrorStringLiteral = ({UnclosedStringLiteral}[\"])
/* No multi-line comments */
/* No documentation comments */
LineCommentBegin = "#"
IntegerLiteral = ({Digit}+)
HexLiteral = (0x{HexDigit}+)
FloatLiteral = (({Digit}+)("."{Digit}+)?(e[+-]?{Digit}+)? | ({Digit}+)?("."{Digit}+)(e[+-]?{Digit}+)?)
ErrorNumberFormat = (({IntegerLiteral}|{HexLiteral}|{FloatLiteral}){NonSeparator}+)
BooleanLiteral = ("true"|"false")
Separator = ([\(\)\{\}\[\]])
Separator2 = ([\;,.])
Identifier = ({IdentifierStart}{IdentifierPart}*)
URLGenDelim = ([:\/\?#\[\]@])
URLSubDelim = ([\!\$&'\(\)\*\+,;=])
URLUnreserved = ({LetterOrUnderscore}|{Digit}|[\-\.\~])
URLCharacter = ({URLGenDelim}|{URLSubDelim}|{URLUnreserved}|[%])
URLCharacters = ({URLCharacter}*)
URLEndCharacter = ([\/\$]|{Letter}|{Digit})
URL = (((https?|f(tp|ile))"://"|"www.")({URLCharacters}{URLEndCharacter})?)
/* Custom Regex */
/* fully-qualified name Rules */
SimpleName = ([a-zA-Z0-9_$]*)
QUALIFIED_TYPE_NAME = ("L"({SimpleName}{SLASH})*{SimpleName}*";")
/* Types */
VOID_TYPE = ("V")
BOOLEAN_TYPE = ("Z")
BYTE_TYPE = ("B")
SHORT_TYPE = ("S")
CHAR_TYPE = ("C")
INT_TYPE = ("I")
LONG_TYPE = ("J")
FLOAT_TYPE = ("F")
DOUBLE_TYPE = ("D")
/* Multi Args Types Highlight */
MULTI_ARGS_TYPES = (({BOOLEAN_TYPE}|{BYTE_TYPE}|{SHORT_TYPE}|{CHAR_TYPE}|{INT_TYPE}|{LONG_TYPE}|{FLOAT_TYPE}|{DOUBLE_TYPE})+);
/* Types fully-qualified name */
COMPOUND_METHOD_ARG_LITERAL = (({BOOLEAN_TYPE}|{BYTE_TYPE}|{SHORT_TYPE}|{CHAR_TYPE}|{INT_TYPE}|{LONG_TYPE}|{FLOAT_TYPE}|{DOUBLE_TYPE})+{QUALIFIED_TYPE_NAME})
LBRACK = ("[")
RBRACK = ("]")
LPAREN = ("(")
RPAREN = (")")
LBRACE = ("{")
RBRACE = ("}")
COLON = (":")
ASSIGN = ("=")
DOT = (".")
SUB = ("-")
COMMA = (",")
SLASH = ("/")
LT = ("<")
GT = (">")
ARROW = ("->")
SEMI = (";")
ARROW_FUNCTION = ({ARROW}[a-zA-Z_$<>]*)
CustomSeparator = ({Separator}|{ARROW_FUNCTION})
/* Register */
VREGISTER = ("v"("0"|[1-9])*)
PREGISTER = ("p"("0"|[1-9])*)
/* Flags */
FLAG_PSWITCH = (":pswitch_"{SimpleName})
FLAG_PSWITCH_DATA = (":pswitch_data_"{SimpleName})
FLAG_GOTO = (":goto_"{SimpleName})
FLAG_COND = (":cond_"{SimpleName})
FLAG_TRY_START = (":try_start_"{SimpleName})
FLAG_TRY_END = (":try_end_"{SimpleName})
FLAG_CATCH = (":catch_"{SimpleName})
FLAG_CATCHALL = (":catchall_"{SimpleName})
FLAG_ARRAY = (":array_"{SimpleName})
/* No string state */
/* No char state */
/* No MLC state */
/* No documentation comment state */
%state EOL_COMMENT
%%
<YYINITIAL> {
/* Keywords Instructions Highlight */
"nop" |
"move" |
"move/from16" |
"move/16" |
"move-wide" |
"move-wide/from16" |
"move-wide/16" |
"move-object" |
"move-object/from16" |
"move-object/16" |
"move-result" |
"move-result-wide" |
"move-result-object" |
"move-exception" |
"return-void" |
"return" |
"return-wide" |
"return-object" |
"const/4" |
"const/16" |
"const" |
"const/high16" |
"const-wide/16" |
"const-wide/32" |
"const-wide" |
"const-wide/high16" |
"const-string" |
"const-string/jumbo" |
"const-class" |
"monitor-enter" |
"monitor-exit" |
"check-cast" |
"instance-of" |
"array-length" |
"new-instance" |
"new-array" |
"filled-new-array" |
"filled-new-array/range" |
"fill-array-data" |
"throw" |
"goto" |
"goto/16" |
"goto/32" |
"cmpl-float" |
"cmpg-float" |
"cmpl-double" |
"cmpg-double" |
"cmp-long" |
"if-eq" |
"if-ne" |
"if-lt" |
"if-ge" |
"if-gt" |
"if-le" |
"if-eqz" |
"if-nez" |
"if-ltz" |
"if-gez" |
"if-gtz" |
"if-lez" |
"aget" |
"aget-wide" |
"aget-object" |
"aget-boolean" |
"aget-byte" |
"aget-char" |
"aget-short" |
"aput" |
"aput-wide" |
"aput-object" |
"aput-boolean" |
"aput-byte" |
"aput-char" |
"aput-short" |
"iget" |
"iget-wide" |
"iget-object" |
"iget-boolean" |
"iget-byte" |
"iget-char" |
"iget-short" |
"iput" |
"iput-wide" |
"iput-object" |
"iput-boolean" |
"iput-byte" |
"iput-char" |
"iput-short" |
"sget" |
"sget-wide" |
"sget-object" |
"sget-boolean" |
"sget-byte" |
"sget-char" |
"sget-short" |
"sput" |
"sput-wide" |
"sput-object" |
"sput-boolean" |
"sput-byte" |
"sput-char" |
"sput-short" |
"invoke-virtual" |
"invoke-super" |
"invoke-direct" |
"invoke-static" |
"invoke-interface" |
"invoke-virtual/range" |
"invoke-super/range" |
"invoke-direct/range" |
"invoke-static/range" |
"invoke-interface/range" |
"neg-int" |
"not-int" |
"neg-long" |
"not-long" |
"neg-float" |
"neg-double" |
"int-to-long" |
"int-to-float" |
"int-to-double" |
"long-to-int" |
"long-to-float" |
"long-to-double" |
"float-to-int" |
"float-to-long" |
"float-to-double" |
"double-to-int" |
"double-to-long" |
"double-to-float" |
"int-to-byte" |
"int-to-char" |
"int-to-short" |
"add-int" |
"sub-int" |
"mul-int" |
"div-int" |
"rem-int" |
"and-int" |
"or-int" |
"xor-int" |
"shl-int" |
"shr-int" |
"ushr-int" |
"add-long" |
"sub-long" |
"mul-long" |
"div-long" |
"rem-long" |
"and-long" |
"or-long" |
"xor-long" |
"shl-long" |
"shr-long" |
"ushr-long" |
"add-float" |
"sub-float" |
"mul-float" |
"div-float" |
"rem-float" |
"add-double" |
"sub-double" |
"mul-double" |
"div-double" |
"rem-double" |
"add-int/2addr" |
"sub-int/2addr" |
"mul-int/2addr" |
"div-int/2addr" |
"rem-int/2addr" |
"and-int/2addr" |
"or-int/2addr" |
"xor-int/2addr" |
"shl-int/2addr" |
"shr-int/2addr" |
"ushr-int/2addr" |
"add-long/2addr" |
"sub-long/2addr" |
"mul-long/2addr" |
"div-long/2addr" |
"rem-long/2addr" |
"and-long/2addr" |
"or-long/2addr" |
"xor-long/2addr" |
"shl-long/2addr" |
"shr-long/2addr" |
"ushr-long/2addr" |
"add-float/2addr" |
"sub-float/2addr" |
"mul-float/2addr" |
"div-float/2addr" |
"rem-float/2addr" |
"add-double/2addr" |
"sub-double/2addr" |
"mul-double/2addr" |
"div-double/2addr" |
"rem-double/2addr" |
"add-int/lit16" |
"rsub-int" |
"mul-int/lit16" |
"div-int/lit16" |
"rem-int/lit16" |
"and-int/lit16" |
"or-int/lit16" |
"xor-int/lit16" |
"add-int/lit8" |
"rsub-int/lit8" |
"mul-int/lit8" |
"div-int/lit8" |
"rem-int/lit8" |
"and-int/lit8" |
"or-int/lit8" |
"xor-int/lit8" |
"shl-int/lit8" |
"shr-int/lit8" |
"ushr-int/lit8" |
"invoke-polymorphic" |
"invoke-polymorphic/range" |
"invoke-custom" |
"invoke-custom/range" |
"const-method-handle" |
"const-method-type" |
"packed-switch" |
"sparse-switch" { addToken(Token.FUNCTION); }
/* Keywords Modifiers(IDENTIFIER标识符、修饰符) Highlight */
"public" |
"private" |
"protected" |
"final" |
"annotation" |
"static" |
"synthetic" |
"constructor" |
"abstract" |
"enum" |
"interface" |
"transient" |
"bridge" |
"declared-synchronized" |
"volatile" |
"strictfp" |
"varargs" |
"native" { addToken(Token.RESERVED_WORD); }
/* Keywords Directives Highlight */
".method" |
".end method" |
".implements" |
".class" |
".prologue" |
".source" |
".super" |
".field" |
".end field" |
".registers" |
".locals" |
".param" |
".line" |
".catch" |
".catchall" |
".annotation" |
".end annotation" |
".local" |
".end local" |
".restart local" |
".packed-switch" |
".end packed-switch" |
".array-data" |
".end array-data" |
".sparse-switch" |
".end sparse-switch" |
".end param" { addToken(Token.RESERVED_WORD_2); }
/* VARIABLE Register Highlight */
{VREGISTER} |
{PREGISTER} { addToken(Token.VARIABLE); }
/* Data types Highlight */
{QUALIFIED_TYPE_NAME} |
{COMPOUND_METHOD_ARG_LITERAL} |
{MULTI_ARGS_TYPES} |
{QUALIFIED_TYPE_NAME} |
{VOID_TYPE} |
{BOOLEAN_TYPE} |
{BYTE_TYPE} |
{SHORT_TYPE} |
{CHAR_TYPE} |
{INT_TYPE} |
{LONG_TYPE} |
{FLOAT_TYPE} |
{DOUBLE_TYPE} { addToken(Token.DATA_TYPE); }
/* FLAGS */
{FLAG_PSWITCH} |
{FLAG_PSWITCH_DATA} |
{FLAG_GOTO} |
{FLAG_COND} |
{FLAG_TRY_START} |
{FLAG_TRY_END} |
{FLAG_CATCHALL} |
{FLAG_ARRAY} |
{FLAG_CATCH} { addToken(Token.MARKUP_TAG_NAME); }
/* Functions */
/* No functions */
{BooleanLiteral} { addToken(Token.LITERAL_BOOLEAN); }
{LineTerminator} { addNullToken(); return firstToken; }
{Identifier} { addToken(Token.IDENTIFIER); }
{WhiteSpace} { addToken(Token.WHITESPACE); }
/* String/Character literals. */
{CharLiteral} { addToken(Token.LITERAL_CHAR); }
{UnclosedCharLiteral} { addToken(Token.ERROR_CHAR); addNullToken(); return firstToken; }
{ErrorCharLiteral} { addToken(Token.ERROR_CHAR); }
{StringLiteral} { addToken(Token.LITERAL_STRING_DOUBLE_QUOTE); }
{UnclosedStringLiteral} { addToken(Token.ERROR_STRING_DOUBLE); addNullToken(); return firstToken; }
{ErrorStringLiteral} { addToken(Token.ERROR_STRING_DOUBLE); }
/* Comment literals. */
/* No multi-line comments */
/* No documentation comments */
{LineCommentBegin} { start = zzMarkedPos-1; yybegin(EOL_COMMENT); }
/* Separators. */
{CustomSeparator} { addToken(Token.SEPARATOR); }
{Separator2} { addToken(Token.IDENTIFIER); }
/* Operators. */
"!" |
";" |
"." |
"=" |
"/" |
"'" |
"(" |
")" |
"," |
"->" |
";->" |
"<" |
">" |
"@" |
"[" |
"]" |
"{" |
"}" { addToken(Token.OPERATOR); }
/* Numbers */
{IntegerLiteral} { addToken(Token.LITERAL_NUMBER_DECIMAL_INT); }
{HexLiteral} { addToken(Token.LITERAL_NUMBER_HEXADECIMAL); }
{FloatLiteral} { addToken(Token.LITERAL_NUMBER_FLOAT); }
{ErrorNumberFormat} { addToken(Token.ERROR_NUMBER_FORMAT); }
/* Ended with a line not in a string or comment. */
<<EOF>> { addNullToken(); return firstToken; }
/* Catch any other (unhandled) characters. */
. { addToken(Token.IDENTIFIER); }
}
/* No char state */
/* No string state */
/* No multi-line comment state */
/* No documentation comment state */
<EOL_COMMENT> {
[^hwf\n]+ {}
{URL} { int temp=zzStartRead; addToken(start,zzStartRead-1, Token.COMMENT_EOL); addHyperlinkToken(temp,zzMarkedPos-1, Token.COMMENT_EOL); start = zzMarkedPos; }
[hwf] {}
\n { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
<<EOF>> { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
}
+243
View File
@@ -0,0 +1,243 @@
/** This character denotes the end of file */
public static final int YYEOF = -1;
/** initial size of the lookahead buffer */
--- private static final int ZZ_BUFFERSIZE = ...;
/** lexical states */
--- lexical states, charmap
/* error codes */
private static final int ZZ_UNKNOWN_ERROR = 0;
private static final int ZZ_NO_MATCH = 1;
private static final int ZZ_PUSHBACK_2BIG = 2;
/* error messages for the codes above */
private static final String ZZ_ERROR_MSG[] = {
"Unkown internal scanner error",
"Error: could not match input",
"Error: pushback value was too large"
};
--- isFinal list
/** the input device */
private java.io.Reader zzReader;
/** the current state of the DFA */
private int zzState;
/** the current lexical state */
private int zzLexicalState = YYINITIAL;
/** this buffer contains the current text to be matched and is
the source of the yytext() string */
private char zzBuffer[];
/** the textposition at the last accepting state */
private int zzMarkedPos;
/** the textposition at the last state to be included in yytext */
private int zzPushbackPos;
/** the current text position in the buffer */
private int zzCurrentPos;
/** startRead marks the beginning of the yytext() string in the buffer */
private int zzStartRead;
/** endRead marks the last character in the buffer, that has been read
from input */
private int zzEndRead;
/** number of newlines encountered up to the start of the matched text */
private int yyline;
/** the number of characters up to the start of the matched text */
private int yychar;
/**
* the number of characters from the last newline up to the start of the
* matched text
*/
private int yycolumn;
/**
* zzAtBOL == true <=> the scanner is currently at the beginning of a line
*/
private boolean zzAtBOL = true;
/** zzAtEOF == true <=> the scanner is at the EOF */
private boolean zzAtEOF;
--- user class code
/**
* Creates a new scanner
* There is also a java.io.InputStream version of this constructor.
*
* @param in the java.io.Reader to read input from.
*/
--- constructor declaration
/**
* Closes the input stream.
*/
public final void yyclose() throws java.io.IOException {
zzAtEOF = true; /* indicate end of file */
zzEndRead = zzStartRead; /* invalidate buffer */
if (zzReader != null)
zzReader.close();
}
/**
* Enters a new lexical state
*
* @param newState the new lexical state
*/
public final void yybegin(int newState) {
zzLexicalState = newState;
}
/**
* Returns the text matched by the current regular expression.
*/
public final String yytext() {
return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
}
/**
* Returns the character at position <tt>pos</tt> from the
* matched text.
*
* It is equivalent to yytext().charAt(pos), but faster
*
* @param pos the position of the character to fetch.
* A value from 0 to yylength()-1.
*
* @return the character at position pos
*/
public final char yycharat(int pos) {
return zzBuffer[zzStartRead+pos];
}
/**
* Returns the length of the matched text region.
*/
public final int yylength() {
return zzMarkedPos-zzStartRead;
}
/**
* Reports an error that occured while scanning.
*
* In a wellformed scanner (no or only correct usage of
* yypushback(int) and a match-all fallback rule) this method
* will only be called with things that "Can't Possibly Happen".
* If this method is called, something is seriously wrong
* (e.g. a JFlex bug producing a faulty scanner etc.).
*
* Usual syntax/scanner level error handling should be done
* in error fallback rules.
*
* @param errorCode the code of the errormessage to display
*/
--- zzScanError declaration
String message;
try {
message = ZZ_ERROR_MSG[errorCode];
}
catch (ArrayIndexOutOfBoundsException e) {
message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
}
--- throws clause
}
/**
* Pushes the specified amount of characters back into the input stream.
*
* They will be read again by then next call of the scanning method
*
* @param number the number of characters to be read again.
* This number must not be greater than yylength()!
*/
--- yypushback decl (contains zzScanError exception)
if ( number > yylength() )
zzScanError(ZZ_PUSHBACK_2BIG);
zzMarkedPos -= number;
}
--- zzDoEOF
/**
* Resumes scanning until the next regular expression is matched,
* the end of input is encountered or an I/O-Error occurs.
*
* @return the next token
* @exception java.io.IOException if any I/O-Error occurs
*/
--- yylex declaration
int zzInput;
int zzAction;
// cached fields:
int zzCurrentPosL;
int zzMarkedPosL;
int zzEndReadL = zzEndRead;
char [] zzBufferL = zzBuffer;
char [] zzCMapL = ZZ_CMAP;
--- local declarations
while (true) {
zzMarkedPosL = zzMarkedPos;
--- start admin (line, char, col count)
zzAction = -1;
zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
--- start admin (lexstate etc)
zzForAction: {
while (true) {
--- next input, line, col, char count, next transition, isFinal action
zzAction = zzState;
zzMarkedPosL = zzCurrentPosL;
--- line count update
}
}
}
// store back cached position
zzMarkedPos = zzMarkedPosL;
--- char count update
--- actions
default:
if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
zzAtEOF = true;
--- eofvalue
}
else {
--- no match
}
}
}
}
--- main
}
+1 -1
View File
@@ -1 +1 @@
org.gradle.daemon=false org.gradle.warning.mode=all
Binary file not shown.
+2 -1
View File
@@ -1,5 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip distributionSha256Sum=e5444a57cda4a95f90b0c9446a9e1b47d3d7f69057765bfb54bd4f482542d548
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
Vendored
+159 -110
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env sh #!/bin/sh
# #
# Copyright 2015 the original author or authors. # Copyright © 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -17,67 +17,101 @@
# #
############################################################################## ##############################################################################
## #
## Gradle start up script for UN*X # Gradle start up script for POSIX generated by Gradle.
## #
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
@@ -106,80 +140,95 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD=$( ulimit -H -n ) ||
MAX_FD="$MAX_FD_LIMIT" warn "Could not query maximum file descriptor limit"
fi esac
ulimit -n $MAX_FD case $MAX_FD in #(
if [ $? -ne 0 ] ; then '' | soft) :;; #(
warn "Could not set maximum file descriptor limit: $MAX_FD" *)
fi ulimit -n "$MAX_FD" ||
else warn "Could not set maximum file descriptor limit to $MAX_FD"
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
# Escape application args # Collect all arguments for the java command, stacking in reverse order:
save () { # * args from the command line
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # * the main class name
echo " " # * -classpath
} # * -D...appname settings
APP_ARGS=`save "$@"` # * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules # For Cygwin or MSYS, switch paths to Windows format before running java
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"
+5 -4
View File
@@ -6,16 +6,17 @@ dependencies {
implementation(project(':jadx-core')) implementation(project(':jadx-core'))
runtimeOnly(project(':jadx-plugins:jadx-dex-input')) runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
runtimeOnly(project(':jadx-plugins:jadx-smali-input')) runtimeOnly(project(':jadx-plugins:jadx-java-input'))
runtimeOnly(project(':jadx-plugins:jadx-java-convert')) runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
implementation 'com.beust:jcommander:1.80' implementation 'com.beust:jcommander:1.82'
implementation 'ch.qos.logback:logback-classic:1.2.3' implementation 'ch.qos.logback:logback-classic:1.2.11'
} }
application { application {
applicationName = 'jadx' applicationName = 'jadx'
mainClassName = 'jadx.cli.JadxCLI' mainClass.set('jadx.cli.JadxCLI')
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC'] applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
} }
@@ -5,8 +5,8 @@ import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -17,6 +17,11 @@ import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameterized; import com.beust.jcommander.Parameterized;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.options.OptionDescription;
public class JCommanderWrapper<T> { public class JCommanderWrapper<T> {
private final JCommander jc; private final JCommander jc;
@@ -70,36 +75,51 @@ public class JCommanderWrapper<T> {
maxNamesLen = len; maxNamesLen = len;
} }
} }
maxNamesLen += 3;
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0); JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
for (Field f : getFields(args.getClass())) { for (Field f : getFields(args.getClass())) {
String name = f.getName(); String name = f.getName();
ParameterDescription p = paramsMap.get(name); ParameterDescription p = paramsMap.get(name);
if (p == null) { if (p == null || p.getParameter().hidden()) {
continue; continue;
} }
StringBuilder opt = new StringBuilder(); StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames()); opt.append(" ").append(p.getNames());
addSpaces(opt, maxNamesLen - opt.length() + 3); String description = p.getDescription();
opt.append("- ").append(p.getDescription()); 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 + 2);
opt.append(lines[i]);
}
} else {
opt.append("- ").append(description);
}
String defaultValue = getDefaultValue(args, f, opt); String defaultValue = getDefaultValue(args, f, opt);
if (defaultValue != null) { if (defaultValue != null && !description.contains("(default)")) {
opt.append(", default: ").append(defaultValue); opt.append(", default: ").append(defaultValue);
} }
out.println(opt); out.println(opt);
} }
out.println("Example:"); out.println(appendPluginOptions(maxNamesLen));
out.println();
out.println("Examples:");
out.println(" jadx -d out classes.dex"); out.println(" jadx -d out classes.dex");
out.println(" jadx --rename-flags \"none\" classes.dex");
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
out.println(" jadx --log-level ERROR app.apk");
out.println(" jadx -Pdex-input.verify-checksum=no app.apk");
} }
/** /**
* Get all declared fields of the specified class and all super classes * Get all declared fields of the specified class and all super classes
*
* @param clazz
* @return
*/ */
private List<Field> getFields(Class<?> clazz) { private List<Field> getFields(Class<?> clazz) {
List<Field> fieldList = new LinkedList<>(); List<Field> fieldList = new ArrayList<>();
while (clazz != null) { while (clazz != null) {
fieldList.addAll(Arrays.asList(clazz.getDeclaredFields())); fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
@@ -120,7 +140,7 @@ public class JCommanderWrapper<T> {
if (Enum.class.isAssignableFrom(fieldType)) { if (Enum.class.isAssignableFrom(fieldType)) {
Enum<?> val = (Enum<?>) f.get(args); Enum<?> val = (Enum<?>) f.get(args);
if (val != null) { if (val != null) {
return val.name(); return val.name().toLowerCase(Locale.ROOT);
} }
} }
} catch (Exception e) { } catch (Exception e) {
@@ -134,4 +154,46 @@ public class JCommanderWrapper<T> {
str.append(' '); str.append(' ');
} }
} }
private String appendPluginOptions(int maxNamesLen) {
StringBuilder sb = new StringBuilder();
JadxPluginManager pluginManager = new JadxPluginManager();
pluginManager.load();
int k = 1;
for (JadxPlugin plugin : pluginManager.getAllPlugins()) {
if (plugin instanceof JadxPluginOptions) {
if (appendPlugin(((JadxPluginOptions) plugin), sb, maxNamesLen, k)) {
k++;
}
}
}
if (sb.length() == 0) {
return "";
}
return "\nPlugin options (-P<name>=<value>):" + sb;
}
private boolean appendPlugin(JadxPluginOptions plugin, StringBuilder out, int maxNamesLen, int k) {
List<OptionDescription> descs = plugin.getOptionsDescriptions();
if (descs.isEmpty()) {
return false;
}
JadxPluginInfo pluginInfo = plugin.getPluginInfo();
out.append("\n ").append(k).append(") ");
out.append(pluginInfo.getPluginId()).append(" (").append(pluginInfo.getDescription()).append(") ");
for (OptionDescription desc : descs) {
StringBuilder opt = new StringBuilder();
opt.append(" -P").append(desc.name());
addSpaces(opt, maxNamesLen - opt.length());
opt.append("- ").append(desc.description());
if (!desc.values().isEmpty()) {
opt.append(", values: ").append(desc.values());
}
if (desc.defaultValue() != null) {
opt.append(", default: ").append(desc.defaultValue());
}
out.append("\n").append(opt);
}
return true;
}
} }
+42 -4
View File
@@ -6,6 +6,8 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.impl.NoOpCodeCache; import jadx.api.impl.NoOpCodeCache;
import jadx.api.impl.SimpleCodeWriter;
import jadx.cli.LogHelper.LogLevelEnum;
import jadx.core.utils.exceptions.JadxArgsValidateException; import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
@@ -20,7 +22,7 @@ public class JadxCLI {
LOG.error("Incorrect arguments: {}", e.getMessage()); LOG.error("Incorrect arguments: {}", e.getMessage());
result = 1; result = 1;
} catch (Exception e) { } catch (Exception e) {
LOG.error("jadx error: {}", e.getMessage(), e); LOG.error("Process error:", e);
result = 1; result = 1;
} finally { } finally {
FileUtils.deleteTempRootDir(); FileUtils.deleteTempRootDir();
@@ -31,16 +33,26 @@ public class JadxCLI {
public static int execute(String[] args) { public static int execute(String[] args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs(); JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) { if (jadxArgs.processArgs(args)) {
return processAndSave(jadxArgs.toJadxArgs()); return processAndSave(jadxArgs);
} }
return 0; return 0;
} }
private static int processAndSave(JadxArgs jadxArgs) { private static int processAndSave(JadxCLIArgs cliArgs) {
LogHelper.initLogLevel(cliArgs);
LogHelper.setLogLevelsForLoadingStage();
JadxArgs jadxArgs = cliArgs.toJadxArgs();
jadxArgs.setCodeCache(new NoOpCodeCache()); jadxArgs.setCodeCache(new NoOpCodeCache());
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) { try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load(); jadx.load();
jadx.save(); if (checkForErrors(jadx)) {
return 1;
}
LogHelper.setLogLevelsForDecompileStage();
if (!SingleClassMode.process(jadx, cliArgs)) {
save(jadx);
}
int errorsCount = jadx.getErrorsCount(); int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) { if (errorsCount != 0) {
jadx.printErrorsReport(); jadx.printErrorsReport();
@@ -51,4 +63,30 @@ public class JadxCLI {
} }
return 0; return 0;
} }
private static boolean checkForErrors(JadxDecompiler jadx) {
if (jadx.getRoot().getClasses().isEmpty()) {
LOG.error("Load failed! No classes for decompile!");
return true;
}
if (jadx.getErrorsCount() > 0) {
LOG.error("Load with errors! Check log for details");
// continue processing
return false;
}
return false;
}
private static void save(JadxDecompiler jadx) {
if (LogHelper.getLogLevel() == LogLevelEnum.QUIET) {
jadx.save();
} else {
jadx.save(500, (done, total) -> {
int progress = (int) (done * 100.0 / total);
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
});
// dumb line clear :)
System.out.print(" \r");
}
}
} }
+166 -25
View File
@@ -2,24 +2,30 @@ package jadx.cli;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.beust.jcommander.DynamicParameter;
import com.beust.jcommander.IStringConverter; import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RenameEnum; import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
public class JadxCLIArgs { public class JadxCLIArgs {
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)") @Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab)")
protected List<String> files = new ArrayList<>(1); protected List<String> files = new ArrayList<>(1);
@Parameter(names = { "-d", "--output-dir" }, description = "output directory") @Parameter(names = { "-d", "--output-dir" }, description = "output directory")
@@ -37,9 +43,12 @@ public class JadxCLIArgs {
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code") @Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
protected boolean skipSources = false; protected boolean skipSources = false;
@Parameter(names = { "--single-class" }, description = "decompile a single class") @Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
protected String singleClass = null; protected String singleClass = null;
@Parameter(names = { "--single-class-output" }, description = "file or dir for write if decompile a single class")
protected String singleClassOutput = null;
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'") @Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
protected String outputFormat = "java"; protected String outputFormat = "java";
@@ -58,9 +67,15 @@ public class JadxCLIArgs {
@Parameter(names = { "--no-debug-info" }, description = "disable debug info") @Parameter(names = { "--no-debug-info" }, description = "disable debug info")
protected boolean debugInfo = true; protected boolean debugInfo = true;
@Parameter(names = { "--add-debug-lines" }, description = "add comments with debug line numbers if available")
protected boolean addDebugLines = false;
@Parameter(names = { "--no-inline-anonymous" }, description = "disable anonymous classes inline") @Parameter(names = { "--no-inline-anonymous" }, description = "disable anonymous classes inline")
protected boolean inlineAnonymousClasses = true; protected boolean inlineAnonymousClasses = true;
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
protected boolean inlineMethods = true;
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field") @Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
protected boolean replaceConsts = true; protected boolean replaceConsts = true;
@@ -79,7 +94,24 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer") @Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer")
protected int deobfuscationMaxLength = 64; protected int deobfuscationMaxLength = 64;
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "force to save deobfuscation map") @Parameter(
names = { "--deobf-cfg-file" },
description = "deobfuscation map file, default: same dir and name as input file with '.jobf' extension"
)
protected String deobfuscationMapFile;
@Parameter(
names = { "--deobf-cfg-file-mode" },
description = "set mode for handle deobfuscation map file:"
+ "\n 'read' - read if found, don't save (default)"
+ "\n 'read-or-save' - read if found, save otherwise (don't overwrite)"
+ "\n 'overwrite' - don't read, always save"
+ "\n 'ignore' - don't read and don't save",
converter = DeobfuscationMapFileModeConverter.class
)
protected DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)")
protected boolean deobfuscationForceSave = false; protected boolean deobfuscationForceSave = false;
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias") @Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
@@ -88,13 +120,21 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names") @Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
protected boolean deobfuscationParseKotlinMetadata = false; protected boolean deobfuscationParseKotlinMetadata = false;
@Parameter(
names = { "--use-kotlin-methods-for-var-names" },
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
converter = UseKotlinMethodsForVarNamesConverter.class
)
protected UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
@Parameter( @Parameter(
names = { "--rename-flags" }, names = { "--rename-flags" },
description = "what to rename, comma-separated," description = "fix options (comma-separated list of):"
+ " 'case' for system case sensitivity," + "\n 'case' - fix case sensitivity issues (according to --fs-case-sensitive option),"
+ " 'valid' for java identifiers," + "\n 'valid' - rename java identifiers to make them valid,"
+ " 'printable' characters," + "\n 'printable' - remove non-printable chars from identifiers,"
+ " 'none' or 'all' (default)", + "\nor single 'none' - to disable all renames"
+ "\nor single 'all' - to enable all (default)",
converter = RenameConverter.class converter = RenameConverter.class
) )
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class); protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
@@ -111,25 +151,38 @@ public class JadxCLIArgs {
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)") @Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false; 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",
converter = CommentsLevelConverter.class
)
protected CommentsLevel commentsLevel = CommentsLevel.INFO;
@Parameter(
names = { "--log-level" },
description = "set log level, values: quiet, progress, error, warn, info, debug",
converter = LogHelper.LogLevelConverter.class
)
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
@Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)") @Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)")
protected boolean verbose = false; protected boolean verbose = false;
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)") @Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
protected boolean quiet = false; protected boolean quiet = false;
@Parameter(
names = { "--log-level" },
description = "set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG",
converter = LogHelper.LogLevelConverter.class
)
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
@Parameter(names = { "--version" }, description = "print jadx version") @Parameter(names = { "--version" }, description = "print jadx version")
protected boolean printVersion = false; protected boolean printVersion = false;
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true) @Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
protected boolean printHelp = false; protected boolean printHelp = false;
@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
protected Map<String, String> pluginOptions = new HashMap<>();
public boolean processArgs(String[] args) { public boolean processArgs(String[] args) {
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this); JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
return jcw.parse(args) && process(jcw); return jcw.parse(args) && process(jcw);
@@ -165,7 +218,6 @@ public class JadxCLIArgs {
if (threadsCount <= 0) { if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive, got: " + threadsCount); throw new JadxException("Threads count must be positive, got: " + threadsCount);
} }
LogHelper.setLogLevelFromArgs(this);
} catch (JadxException e) { } catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage()); System.err.println("ERROR: " + e.getMessage());
jcw.printUsage(); jcw.printUsage();
@@ -183,9 +235,6 @@ public class JadxCLIArgs {
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase())); args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
args.setThreadsCount(threadsCount); args.setThreadsCount(threadsCount);
args.setSkipSources(skipSources); args.setSkipSources(skipSources);
if (singleClass != null) {
args.setClassFilter(className -> singleClass.equals(className));
}
args.setSkipResources(skipResources); args.setSkipResources(skipResources);
args.setFallbackMode(fallbackMode); args.setFallbackMode(fallbackMode);
args.setShowInconsistentCode(showInconsistentCode); args.setShowInconsistentCode(showInconsistentCode);
@@ -193,21 +242,30 @@ public class JadxCLIArgs {
args.setRawCFGOutput(rawCfgOutput); args.setRawCFGOutput(rawCfgOutput);
args.setReplaceConsts(replaceConsts); args.setReplaceConsts(replaceConsts);
args.setDeobfuscationOn(deobfuscationOn); args.setDeobfuscationOn(deobfuscationOn);
args.setDeobfuscationForceSave(deobfuscationForceSave); args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
if (deobfuscationForceSave) {
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
} else {
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
}
args.setDeobfuscationMinLength(deobfuscationMinLength); args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength); args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias); args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata); args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
args.setEscapeUnicode(escapeUnicode); args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers); args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject); args.setExportAsGradleProject(exportAsGradleProject);
args.setUseImports(useImports); args.setUseImports(useImports);
args.setDebugInfo(debugInfo); args.setDebugInfo(debugInfo);
args.setInsertDebugLines(addDebugLines);
args.setInlineAnonymousClasses(inlineAnonymousClasses); args.setInlineAnonymousClasses(inlineAnonymousClasses);
args.setRenameCaseSensitive(isRenameCaseSensitive()); args.setInlineMethods(inlineMethods);
args.setRenameValid(isRenameValid()); args.setRenameFlags(renameFlags);
args.setRenamePrintable(isRenamePrintable());
args.setFsCaseSensitive(fsCaseSensitive); args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel);
args.setUseDxInput(useDx);
args.setPluginOptions(pluginOptions);
return args; return args;
} }
@@ -227,6 +285,14 @@ public class JadxCLIArgs {
return outDirRes; return outDirRes;
} }
public String getSingleClass() {
return singleClass;
}
public String getSingleClassOutput() {
return singleClassOutput;
}
public boolean isSkipResources() { public boolean isSkipResources() {
return skipResources; return skipResources;
} }
@@ -243,6 +309,10 @@ public class JadxCLIArgs {
return fallbackMode; return fallbackMode;
} }
public boolean isUseDx() {
return useDx;
}
public boolean isShowInconsistentCode() { public boolean isShowInconsistentCode() {
return showInconsistentCode; return showInconsistentCode;
} }
@@ -255,10 +325,18 @@ public class JadxCLIArgs {
return debugInfo; return debugInfo;
} }
public boolean isAddDebugLines() {
return addDebugLines;
}
public boolean isInlineAnonymousClasses() { public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses; return inlineAnonymousClasses;
} }
public boolean isInlineMethods() {
return inlineMethods;
}
public boolean isDeobfuscationOn() { public boolean isDeobfuscationOn() {
return deobfuscationOn; return deobfuscationOn;
} }
@@ -271,6 +349,14 @@ public class JadxCLIArgs {
return deobfuscationMaxLength; return deobfuscationMaxLength;
} }
public String getDeobfuscationMapFile() {
return deobfuscationMapFile;
}
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
return deobfuscationMapFileMode;
}
public boolean isDeobfuscationForceSave() { public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave; return deobfuscationForceSave;
} }
@@ -283,6 +369,10 @@ public class JadxCLIArgs {
return deobfuscationParseKotlinMetadata; return deobfuscationParseKotlinMetadata;
} }
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public boolean isEscapeUnicode() { public boolean isEscapeUnicode() {
return escapeUnicode; return escapeUnicode;
} }
@@ -323,6 +413,18 @@ public class JadxCLIArgs {
return fsCaseSensitive; return fsCaseSensitive;
} }
public CommentsLevel getCommentsLevel() {
return commentsLevel;
}
public LogHelper.LogLevelEnum getLogLevel() {
return logLevel;
}
public Map<String, String> getPluginOptions() {
return pluginOptions;
}
static class RenameConverter implements IStringConverter<Set<RenameEnum>> { static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
private final String paramName; private final String paramName;
@@ -341,7 +443,7 @@ public class JadxCLIArgs {
Set<RenameEnum> set = EnumSet.noneOf(RenameEnum.class); Set<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
for (String s : value.split(",")) { for (String s : value.split(",")) {
try { try {
set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT))); set.add(RenameEnum.valueOf(s.trim().toUpperCase(Locale.ROOT)));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
'\'' + s + "' is unknown for parameter " + paramName '\'' + s + "' is unknown for parameter " + paramName
@@ -352,9 +454,48 @@ public class JadxCLIArgs {
} }
} }
public static class CommentsLevelConverter implements IStringConverter<CommentsLevel> {
@Override
public CommentsLevel convert(String value) {
try {
return CommentsLevel.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown comments level, possible values are: "
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
}
}
}
public static class UseKotlinMethodsForVarNamesConverter implements IStringConverter<UseKotlinMethodsForVarNames> {
@Override
public UseKotlinMethodsForVarNames convert(String value) {
try {
return UseKotlinMethodsForVarNames.valueOf(value.replace('-', '_').toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: "
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
}
}
}
public static class DeobfuscationMapFileModeConverter implements IStringConverter<DeobfuscationMapFileMode> {
@Override
public DeobfuscationMapFileMode convert(String value) {
try {
return DeobfuscationMapFileMode.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: "
+ JadxCLIArgs.enumValuesString(DeobfuscationMapFileMode.values()));
}
}
}
public static String enumValuesString(Enum<?>[] values) { public static String enumValuesString(Enum<?>[] values) {
return Stream.of(values) return Stream.of(values)
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'') .map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
.collect(Collectors.joining(", ")); .collect(Collectors.joining(", "));
} }
} }
+59 -16
View File
@@ -1,5 +1,7 @@
package jadx.cli; package jadx.cli;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter; import com.beust.jcommander.IStringConverter;
@@ -31,35 +33,76 @@ public class LogHelper {
} }
} }
public static void setLogLevelFromArgs(JadxCLIArgs args) { @Nullable("For disable log level control")
private static LogLevelEnum logLevelValue;
public static void initLogLevel(JadxCLIArgs args) {
logLevelValue = getLogLevelFromArgs(args);
}
private static LogLevelEnum getLogLevelFromArgs(JadxCLIArgs args) {
if (isCustomLogConfig()) { 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; return;
} }
LogLevelEnum logLevel = args.logLevel; if (logLevelValue == LogLevelEnum.PROGRESS) {
if (args.quiet) { // show load errors
logLevel = LogLevelEnum.QUIET; LogHelper.applyLogLevel(LogLevelEnum.ERROR);
} else if (args.verbose) { fixForShowProgress();
logLevel = LogLevelEnum.DEBUG; return;
} }
applyLogLevel(logLevelValue);
applyLogLevel(logLevel);
} }
public static void applyLogLevel(LogLevelEnum logLevel) { public static void setLogLevelsForDecompileStage() {
if (logLevelValue == null) {
return;
}
applyLogLevel(logLevelValue);
if (logLevelValue == LogLevelEnum.PROGRESS) {
fixForShowProgress();
}
}
/**
* Show progress: change to 'INFO' for control classes
*/
private static void fixForShowProgress() {
setLevelForClass(JadxCLI.class, Level.INFO);
setLevelForClass(JadxDecompiler.class, Level.INFO);
setLevelForClass(SingleClassMode.class, Level.INFO);
}
private static void applyLogLevel(@NotNull LogLevelEnum logLevel) {
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(logLevel.getLevel()); rootLogger.setLevel(logLevel.getLevel());
if (logLevel != LogLevelEnum.QUIET) {
// show progress for all levels except quiet
setLevelForClass(JadxCLI.class, Level.INFO);
setLevelForClass(JadxDecompiler.class, Level.INFO);
}
} }
private static void setLevelForClass(Class<?> cls, Level level) { @Nullable
public static LogLevelEnum getLogLevel() {
return logLevelValue;
}
public static void setLevelForClass(Class<?> cls, Level level) {
((Logger) LoggerFactory.getLogger(cls)).setLevel(level); ((Logger) LoggerFactory.getLogger(cls)).setLevel(level);
} }
public static void setLevelForPackage(String pkgName, Level level) {
((Logger) LoggerFactory.getLogger(pkgName)).setLevel(level);
}
/** /**
* Try to detect if user provide custom logback config via -Dlogback.configurationFile= * Try to detect if user provide custom logback config via -Dlogback.configurationFile=
*/ */
@@ -0,0 +1,87 @@
package jadx.cli;
import java.io.File;
import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.api.JadxDecompiler;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
public class SingleClassMode {
private static final Logger LOG = LoggerFactory.getLogger(SingleClassMode.class);
public static boolean process(JadxDecompiler jadx, JadxCLIArgs cliArgs) {
String singleClass = cliArgs.getSingleClass();
String singleClassOutput = cliArgs.getSingleClassOutput();
if (singleClass == null && singleClassOutput == null) {
return false;
}
ClassNode clsForProcess;
if (singleClass != null) {
clsForProcess = jadx.getRoot().resolveClass(singleClass);
if (clsForProcess == null) {
clsForProcess = jadx.getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(singleClass))
.findFirst().orElse(null);
}
if (clsForProcess == null) {
throw new JadxRuntimeException("Input class not found: " + singleClass);
}
if (clsForProcess.contains(AFlag.DONT_GENERATE)) {
throw new JadxRuntimeException("Input class can't be saved by currect jadx settings (marked as DONT_GENERATE)");
}
if (clsForProcess.isInner()) {
clsForProcess = clsForProcess.getTopParentClass();
LOG.warn("Input class is inner, parent class will be saved: {}", clsForProcess.getFullName());
}
} else {
// singleClassOutput is set
// expect only one class to be loaded
List<ClassNode> classes = jadx.getRoot().getClasses().stream()
.filter(c -> !c.isInner() && !c.contains(AFlag.DONT_GENERATE))
.collect(Collectors.toList());
int size = classes.size();
if (size == 1) {
clsForProcess = classes.get(0);
} else {
throw new JadxRuntimeException("Found " + size + " classes, single class output can't be used");
}
}
ICodeInfo codeInfo;
try {
codeInfo = clsForProcess.decompile();
} catch (Exception e) {
throw new JadxRuntimeException("Class decompilation failed", e);
}
String fileExt = SaveCode.getFileExtension(jadx.getRoot());
File out;
if (singleClassOutput == null) {
out = new File(jadx.getArgs().getOutDirSrc(), clsForProcess.getClassInfo().getAliasFullPath() + fileExt);
} else {
if (singleClassOutput.endsWith(fileExt)) {
// treat as file name
out = new File(singleClassOutput);
} else {
// treat as directory
out = new File(singleClassOutput, clsForProcess.getShortName() + fileExt);
}
}
File resultOut = FileUtils.prepareFile(out);
if (clsForProcess.getClassInfo().hasAlias()) {
LOG.info("Saving class '{}' (alias: '{}') to file '{}'",
clsForProcess.getClassInfo().getFullName(), clsForProcess.getFullName(), resultOut.getAbsolutePath());
} else {
LOG.info("Saving class '{}' to file '{}'", clsForProcess.getFullName(), resultOut.getAbsolutePath());
}
SaveCode.save(codeInfo.getCodeStr(), resultOut);
return true;
}
}
@@ -1,6 +1,5 @@
package jadx.cli.clst; package jadx.cli.clst;
import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
@@ -17,7 +16,9 @@ import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin; import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult; import jadx.api.plugins.input.data.ILoadResult;
import jadx.core.clsp.ClsSet; import jadx.core.clsp.ClsSet;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.SignatureProcessor;
/** /**
* Utility class for convert dex or jar to jadx classes set (.jcst) * Utility class for convert dex or jar to jadx classes set (.jcst)
@@ -29,7 +30,7 @@ public class ConvertToClsSet {
LOG.info("<output .jcst or .jar file> <several input dex or jar files> "); LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
} }
public static void main(String[] args) throws IOException { public static void main(String[] args) throws Exception {
if (args.length < 2) { if (args.length < 2) {
usage(); usage();
System.exit(1); System.exit(1);
@@ -38,6 +39,7 @@ public class ConvertToClsSet {
Path output = inputPaths.remove(0); Path output = inputPaths.remove(0);
JadxPluginManager pluginManager = new JadxPluginManager(); JadxPluginManager pluginManager = new JadxPluginManager();
pluginManager.load();
List<ILoadResult> loadedInputs = new ArrayList<>(); List<ILoadResult> loadedInputs = new ArrayList<>();
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) { for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
loadedInputs.add(inputPlugin.loadFiles(inputPaths)); loadedInputs.add(inputPlugin.loadFiles(inputPaths));
@@ -48,11 +50,18 @@ public class ConvertToClsSet {
RootNode root = new RootNode(jadxArgs); RootNode root = new RootNode(jadxArgs);
root.loadClasses(loadedInputs); root.loadClasses(loadedInputs);
// from pre-decompilation stage run only SignatureProcessor
SignatureProcessor signatureProcessor = new SignatureProcessor();
signatureProcessor.init(root);
for (ClassNode classNode : root.getClasses()) {
signatureProcessor.visit(classNode);
}
ClsSet set = new ClsSet(root); ClsSet set = new ClsSet(root);
set.loadFrom(root); set.loadFrom(root);
set.save(output); set.save(output);
LOG.info("Output: {}, file size: {}B", output, output.toFile().length()); LOG.info("Output: {}", output);
LOG.info("done"); LOG.info("done");
} }
} }
@@ -0,0 +1,98 @@
package jadx.cli.tools;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.android.TextResMapFile;
import jadx.core.xmlgen.ResTableParser;
/**
* Utility class for convert '.arsc' to simple text file with mapping id to resource name
*/
public class ConvertArscFile {
private static final Logger LOG = LoggerFactory.getLogger(ConvertArscFile.class);
private static int rewritesCount;
public static void usage() {
LOG.info("<res-map file> <input .arsc files>");
LOG.info("");
LOG.info("Note: If res-map already exists - it will be merged and updated");
}
public static void main(String[] args) throws IOException {
if (args.length < 2) {
usage();
System.exit(1);
}
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
Path resMapFile = inputPaths.remove(0);
Map<Integer, String> resMap;
if (Files.isReadable(resMapFile)) {
resMap = TextResMapFile.read(resMapFile);
} else {
resMap = new HashMap<>();
}
LOG.info("Input entries count: {}", resMap.size());
RootNode root = new RootNode(new JadxArgs()); // not really needed
rewritesCount = 0;
for (Path resFile : inputPaths) {
LOG.info("Processing {}", resFile);
ResTableParser resTableParser = new ResTableParser(root, true);
if (resFile.getFileName().toString().endsWith(".jar")) {
// Load resources.arsc from android.jar
try (ZipFile zip = new ZipFile(resFile.toFile())) {
ZipEntry entry = zip.getEntry("resources.arsc");
if (entry == null) {
LOG.error("Failed to load \"resources.arsc\" from {}", resFile);
continue;
}
try (InputStream inputStream = zip.getInputStream(entry)) {
resTableParser.decode(inputStream);
}
}
} else {
// Load resources.arsc from extracted file
try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(resFile))) {
resTableParser.decode(inputStream);
}
}
Map<Integer, String> singleResMap = resTableParser.getResStorage().getResourcesNames();
mergeResMaps(resMap, singleResMap);
LOG.info("{} entries count: {}, after merge: {}", resFile.getFileName(), singleResMap.size(), resMap.size());
}
LOG.info("Output entries count: {}", resMap.size());
LOG.info("Total rewrites count: {}", rewritesCount);
TextResMapFile.write(resMapFile, resMap);
LOG.info("Result file size: {} B", resMapFile.toFile().length());
LOG.info("done");
}
private static void mergeResMaps(Map<Integer, String> mainResMap, Map<Integer, String> newResMap) {
for (Map.Entry<Integer, String> entry : newResMap.entrySet()) {
Integer id = entry.getKey();
String name = entry.getValue();
String prevName = mainResMap.put(id, name);
if (prevName != null && !name.equals(prevName)) {
LOG.debug("Rewrite id: {} from: '{}' to: '{}'", Integer.toHexString(id), prevName, name);
rewritesCount++;
}
}
}
}
@@ -42,8 +42,7 @@ public class RenameConverterTest {
() -> converter.convert("wrong"), () -> converter.convert("wrong"),
"Expected convert() to throw, but it didn't"); "Expected convert() to throw, but it didn't");
assertEquals("'wrong' is unknown for parameter someParam, " assertEquals("'wrong' is unknown for parameter someParam, possible values are case, valid, printable",
+ "possible values are 'case', 'valid', 'printable'",
thrown.getMessage()); thrown.getMessage());
} }
} }
@@ -7,6 +7,7 @@ import java.nio.file.Files;
import java.nio.file.LinkOption; import java.nio.file.LinkOption;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.PathMatcher; import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -44,19 +45,20 @@ public class TestInput {
} }
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException { private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
StringBuilder args = new StringBuilder(); List<String> args = new ArrayList<>();
Path tempDir = FileUtils.createTempDir(tmpDirName); Path tempDir = FileUtils.createTempDir(tmpDirName);
args.append("-v"); args.add("-v");
args.append(" -d ").append(tempDir.toAbsolutePath()); args.add("-d");
args.add(tempDir.toAbsolutePath().toString());
for (String inputSample : inputSamples) { for (String inputSample : inputSamples) {
URL resource = getClass().getClassLoader().getResource(inputSample); URL resource = getClass().getClassLoader().getResource(inputSample);
assertThat(resource).isNotNull(); assertThat(resource).isNotNull();
String sampleFile = resource.toURI().getRawPath(); String sampleFile = resource.toURI().getRawPath();
args.append(' ').append(sampleFile); args.add(sampleFile);
} }
int result = JadxCLI.execute(args.toString().split(" ")); int result = JadxCLI.execute(args.toArray(new String[0]));
assertThat(result).isEqualTo(0); assertThat(result).isEqualTo(0);
List<Path> resultJavaFiles = collectJavaFilesInDir(tempDir); List<Path> resultJavaFiles = collectJavaFilesInDir(tempDir);
assertThat(resultJavaFiles).isNotEmpty(); assertThat(resultJavaFiles).isNotEmpty();
+13 -6
View File
@@ -1,20 +1,27 @@
plugins { plugins {
id 'java-library' id 'jadx-library'
} }
dependencies { dependencies {
runtimeOnly files('clsp-data/android-29-clst.jar')
runtimeOnly files('clsp-data/android-29-res.jar')
api(project(':jadx-plugins:jadx-plugins-api')) api(project(':jadx-plugins:jadx-plugins-api'))
implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.google.code.gson:gson:2.9.0'
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
constraints {
// Force protobuf version to prevent Java-7 issue
implementation 'com.google.protobuf:protobuf-java:3.11.4'
}
testImplementation 'org.apache.commons:commons-lang3:3.11' testImplementation 'org.apache.commons:commons-lang3:3.12.0'
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input')) testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input')) testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert')) testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
testImplementation 'org.eclipse.jdt:ecj:3.29.0'
testImplementation 'tools.profiler:async-profiler:1.8.3'
} }
test { test {
Binary file not shown.
Binary file not shown.
@@ -2,32 +2,27 @@ package jadx.api;
public final class CodePosition { public final class CodePosition {
private final JavaNode node;
private final int line; private final int line;
private final int offset; private final int offset;
private final int pos;
public CodePosition(JavaNode node, int line, int offset) { public CodePosition(int line, int offset, int pos) {
this.node = node;
this.line = line; this.line = line;
this.offset = offset; this.offset = offset;
this.pos = pos;
} }
public CodePosition(int line) {
this(line, 0, -1);
}
@Deprecated
public CodePosition(int line, int offset) { public CodePosition(int line, int offset) {
this.node = null; this(line, offset, -1);
this.line = line;
this.offset = offset;
} }
public JavaNode getNode() { public int getPos() {
return node; return pos;
}
public JavaClass getJavaClass() {
JavaClass parent = node.getDeclaringClass();
if (parent == null && node instanceof JavaClass) {
return (JavaClass) node;
}
return parent;
} }
public int getLine() { public int getLine() {
@@ -62,8 +57,8 @@ public final class CodePosition {
if (offset != 0) { if (offset != 0) {
sb.append(':').append(offset); sb.append(':').append(offset);
} }
if (node != null) { if (pos > 0) {
sb.append(' ').append(node); sb.append('@').append(pos);
} }
return sb.toString(); return sb.toString();
} }
@@ -0,0 +1,14 @@
package jadx.api;
public enum CommentsLevel {
NONE,
USER_ONLY,
ERROR,
WARN,
INFO,
DEBUG;
public boolean filter(CommentsLevel limit) {
return this.ordinal() <= limit.ordinal();
}
}
@@ -0,0 +1,60 @@
package jadx.api;
import java.util.Map;
import jadx.core.dex.attributes.ILineAttributeNode;
public interface ICodeWriter {
String NL = System.getProperty("line.separator");
String INDENT_STR = " ";
boolean isMetadataSupported();
ICodeWriter startLine();
ICodeWriter startLine(char c);
ICodeWriter startLine(String str);
ICodeWriter startLineWithNum(int sourceLine);
ICodeWriter addMultiLine(String str);
ICodeWriter add(String str);
ICodeWriter add(char c);
ICodeWriter add(ICodeWriter code);
ICodeWriter newLine();
ICodeWriter addIndent();
void incIndent();
void decIndent();
int getIndent();
void setIndent(int indent);
int getLine();
void attachDefinition(ILineAttributeNode obj);
void attachAnnotation(Object obj);
void attachLineAnnotation(Object obj);
void attachSourceLine(int sourceLine);
ICodeInfo finish();
String getCodeStr();
int getLength();
StringBuilder getRawBuf();
Map<CodePosition, Object> getRawAnnotations();
}
@@ -0,0 +1,7 @@
package jadx.api;
import java.util.List;
public interface IDecompileScheduler {
List<List<JavaClass>> buildBatches(List<JavaClass> classes);
}
+142 -5
View File
@@ -4,10 +4,16 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.data.ICodeData;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache; import jadx.api.impl.InMemoryCodeCache;
public class JadxArgs { public class JadxArgs {
@@ -25,6 +31,7 @@ public class JadxArgs {
private File outDirRes; private File outDirRes;
private ICodeCache codeCache = new InMemoryCodeCache(); private ICodeCache codeCache = new InMemoryCodeCache();
private Function<JadxArgs, ICodeWriter> codeWriterProvider = AnnotatedCodeWriter::new;
private int threadsCount = DEFAULT_THREADS_COUNT; private int threadsCount = DEFAULT_THREADS_COUNT;
@@ -36,7 +43,10 @@ public class JadxArgs {
private boolean useImports = true; private boolean useImports = true;
private boolean debugInfo = true; private boolean debugInfo = true;
private boolean insertDebugLines = false;
private boolean extractFinally = true;
private boolean inlineAnonymousClasses = true; private boolean inlineAnonymousClasses = true;
private boolean inlineMethods = true;
private boolean skipResources = false; private boolean skipResources = false;
private boolean skipSources = false; private boolean skipSources = false;
@@ -47,9 +57,11 @@ public class JadxArgs {
private Predicate<String> classFilter = null; private Predicate<String> classFilter = null;
private boolean deobfuscationOn = false; private boolean deobfuscationOn = false;
private boolean deobfuscationForceSave = false;
private boolean useSourceNameAsClassAlias = false; private boolean useSourceNameAsClassAlias = false;
private boolean parseKotlinMetadata = false; private boolean parseKotlinMetadata = false;
private File deobfuscationMapFile = null;
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
private int deobfuscationMinLength = 0; private int deobfuscationMinLength = 0;
private int deobfuscationMaxLength = Integer.MAX_VALUE; private int deobfuscationMaxLength = Integer.MAX_VALUE;
@@ -73,6 +85,25 @@ public class JadxArgs {
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA; private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
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() { public JadxArgs() {
// use default options // use default options
} }
@@ -124,7 +155,7 @@ public class JadxArgs {
} }
public void setThreadsCount(int threadsCount) { public void setThreadsCount(int threadsCount) {
this.threadsCount = threadsCount; this.threadsCount = Math.max(1, threadsCount); // make sure threadsCount >= 1
} }
public boolean isCfgOutput() { public boolean isCfgOutput() {
@@ -175,6 +206,14 @@ public class JadxArgs {
this.debugInfo = debugInfo; this.debugInfo = debugInfo;
} }
public boolean isInsertDebugLines() {
return insertDebugLines;
}
public void setInsertDebugLines(boolean insertDebugLines) {
this.insertDebugLines = insertDebugLines;
}
public boolean isInlineAnonymousClasses() { public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses; return inlineAnonymousClasses;
} }
@@ -183,6 +222,22 @@ public class JadxArgs {
this.inlineAnonymousClasses = inlineAnonymousClasses; this.inlineAnonymousClasses = inlineAnonymousClasses;
} }
public boolean isInlineMethods() {
return inlineMethods;
}
public void setInlineMethods(boolean inlineMethods) {
this.inlineMethods = inlineMethods;
}
public boolean isExtractFinally() {
return extractFinally;
}
public void setExtractFinally(boolean extractFinally) {
this.extractFinally = extractFinally;
}
public boolean isSkipResources() { public boolean isSkipResources() {
return skipResources; return skipResources;
} }
@@ -215,12 +270,24 @@ public class JadxArgs {
this.deobfuscationOn = deobfuscationOn; this.deobfuscationOn = deobfuscationOn;
} }
@Deprecated
public boolean isDeobfuscationForceSave() { public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave; return deobfuscationMapFileMode == DeobfuscationMapFileMode.OVERWRITE;
} }
@Deprecated
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) { public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
this.deobfuscationForceSave = deobfuscationForceSave; if (deobfuscationForceSave) {
this.deobfuscationMapFileMode = DeobfuscationMapFileMode.OVERWRITE;
}
}
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
return deobfuscationMapFileMode;
}
public void setDeobfuscationMapFileMode(DeobfuscationMapFileMode deobfuscationMapFileMode) {
this.deobfuscationMapFileMode = deobfuscationMapFileMode;
} }
public boolean isUseSourceNameAsClassAlias() { public boolean isUseSourceNameAsClassAlias() {
@@ -255,6 +322,14 @@ public class JadxArgs {
this.deobfuscationMaxLength = deobfuscationMaxLength; this.deobfuscationMaxLength = deobfuscationMaxLength;
} }
public File getDeobfuscationMapFile() {
return deobfuscationMapFile;
}
public void setDeobfuscationMapFile(File deobfuscationMapFile) {
this.deobfuscationMapFile = deobfuscationMapFile;
}
public boolean isEscapeUnicode() { public boolean isEscapeUnicode() {
return escapeUnicode; return escapeUnicode;
} }
@@ -355,6 +430,62 @@ public class JadxArgs {
this.codeCache = codeCache; this.codeCache = codeCache;
} }
public Function<JadxArgs, ICodeWriter> getCodeWriterProvider() {
return codeWriterProvider;
}
public void setCodeWriterProvider(Function<JadxArgs, ICodeWriter> codeWriterProvider) {
this.codeWriterProvider = codeWriterProvider;
}
public ICodeData getCodeData() {
return codeData;
}
public void setCodeData(ICodeData codeData) {
this.codeData = codeData;
}
public CommentsLevel getCommentsLevel() {
return commentsLevel;
}
public void setCommentsLevel(CommentsLevel commentsLevel) {
this.commentsLevel = commentsLevel;
}
public boolean isUseDxInput() {
return useDxInput;
}
public void setUseDxInput(boolean useDxInput) {
this.useDxInput = useDxInput;
}
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public void setUseKotlinMethodsForVarNames(UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
}
public boolean isSkipFilesSave() {
return skipFilesSave;
}
public void setSkipFilesSave(boolean skipFilesSave) {
this.skipFilesSave = skipFilesSave;
}
public Map<String, String> getPluginOptions() {
return pluginOptions;
}
public void setPluginOptions(Map<String, String> pluginOptions) {
this.pluginOptions = pluginOptions;
}
@Override @Override
public String toString() { public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -370,9 +501,11 @@ public class JadxArgs {
+ ", skipResources=" + skipResources + ", skipResources=" + skipResources
+ ", skipSources=" + skipSources + ", skipSources=" + skipSources
+ ", deobfuscationOn=" + deobfuscationOn + ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationForceSave=" + deobfuscationForceSave + ", deobfuscationMapFile=" + deobfuscationMapFile
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias + ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata + ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
+ ", deobfuscationMinLength=" + deobfuscationMinLength + ", deobfuscationMinLength=" + deobfuscationMinLength
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength + ", deobfuscationMaxLength=" + deobfuscationMaxLength
+ ", escapeUnicode=" + escapeUnicode + ", escapeUnicode=" + escapeUnicode
@@ -382,7 +515,11 @@ public class JadxArgs {
+ ", fsCaseSensitive=" + fsCaseSensitive + ", fsCaseSensitive=" + fsCaseSensitive
+ ", renameFlags=" + renameFlags + ", renameFlags=" + renameFlags
+ ", outputFormat=" + outputFormat + ", outputFormat=" + outputFormat
+ ", commentsLevel=" + commentsLevel
+ ", codeCache=" + codeCache + ", codeCache=" + codeCache
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
+ ", useDxInput=" + useDxInput
+ ", pluginOptions=" + pluginOptions
+ '}'; + '}';
} }
} }
@@ -85,9 +85,6 @@ public class JadxArgsValidator {
if (!file.exists()) { if (!file.exists()) {
throw new JadxArgsValidateException("File not found " + file.getAbsolutePath()); throw new JadxArgsValidateException("File not found " + file.getAbsolutePath());
} }
if (file.isDirectory()) {
throw new JadxArgsValidateException("Expected file but found directory instead: " + file.getAbsolutePath());
}
} }
private static void checkDir(File dir, String desc) { private static void checkDir(File dir, String desc) {
@@ -15,18 +15,22 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.data.annotations.VarRef;
import jadx.api.plugins.JadxPlugin; import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginManager; import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin; import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult; import jadx.api.plugins.input.data.ILoadResult;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.core.Jadx; import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.attributes.nodes.LineAttrNode;
@@ -36,9 +40,13 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.SaveCode; import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject; import jadx.core.export.ExportGradleProject;
import jadx.core.utils.DecompilerScheduler;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.BinaryXMLParser; import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ProtoXMLParser;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResourcesSaver; import jadx.core.xmlgen.ResourcesSaver;
/** /**
@@ -46,6 +54,7 @@ import jadx.core.xmlgen.ResourcesSaver;
* *
* <pre> * <pre>
* <code> * <code>
*
* JadxArgs args = new JadxArgs(); * JadxArgs args = new JadxArgs();
* args.getInputFiles().add(new File("test.apk")); * args.getInputFiles().add(new File("test.apk"));
* args.setOutDir(new File("jadx-test-output")); * args.setOutDir(new File("jadx-test-output"));
@@ -60,6 +69,7 @@ import jadx.core.xmlgen.ResourcesSaver;
* *
* <pre> * <pre>
* <code> * <code>
*
* for(JavaClass cls : jadx.getClasses()) { * for(JavaClass cls : jadx.getClasses()) {
* System.out.println(cls.getCode()); * System.out.println(cls.getCode());
* } * }
@@ -77,12 +87,15 @@ public final class JadxDecompiler implements Closeable {
private List<JavaClass> classes; private List<JavaClass> classes;
private List<ResourceFile> resources; private List<ResourceFile> resources;
private BinaryXMLParser xmlParser; private BinaryXMLParser binaryXmlParser;
private ProtoXMLParser protoXmlParser;
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>(); private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>(); private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>(); private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
public JadxDecompiler() { public JadxDecompiler() {
this(new JadxArgs()); this(new JadxArgs());
} }
@@ -95,6 +108,7 @@ public final class JadxDecompiler implements Closeable {
reset(); reset();
JadxArgsValidator.validate(args); JadxArgsValidator.validate(args);
LOG.info("loading ..."); LOG.info("loading ...");
loadPlugins(args);
loadInputFiles(); loadInputFiles();
root = new RootNode(args); root = new RootNode(args);
@@ -108,19 +122,25 @@ public final class JadxDecompiler implements Closeable {
private void loadInputFiles() { private void loadInputFiles() {
loadedInputs.clear(); loadedInputs.clear();
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath); List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
long start = System.currentTimeMillis();
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) { for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
ILoadResult loadResult = inputPlugin.loadFiles(inputPaths); ILoadResult loadResult = inputPlugin.loadFiles(inputFiles);
if (loadResult != null && !loadResult.isEmpty()) { if (loadResult != null && !loadResult.isEmpty()) {
loadedInputs.add(loadResult); loadedInputs.add(loadResult);
} }
} }
if (LOG.isDebugEnabled()) {
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
}
} }
private void reset() { private void reset() {
root = null; root = null;
classes = null; classes = null;
resources = null; resources = null;
xmlParser = null; binaryXmlParser = null;
protoXmlParser = null;
classesMap.clear(); classesMap.clear();
methodsMap.clear(); methodsMap.clear();
@@ -145,6 +165,27 @@ public final class JadxDecompiler implements Closeable {
reset(); reset();
} }
private void loadPlugins(JadxArgs args) {
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
pluginManager.load();
if (LOG.isDebugEnabled()) {
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
p -> p.getPluginInfo().getPluginId()));
}
Map<String, String> pluginOptions = args.getPluginOptions();
if (!pluginOptions.isEmpty()) {
LOG.debug("Applying plugin options: {}", pluginOptions);
for (JadxPluginOptions plugin : pluginManager.getPluginsWithOptions()) {
try {
plugin.setOptions(pluginOptions);
} catch (Exception e) {
String pluginId = plugin.getPluginInfo().getPluginId();
throw new JadxRuntimeException("Failed to apply options for plugin: " + pluginId, e);
}
}
}
}
public void registerPlugin(JadxPlugin plugin) { public void registerPlugin(JadxPlugin plugin) {
pluginManager.register(plugin); pluginManager.register(plugin);
} }
@@ -157,6 +198,27 @@ public final class JadxDecompiler implements Closeable {
save(!args.isSkipSources(), !args.isSkipResources()); save(!args.isSkipSources(), !args.isSkipResources());
} }
public interface ProgressListener {
void progress(long done, long total);
}
@SuppressWarnings("BusyWait")
public void save(int intervalInMillis, ProgressListener listener) {
ThreadPoolExecutor ex = (ThreadPoolExecutor) getSaveExecutor();
ex.shutdown();
try {
long total = ex.getTaskCount();
while (ex.isTerminating()) {
long done = ex.getCompletedTaskCount();
listener.progress(done, total);
Thread.sleep(intervalInMillis);
}
} catch (InterruptedException e) {
LOG.error("Save interrupted", e);
Thread.currentThread().interrupt();
}
}
public void saveSources() { public void saveSources() {
save(true, false); save(true, false);
} }
@@ -180,20 +242,44 @@ public final class JadxDecompiler implements Closeable {
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources()); return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
} }
public List<Runnable> getSaveTasks() {
return getSaveTasks(!args.isSkipSources(), !args.isSkipResources());
}
private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) { private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) {
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
LOG.info("processing ...");
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
List<Runnable> tasks = getSaveTasks(saveSources, saveResources);
tasks.forEach(executor::execute);
return executor;
}
private List<Runnable> getSaveTasks(boolean saveSources, boolean saveResources) {
if (root == null) { if (root == null) {
throw new JadxRuntimeException("No loaded files"); throw new JadxRuntimeException("No loaded files");
} }
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
LOG.info("processing ...");
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
File sourcesOutDir; File sourcesOutDir;
File resOutDir; File resOutDir;
if (args.isExportAsGradleProject()) { if (args.isExportAsGradleProject()) {
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir()); ResourceFile androidManifest = resources.stream()
.filter(resourceFile -> resourceFile.getType() == ResourceType.MANIFEST)
.findFirst()
.orElseThrow(IllegalStateException::new);
ResContainer strings = resources.stream()
.filter(resourceFile -> resourceFile.getType() == ResourceType.ARSC)
.findFirst()
.orElseThrow(IllegalStateException::new)
.loadContent()
.getSubFiles()
.stream()
.filter(resContainer -> resContainer.getFileName().contains("strings.xml"))
.findFirst()
.orElseThrow(IllegalStateException::new);
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir(), androidManifest, strings);
export.init(); export.init();
sourcesOutDir = export.getSrcOutDir(); sourcesOutDir = export.getSrcOutDir();
resOutDir = export.getResOutDir(); resOutDir = export.getResOutDir();
@@ -201,16 +287,21 @@ public final class JadxDecompiler implements Closeable {
sourcesOutDir = args.getOutDirSrc(); sourcesOutDir = args.getOutDirSrc();
resOutDir = args.getOutDirRes(); resOutDir = args.getOutDirRes();
} }
List<Runnable> tasks = new ArrayList<>();
// save resources first because decompilation can hang or fail
if (saveResources) { if (saveResources) {
appendResourcesSave(executor, resOutDir); appendResourcesSaveTasks(tasks, resOutDir);
} }
if (saveSources) { if (saveSources) {
appendSourcesSave(executor, sourcesOutDir); appendSourcesSave(tasks, sourcesOutDir);
} }
return executor; return tasks;
} }
private void appendResourcesSave(ExecutorService executor, File outDir) { private void appendResourcesSaveTasks(List<Runnable> tasks, File outDir) {
if (args.isSkipFilesSave()) {
return;
}
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet()); Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
for (ResourceFile resourceFile : getResources()) { for (ResourceFile resourceFile : getResources()) {
if (resourceFile.getType() != ResourceType.ARSC if (resourceFile.getType() != ResourceType.ARSC
@@ -218,25 +309,38 @@ public final class JadxDecompiler implements Closeable {
// ignore resource made from input file // ignore resource made from input file
continue; continue;
} }
executor.execute(new ResourcesSaver(outDir, resourceFile)); tasks.add(new ResourcesSaver(outDir, resourceFile));
} }
} }
private void appendSourcesSave(ExecutorService executor, File outDir) { private void appendSourcesSave(List<Runnable> tasks, File outDir) {
Predicate<String> classFilter = args.getClassFilter(); Predicate<String> classFilter = args.getClassFilter();
for (JavaClass cls : getClasses()) { List<JavaClass> classes = getClasses();
List<JavaClass> processQueue = new ArrayList<>(classes.size());
for (JavaClass cls : classes) {
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) { if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
continue; continue;
} }
if (classFilter != null && !classFilter.test(cls.getFullName())) { if (classFilter != null && !classFilter.test(cls.getFullName())) {
continue; continue;
} }
executor.execute(() -> { processQueue.add(cls);
try { }
ICodeInfo code = cls.getCodeInfo(); List<List<JavaClass>> batches;
SaveCode.save(outDir, cls.getClassNode(), code); try {
} catch (Exception e) { batches = decompileScheduler.buildBatches(processQueue);
LOG.error("Error saving class: {}", cls.getFullName(), e); } 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);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls, e);
}
} }
}); });
} }
@@ -323,11 +427,18 @@ public final class JadxDecompiler implements Closeable {
return root; return root;
} }
synchronized BinaryXMLParser getXmlParser() { synchronized BinaryXMLParser getBinaryXmlParser() {
if (xmlParser == null) { if (binaryXmlParser == null) {
xmlParser = new BinaryXMLParser(root); binaryXmlParser = new BinaryXMLParser(root);
} }
return xmlParser; return binaryXmlParser;
}
synchronized ProtoXMLParser getProtoXmlParser() {
if (protoXmlParser == null) {
protoXmlParser = new ProtoXMLParser(root);
}
return protoXmlParser;
} }
private void loadJavaClass(JavaClass javaClass) { private void loadJavaClass(JavaClass javaClass) {
@@ -338,12 +449,33 @@ public final class JadxDecompiler implements Closeable {
classesMap.put(innerCls.getClassNode(), innerCls); classesMap.put(innerCls.getClassNode(), innerCls);
loadJavaClass(innerCls); loadJavaClass(innerCls);
} }
for (JavaClass inlinedCls : javaClass.getInlinedClasses()) {
classesMap.put(inlinedCls.getClassNode(), inlinedCls);
loadJavaClass(inlinedCls);
}
}
/**
* Get JavaClass by ClassNode without loading and decompilation
*/
JavaClass convertClassNode(ClassNode cls) {
return classesMap.compute(cls, (node, prevJavaCls) -> {
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) {
// keep previous variable
return prevJavaCls;
}
if (cls.isInner()) {
return new JavaClass(cls, convertClassNode(cls.getParentClass()));
}
return new JavaClass(cls, this);
});
} }
@Nullable("For not generated classes") @Nullable("For not generated classes")
private JavaClass getJavaClassByNode(ClassNode cls) { @ApiStatus.Internal
public JavaClass getJavaClassByNode(ClassNode cls) {
JavaClass javaClass = classesMap.get(cls); JavaClass javaClass = classesMap.get(cls);
if (javaClass != null) { if (javaClass != null && javaClass.getClassNode() == cls) {
return javaClass; return javaClass;
} }
// load parent class if inner // load parent class if inner
@@ -373,9 +505,12 @@ public final class JadxDecompiler implements Closeable {
@Nullable @Nullable
private JavaMethod getJavaMethodByNode(MethodNode mth) { private JavaMethod getJavaMethodByNode(MethodNode mth) {
JavaMethod javaMethod = methodsMap.get(mth); JavaMethod javaMethod = methodsMap.get(mth);
if (javaMethod != null) { if (javaMethod != null && javaMethod.getMethodNode() == mth) {
return javaMethod; return javaMethod;
} }
if (mth.contains(AFlag.DONT_GENERATE)) {
return null;
}
// parent class not loaded yet // parent class not loaded yet
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass()); JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
if (javaClass == null) { if (javaClass == null) {
@@ -395,7 +530,7 @@ public final class JadxDecompiler implements Closeable {
@Nullable @Nullable
private JavaField getJavaFieldByNode(FieldNode fld) { private JavaField getJavaFieldByNode(FieldNode fld) {
JavaField javaField = fieldsMap.get(fld); JavaField javaField = fieldsMap.get(fld);
if (javaField != null) { if (javaField != null && javaField.getFieldNode() == fld) {
return javaField; return javaField;
} }
// parent class not loaded yet // parent class not loaded yet
@@ -414,8 +549,60 @@ public final class JadxDecompiler implements Closeable {
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld); throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
} }
@Nullable
public JavaClass searchJavaClassByOrigFullName(String fullName) {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
.findFirst()
.map(this::getJavaClassByNode)
.orElse(null);
}
@Nullable
public ClassNode searchClassNodeByOrigFullName(String fullName) {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
.findFirst()
.orElse(null);
}
// returns parent if class contains DONT_GENERATE flag.
@Nullable
public JavaClass searchJavaClassOrItsParentByOrigFullName(String fullName) {
ClassNode node = getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
.findFirst()
.orElse(null);
if (node != null) {
if (node.contains(AFlag.DONT_GENERATE)) {
return getJavaClassByNode(node.getTopParentClass());
} else {
return getJavaClassByNode(node);
}
}
return null;
}
@Nullable
public JavaClass searchJavaClassByAliasFullName(String fullName) {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
.findFirst()
.map(this::getJavaClassByNode)
.orElse(null);
}
@Nullable @Nullable
JavaNode convertNode(Object obj) { JavaNode convertNode(Object obj) {
if (obj instanceof VarRef) {
VarRef varRef = (VarRef) obj;
MethodNode mthNode = varRef.getMth();
JavaMethod mth = getJavaMethodByNode(mthNode);
if (mth == null) {
return null;
}
return new JavaVariable(mth, varRef);
}
if (!(obj instanceof LineAttrNode)) { if (!(obj instanceof LineAttrNode)) {
return null; return null;
} }
@@ -424,7 +611,7 @@ public final class JadxDecompiler implements Closeable {
return null; return null;
} }
if (obj instanceof ClassNode) { if (obj instanceof ClassNode) {
return getJavaClassByNode((ClassNode) obj); return convertClassNode((ClassNode) obj);
} }
if (obj instanceof MethodNode) { if (obj instanceof MethodNode) {
return getJavaMethodByNode(((MethodNode) obj)); return getJavaMethodByNode(((MethodNode) obj));
@@ -435,6 +622,23 @@ public final class JadxDecompiler implements Closeable {
throw new JadxRuntimeException("Unexpected node type: " + obj); throw new JadxRuntimeException("Unexpected node type: " + obj);
} }
// 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<?> nodesList) {
return nodesList.stream() return nodesList.stream()
.map(this::convertNode) .map(this::convertNode)
@@ -463,13 +667,25 @@ public final class JadxDecompiler implements Closeable {
if (defLine == 0) { if (defLine == 0) {
return null; return null;
} }
return new CodePosition(jCls, defLine, 0); return new CodePosition(defLine, 0, javaNode.getDefPos());
}
public void reloadCodeData() {
root.notifyCodeDataListeners();
} }
public JadxArgs getArgs() { public JadxArgs getArgs() {
return args; return args;
} }
public JadxPluginManager getPluginManager() {
return pluginManager;
}
public IDecompileScheduler getDecompileScheduler() {
return decompileScheduler;
}
@Override @Override
public String toString() { public String toString() {
return "jadx decompiler " + getVersion(); return "jadx decompiler " + getVersion();
@@ -7,9 +7,12 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
@@ -22,6 +25,7 @@ public final class JavaClass implements JavaNode {
private final JavaClass parent; private final JavaClass parent;
private List<JavaClass> innerClasses = Collections.emptyList(); private List<JavaClass> innerClasses = Collections.emptyList();
private List<JavaClass> inlinedClasses = Collections.emptyList();
private List<JavaField> fields = Collections.emptyList(); private List<JavaField> fields = Collections.emptyList();
private List<JavaMethod> methods = Collections.emptyList(); private List<JavaMethod> methods = Collections.emptyList();
private boolean listsLoaded; private boolean listsLoaded;
@@ -62,18 +66,23 @@ public final class JavaClass implements JavaNode {
cls.reloadCode(); cls.reloadCode();
} }
public synchronized String getSmali() { public void unload() {
return cls.getSmali(); listsLoaded = false;
cls.unloadCode();
} }
public synchronized void unload() { public boolean isNoCode() {
cls.unload(); return cls.contains(AFlag.DONT_GENERATE);
listsLoaded = false; }
public synchronized String getSmali() {
return cls.getDisassembledCode();
} }
/** /**
* Internal API. Not Stable! * Internal API. Not Stable!
*/ */
@ApiStatus.Internal
public ClassNode getClassNode() { public ClassNode getClassNode() {
return cls; return cls;
} }
@@ -84,26 +93,37 @@ public final class JavaClass implements JavaNode {
} }
listsLoaded = true; listsLoaded = true;
decompile(); decompile();
JadxDecompiler rootDecompiler = getRootDecompiler();
int inClsCount = cls.getInnerClasses().size(); int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) { if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inClsCount); List<JavaClass> list = new ArrayList<>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) { for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.contains(AFlag.DONT_GENERATE)) { if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(inner, this); JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists(); javaClass.loadLists();
list.add(javaClass); list.add(javaClass);
} }
} }
this.innerClasses = Collections.unmodifiableList(list); this.innerClasses = Collections.unmodifiableList(list);
} }
int inlinedClsCount = cls.getInlinedClasses().size();
if (inlinedClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
for (ClassNode inner : cls.getInlinedClasses()) {
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists();
list.add(javaClass);
}
this.inlinedClasses = Collections.unmodifiableList(list);
}
int fieldsCount = cls.getFields().size(); int fieldsCount = cls.getFields().size();
if (fieldsCount != 0) { if (fieldsCount != 0) {
List<JavaField> flds = new ArrayList<>(fieldsCount); List<JavaField> flds = new ArrayList<>(fieldsCount);
for (FieldNode f : cls.getFields()) { for (FieldNode f : cls.getFields()) {
if (!f.contains(AFlag.DONT_GENERATE)) { if (!f.contains(AFlag.DONT_GENERATE)) {
JavaField javaField = new JavaField(f, this); JavaField javaField = new JavaField(this, f);
flds.add(javaField); flds.add(javaField);
} }
} }
@@ -131,7 +151,7 @@ public final class JavaClass implements JavaNode {
return decompiler; return decompiler;
} }
private Map<CodePosition, Object> getCodeAnnotations() { public Map<CodePosition, Object> getCodeAnnotations() {
ICodeInfo code = getCodeInfo(); ICodeInfo code = getCodeInfo();
if (code == null) { if (code == null) {
return Collections.emptyMap(); return Collections.emptyMap();
@@ -139,6 +159,10 @@ public final class JavaClass implements JavaNode {
return code.getAnnotations(); return code.getAnnotations();
} }
public Object getAnnotationAt(CodePosition pos) {
return getCodeAnnotations().get(pos);
}
public Map<CodePosition, JavaNode> getUsageMap() { public Map<CodePosition, JavaNode> getUsageMap() {
Map<CodePosition, Object> map = getCodeAnnotations(); Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty() || decompiler == null) { if (map.isEmpty() || decompiler == null) {
@@ -156,6 +180,23 @@ public final class JavaClass implements JavaNode {
return resultMap; return resultMap;
} }
public List<CodePosition> getUsageFor(JavaNode javaNode) {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty() || decompiler == null) {
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);
}
}
return result;
}
@Override @Override
public List<JavaNode> getUseIn() { public List<JavaNode> getUseIn() {
return getRootDecompiler().convertNodes(cls.getUseIn()); return getRootDecompiler().convertNodes(cls.getUseIn());
@@ -202,9 +243,19 @@ public final class JavaClass implements JavaNode {
@Override @Override
public JavaClass getTopParentClass() { public JavaClass getTopParentClass() {
if (cls.contains(AType.ANONYMOUS_CLASS)) {
// moved to usage class
return getParentForAnonymousClass();
}
return parent == null ? this : parent.getTopParentClass(); return parent == null ? this : parent.getTopParentClass();
} }
private JavaClass getParentForAnonymousClass() {
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
ClassNode topParentClass = attr.getOuterCls().getTopParentClass();
return getRootDecompiler().convertClassNode(topParentClass);
}
public AccessInfo getAccessInfo() { public AccessInfo getAccessInfo() {
return cls.getAccessFlags(); return cls.getAccessFlags();
} }
@@ -214,6 +265,11 @@ public final class JavaClass implements JavaNode {
return innerClasses; return innerClasses;
} }
public List<JavaClass> getInlinedClasses() {
loadLists();
return inlinedClasses;
}
public List<JavaField> getFields() { public List<JavaField> getFields() {
loadLists(); loadLists();
return fields; return fields;
@@ -224,11 +280,30 @@ public final class JavaClass implements JavaNode {
return methods; return methods;
} }
@Nullable
public JavaMethod searchMethodByShortId(String shortId) {
MethodNode methodNode = cls.searchMethodByShortId(shortId);
if (methodNode == null) {
return null;
}
return new JavaMethod(this, methodNode);
}
@Override
public void removeAlias() {
this.cls.getClassInfo().removeAlias();
}
@Override @Override
public int getDecompiledLine() { public int getDecompiledLine() {
return cls.getDecompiledLine(); return cls.getDecompiledLine();
} }
@Override
public int getDefPos() {
return cls.getDefPosition();
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls); return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
@@ -2,6 +2,8 @@ package jadx.api;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
@@ -11,7 +13,7 @@ public final class JavaField implements JavaNode {
private final FieldNode field; private final FieldNode field;
private final JavaClass parent; private final JavaClass parent;
JavaField(FieldNode f, JavaClass cls) { JavaField(JavaClass cls, FieldNode f) {
this.field = f; this.field = f;
this.parent = cls; this.parent = cls;
} }
@@ -26,6 +28,10 @@ public final class JavaField implements JavaNode {
return parent.getFullName() + '.' + getName(); return parent.getFullName() + '.' + getName();
} }
public String getRawName() {
return field.getName();
}
@Override @Override
public JavaClass getDeclaringClass() { public JavaClass getDeclaringClass() {
return parent; return parent;
@@ -49,14 +55,25 @@ public final class JavaField implements JavaNode {
return field.getDecompiledLine(); return field.getDecompiledLine();
} }
@Override
public int getDefPos() {
return field.getDefPosition();
}
@Override @Override
public List<JavaNode> getUseIn() { public List<JavaNode> getUseIn() {
return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn()); return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn());
} }
@Override
public void removeAlias() {
this.field.getFieldInfo().removeAlias();
}
/** /**
* Internal API. Not Stable! * Internal API. Not Stable!
*/ */
@ApiStatus.Internal
public FieldNode getFieldNode() { public FieldNode getFieldNode() {
return field; return field;
} }
@@ -2,7 +2,12 @@ package jadx.api;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.jetbrains.annotations.ApiStatus;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
@@ -61,6 +66,17 @@ public final class JavaMethod implements JavaNode {
return getDeclaringClass().getRootDecompiler().convertNodes(mth.getUseIn()); return getDeclaringClass().getRootDecompiler().convertNodes(mth.getUseIn());
} }
public List<JavaMethod> getOverrideRelatedMethods() {
MethodOverrideAttr ovrdAttr = mth.get(AType.METHOD_OVERRIDE);
if (ovrdAttr == null) {
return Collections.emptyList();
}
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
return ovrdAttr.getRelatedMthNodes().stream()
.map(m -> ((JavaMethod) decompiler.convertNode(m)))
.collect(Collectors.toList());
}
public boolean isConstructor() { public boolean isConstructor() {
return mth.getMethodInfo().isConstructor(); return mth.getMethodInfo().isConstructor();
} }
@@ -74,9 +90,20 @@ public final class JavaMethod implements JavaNode {
return mth.getDecompiledLine(); return mth.getDecompiledLine();
} }
@Override
public int getDefPos() {
return mth.getDefPosition();
}
@Override
public void removeAlias() {
this.mth.getMethodInfo().removeAlias();
}
/** /**
* Internal API. Not Stable! * Internal API. Not Stable!
*/ */
@ApiStatus.Internal
public MethodNode getMethodNode() { public MethodNode getMethodNode() {
return mth; return mth;
} }
@@ -14,5 +14,10 @@ public interface JavaNode {
int getDecompiledLine(); int getDecompiledLine();
int getDefPos();
List<JavaNode> getUseIn(); List<JavaNode> getUseIn();
default void removeAlias() {
}
} }
@@ -44,6 +44,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return 0; return 0;
} }
@Override
public int getDefPos() {
return 0;
}
@Override @Override
public List<JavaNode> getUseIn() { public List<JavaNode> getUseIn() {
return Collections.emptyList(); return Collections.emptyList();
@@ -0,0 +1,93 @@
package jadx.api;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.data.annotations.VarRef;
public class JavaVariable implements JavaNode {
private final JavaMethod mth;
private final VarRef varRef;
public JavaVariable(JavaMethod mth, VarRef varRef) {
this.mth = mth;
this.varRef = varRef;
}
public JavaMethod getMth() {
return mth;
}
public int getReg() {
return varRef.getReg();
}
public int getSsa() {
return varRef.getSsa();
}
@Override
public String getName() {
return varRef.getName();
}
@ApiStatus.Internal
public VarRef getVarRef() {
return varRef;
}
@Override
public String getFullName() {
return varRef.getType() + " " + varRef.getName() + " (r" + varRef.getReg() + "v" + varRef.getSsa() + ")";
}
@Override
public JavaClass getDeclaringClass() {
return mth.getDeclaringClass();
}
@Override
public JavaClass getTopParentClass() {
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;
}
@Override
public List<JavaNode> getUseIn() {
return Collections.singletonList(mth);
}
@Override
public int hashCode() {
return varRef.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof JavaVariable)) {
return false;
}
return varRef.equals(((JavaVariable) o).varRef);
}
}
@@ -37,6 +37,10 @@ public class ResourceFile {
private ZipRef zipRef; private ZipRef zipRef;
private String deobfName; private String deobfName;
public static ResourceFile createResourceFile(JadxDecompiler decompiler, File file, ResourceType type) {
return new ResourceFile(decompiler, file.getAbsolutePath(), type);
}
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) { public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) { if (!ZipSecurity.isValidZipEntryName(name)) {
return null; return null;
@@ -41,6 +41,9 @@ public enum ResourceType {
} }
public static ResourceType getFileType(String fileName) { public static ResourceType getFileType(String fileName) {
if (fileName.endsWith("/resources.pb")) {
return ARSC;
}
int dot = fileName.lastIndexOf('.'); int dot = fileName.lastIndexOf('.');
if (dot != -1) { if (dot != -1) {
String ext = fileName.substring(dot).toLowerCase(Locale.ROOT); String ext = fileName.substring(dot).toLowerCase(Locale.ROOT);
@@ -17,12 +17,13 @@ import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile.ZipRef; import jadx.api.ResourceFile.ZipRef;
import jadx.api.impl.SimpleCodeInfo; import jadx.api.impl.SimpleCodeInfo;
import jadx.api.plugins.utils.ZipSecurity; import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.codegen.CodeWriter; import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.android.Res9patchStreamDecoder; import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.ResContainer; import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResProtoParser;
import jadx.core.xmlgen.ResTableParser; import jadx.core.xmlgen.ResTableParser;
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE; import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
@@ -83,7 +84,7 @@ public final class ResourcesLoader {
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is)); return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
} catch (JadxException e) { } catch (JadxException e) {
LOG.error("Decode error", e); LOG.error("Decode error", e);
CodeWriter cw = new CodeWriter(); ICodeWriter cw = jadxRef.getRoot().makeCodeWriter();
cw.add("Error decode ").add(rf.getType().toString().toLowerCase()); cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
Utils.appendStackTrace(cw, e.getCause()); Utils.appendStackTrace(cw, e.getCause());
return ResContainer.textResource(rf.getDeobfName(), cw.finish()); return ResContainer.textResource(rf.getDeobfName(), cw.finish());
@@ -92,14 +93,25 @@ public final class ResourcesLoader {
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf, private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream) throws IOException { InputStream inputStream) throws IOException {
RootNode root = jadxRef.getRoot();
switch (rf.getType()) { switch (rf.getType()) {
case MANIFEST: case MANIFEST:
case XML: case XML: {
ICodeInfo content = jadxRef.getXmlParser().parse(inputStream); ICodeInfo content;
if (root.isProto()) {
content = jadxRef.getProtoXmlParser().parse(inputStream);
} else {
content = jadxRef.getBinaryXmlParser().parse(inputStream);
}
return ResContainer.textResource(rf.getDeobfName(), content); return ResContainer.textResource(rf.getDeobfName(), content);
}
case ARSC: case ARSC:
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream); if (root.isProto()) {
return new ResProtoParser(root).decodeFiles(inputStream);
} else {
return new ResTableParser(root).decodeFiles(inputStream);
}
case IMG: case IMG:
return decodeImage(rf, inputStream); return decodeImage(rf, inputStream);
@@ -114,8 +126,9 @@ public final class ResourcesLoader {
if (name.endsWith(".9.png")) { if (name.endsWith(".9.png")) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder(); Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
decoder.decode(inputStream, os); if (decoder.decode(inputStream, os)) {
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray()); return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
}
} catch (Exception e) { } catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e); LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
} }
@@ -124,22 +137,17 @@ public final class ResourcesLoader {
} }
private void loadFile(List<ResourceFile> list, File file) { private void loadFile(List<ResourceFile> list, File file) {
if (file == null) { if (file == null || file.isDirectory()) {
return; return;
} }
if (FileUtils.isZipFile(file)) { if (FileUtils.isZipFile(file)) {
ZipSecurity.visitZipEntries(file, (zipFile, entry) -> addEntry(list, file, entry)); ZipSecurity.visitZipEntries(file, (zipFile, entry) -> {
addEntry(list, file, entry);
return null;
});
} else { } else {
addResourceFile(list, file); ResourceType type = ResourceType.getFileType(file.getAbsolutePath());
} list.add(ResourceFile.createResourceFile(jadxRef, file, type));
}
private void addResourceFile(List<ResourceFile> list, File file) {
String name = file.getAbsolutePath();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
list.add(rf);
} }
} }
@@ -0,0 +1,32 @@
package jadx.api.args;
public enum DeobfuscationMapFileMode {
/**
* Load if found, don't save (default)
*/
READ,
/**
* Load if found, save only if new (don't overwrite)
*/
READ_OR_SAVE,
/**
* Don't load, always save
*/
OVERWRITE,
/**
* Don't load and don't save
*/
IGNORE;
public boolean shouldRead() {
return this == READ || this == READ_OR_SAVE;
}
public boolean shouldWrite() {
return this == READ_OR_SAVE || this == OVERWRITE;
}
}
@@ -0,0 +1,8 @@
package jadx.api.data;
public enum CodeRefType {
MTH_ARG,
VAR,
CATCH,
INSN,
}
@@ -0,0 +1,13 @@
package jadx.api.data;
import org.jetbrains.annotations.Nullable;
public interface ICodeComment extends Comparable<ICodeComment> {
IJavaNodeRef getNodeRef();
@Nullable
IJavaCodeRef getCodeRef();
String getComment();
}
@@ -0,0 +1,10 @@
package jadx.api.data;
import java.util.List;
public interface ICodeData {
List<ICodeComment> getComments();
List<ICodeRename> getRenames();
}
@@ -0,0 +1,13 @@
package jadx.api.data;
import org.jetbrains.annotations.Nullable;
public interface ICodeRename extends Comparable<ICodeRename> {
IJavaNodeRef getNodeRef();
@Nullable
IJavaCodeRef getCodeRef();
String getNewName();
}
@@ -0,0 +1,15 @@
package jadx.api.data;
import org.jetbrains.annotations.NotNull;
public interface IJavaCodeRef extends Comparable<IJavaCodeRef> {
CodeRefType getAttachType();
int getIndex();
@Override
default int compareTo(@NotNull IJavaCodeRef o) {
return Integer.compare(getIndex(), o.getIndex());
}
}
@@ -0,0 +1,14 @@
package jadx.api.data;
public interface IJavaNodeRef extends Comparable<IJavaNodeRef> {
enum RefType {
CLASS, FIELD, METHOD, PKG
}
RefType getType();
String getDeclaringClass();
String getShortId();
}
@@ -0,0 +1,5 @@
package jadx.api.data.annotations;
public interface ICodeRawOffset {
int getOffset();
}
@@ -0,0 +1,52 @@
package jadx.api.data.annotations;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
import jadx.core.dex.nodes.InsnNode;
public class InsnCodeOffset implements ICodeRawOffset {
public static void attach(ICodeWriter code, InsnNode insn) {
if (insn == null) {
return;
}
if (code.isMetadataSupported()) {
InsnCodeOffset ann = from(insn);
if (ann != null) {
code.attachLineAnnotation(ann);
}
}
}
public static void attach(ICodeWriter code, int offset) {
if (offset >= 0 && code.isMetadataSupported()) {
code.attachLineAnnotation(new InsnCodeOffset(offset));
}
}
@Nullable
public static InsnCodeOffset from(InsnNode insn) {
int offset = insn.getOffset();
if (offset < 0) {
return null;
}
return new InsnCodeOffset(offset);
}
private final int offset;
public InsnCodeOffset(int offset) {
this.offset = offset;
}
@Override
public int getOffset() {
return offset;
}
@Override
public String toString() {
return "offset=" + offset;
}
}
@@ -0,0 +1,57 @@
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() + '}';
}
}
@@ -0,0 +1,98 @@
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 + '}';
}
}
@@ -0,0 +1,78 @@
package jadx.api.data.impl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.data.ICodeComment;
import jadx.api.data.IJavaCodeRef;
import jadx.api.data.IJavaNodeRef;
public class JadxCodeComment implements ICodeComment {
private IJavaNodeRef nodeRef;
@Nullable
private IJavaCodeRef codeRef;
private String comment;
public JadxCodeComment(IJavaNodeRef nodeRef, String comment) {
this(nodeRef, null, comment);
}
public JadxCodeComment(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String comment) {
this.nodeRef = nodeRef;
this.codeRef = codeRef;
this.comment = comment;
}
public JadxCodeComment() {
// for json deserialization
}
@Override
public IJavaNodeRef getNodeRef() {
return nodeRef;
}
public void setNodeRef(IJavaNodeRef nodeRef) {
this.nodeRef = nodeRef;
}
@Nullable
@Override
public IJavaCodeRef getCodeRef() {
return codeRef;
}
public void setCodeRef(@Nullable IJavaCodeRef codeRef) {
this.codeRef = codeRef;
}
@Override
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
@Override
public int compareTo(@NotNull ICodeComment other) {
int cmpNodeRef = this.getNodeRef().compareTo(other.getNodeRef());
if (cmpNodeRef != 0) {
return cmpNodeRef;
}
if (this.getCodeRef() != null && other.getCodeRef() != null) {
return this.getCodeRef().compareTo(other.getCodeRef());
}
return this.getComment().compareTo(other.getComment());
}
@Override
public String toString() {
return "JadxCodeComment{" + nodeRef
+ ", ref=" + codeRef
+ ", comment='" + comment + '\''
+ '}';
}
}
@@ -0,0 +1,31 @@
package jadx.api.data.impl;
import java.util.Collections;
import java.util.List;
import jadx.api.data.ICodeComment;
import jadx.api.data.ICodeData;
import jadx.api.data.ICodeRename;
public class JadxCodeData implements ICodeData {
private List<ICodeComment> comments = Collections.emptyList();
private List<ICodeRename> renames = Collections.emptyList();
@Override
public List<ICodeComment> getComments() {
return comments;
}
public void setComments(List<ICodeComment> comments) {
this.comments = comments;
}
@Override
public List<ICodeRename> getRenames() {
return renames;
}
public void setRenames(List<ICodeRename> renames) {
this.renames = renames;
}
}
@@ -0,0 +1,88 @@
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;
public class JadxCodeRef implements IJavaCodeRef {
public static JadxCodeRef forInsn(int offset) {
return new JadxCodeRef(CodeRefType.INSN, offset);
}
public static JadxCodeRef forMthArg(int argIndex) {
return new JadxCodeRef(CodeRefType.MTH_ARG, argIndex);
}
public static JadxCodeRef forVar(int regNum, int ssaVersion) {
return new JadxCodeRef(CodeRefType.VAR, regNum << 16 | ssaVersion);
}
public static JadxCodeRef forVar(JavaVariable javaVariable) {
return forVar(javaVariable.getReg(), javaVariable.getSsa());
}
public static JadxCodeRef forVar(VarRef varRef) {
return forVar(varRef.getReg(), varRef.getSsa());
}
public static JadxCodeRef forCatch(int handlerOffset) {
return new JadxCodeRef(CodeRefType.CATCH, handlerOffset);
}
private CodeRefType attachType;
private int index;
public JadxCodeRef(CodeRefType attachType, int index) {
this.attachType = attachType;
this.index = index;
}
public JadxCodeRef() {
// used for json serialization
}
public CodeRefType getAttachType() {
return attachType;
}
public void setAttachType(CodeRefType attachType) {
this.attachType = attachType;
}
@Override
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof JadxCodeRef)) {
return false;
}
JadxCodeRef other = (JadxCodeRef) o;
return getIndex() == other.getIndex()
&& getAttachType() == other.getAttachType();
}
@Override
public int hashCode() {
return 31 * getAttachType().hashCode() + getIndex();
}
@Override
public String toString() {
return "JadxCodeRef{"
+ "attachType=" + attachType
+ ", index=" + index
+ '}';
}
}
@@ -0,0 +1,88 @@
package jadx.api.data.impl;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.data.ICodeRename;
import jadx.api.data.IJavaCodeRef;
import jadx.api.data.IJavaNodeRef;
public class JadxCodeRename implements ICodeRename {
private IJavaNodeRef nodeRef;
@Nullable
private IJavaCodeRef codeRef;
private String newName;
public JadxCodeRename(IJavaNodeRef nodeRef, String newName) {
this(nodeRef, null, newName);
}
public JadxCodeRename(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String newName) {
this.nodeRef = nodeRef;
this.codeRef = codeRef;
this.newName = newName;
}
public JadxCodeRename() {
// used in json serialization
}
@Override
public IJavaNodeRef getNodeRef() {
return nodeRef;
}
public void setNodeRef(IJavaNodeRef nodeRef) {
this.nodeRef = nodeRef;
}
@Override
public IJavaCodeRef getCodeRef() {
return codeRef;
}
public void setCodeRef(IJavaCodeRef codeRef) {
this.codeRef = codeRef;
}
@Override
public String getNewName() {
return newName;
}
public void setNewName(String newName) {
this.newName = newName;
}
@Override
public int compareTo(@NotNull ICodeRename other) {
int cmpNodeRef = this.getNodeRef().compareTo(other.getNodeRef());
if (cmpNodeRef != 0) {
return cmpNodeRef;
}
if (this.getCodeRef() != null && other.getCodeRef() != null) {
return this.getCodeRef().compareTo(other.getCodeRef());
}
return this.getNewName().compareTo(other.getNewName());
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ICodeRename)) {
return false;
}
ICodeRename other = (ICodeRename) o;
return getNodeRef().equals(other.getNodeRef())
&& Objects.equals(getCodeRef(), other.getCodeRef());
}
@Override
public int hashCode() {
return 31 * getNodeRef().hashCode() + Objects.hashCode(getCodeRef());
}
}
@@ -0,0 +1,144 @@
package jadx.api.data.impl;
import java.util.Comparator;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.api.data.IJavaNodeRef;
public class JadxNodeRef implements IJavaNodeRef {
@Nullable
public static JadxNodeRef forJavaNode(JavaNode javaNode) {
if (javaNode instanceof JavaClass) {
return forCls((JavaClass) javaNode);
}
if (javaNode instanceof JavaMethod) {
return forMth((JavaMethod) javaNode);
}
if (javaNode instanceof JavaField) {
return forFld((JavaField) javaNode);
}
return null;
}
public static JadxNodeRef forCls(JavaClass cls) {
return new JadxNodeRef(RefType.CLASS, getClassRefStr(cls), null);
}
public static JadxNodeRef forCls(String clsFullName) {
return new JadxNodeRef(RefType.CLASS, clsFullName, null);
}
public static JadxNodeRef forMth(JavaMethod mth) {
return new JadxNodeRef(RefType.METHOD,
getClassRefStr(mth.getDeclaringClass()),
mth.getMethodNode().getMethodInfo().getShortId());
}
public static JadxNodeRef forFld(JavaField fld) {
return new JadxNodeRef(RefType.FIELD,
getClassRefStr(fld.getDeclaringClass()),
fld.getFieldNode().getFieldInfo().getShortId());
}
public static JadxNodeRef forPkg(String pkgFullName) {
return new JadxNodeRef(RefType.PKG, pkgFullName, "");
}
private static String getClassRefStr(JavaClass cls) {
return cls.getClassNode().getClassInfo().getRawName();
}
private RefType refType;
private String declClass;
@Nullable
private String shortId;
public JadxNodeRef(RefType refType, String declClass, @Nullable String shortId) {
this.refType = refType;
this.declClass = declClass;
this.shortId = shortId;
}
public JadxNodeRef() {
// for json deserialization
}
@Override
public RefType getType() {
return refType;
}
public void setRefType(RefType refType) {
this.refType = refType;
}
@Override
public String getDeclaringClass() {
return declClass;
}
public void setDeclClass(String declClass) {
this.declClass = declClass;
}
@Nullable
@Override
public String getShortId() {
return shortId;
}
public void setShortId(@Nullable String shortId) {
this.shortId = shortId;
}
private static final Comparator<IJavaNodeRef> COMPARATOR = Comparator
.comparing(IJavaNodeRef::getType)
.thenComparing(IJavaNodeRef::getDeclaringClass)
.thenComparing(IJavaNodeRef::getShortId);
@Override
public int compareTo(@NotNull IJavaNodeRef other) {
return COMPARATOR.compare(this, other);
}
@Override
public int hashCode() {
return Objects.hash(refType, declClass, shortId);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof JadxNodeRef)) {
return false;
}
JadxNodeRef that = (JadxNodeRef) o;
return refType == that.refType
&& Objects.equals(declClass, that.declClass)
&& Objects.equals(shortId, that.shortId);
}
@Override
public String toString() {
switch (refType) {
case CLASS:
case PKG:
return declClass;
case FIELD:
case METHOD:
return declClass + "->" + shortId;
default:
return "unknown node ref type";
}
}
}
@@ -0,0 +1,43 @@
package jadx.api.impl;
import java.util.Map;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
public class AnnotatedCodeInfo implements ICodeInfo {
private final String code;
private final Map<Integer, Integer> lineMapping;
private final Map<CodePosition, Object> annotations;
public AnnotatedCodeInfo(ICodeInfo codeInfo) {
this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations());
}
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<CodePosition, Object> annotations) {
this.code = code;
this.lineMapping = lineMapping;
this.annotations = annotations;
}
@Override
public String getCodeStr() {
return code;
}
@Override
public Map<Integer, Integer> getLineMapping() {
return lineMapping;
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return annotations;
}
@Override
public String toString() {
return code;
}
}
@@ -0,0 +1,192 @@
package jadx.api.impl;
import java.util.Collections;
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.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, Integer> lineMap = Collections.emptyMap();
public AnnotatedCodeWriter() {
}
public AnnotatedCodeWriter(JadxArgs args) {
super(args);
}
@Override
public boolean isMetadataSupported() {
return true;
}
@Override
public AnnotatedCodeWriter addMultiLine(String str) {
if (str.contains(NL)) {
buf.append(str.replace(NL, NL + indentStr));
line += StringUtils.countMatches(str, NL);
offset = 0;
} else {
buf.append(str);
}
return this;
}
@Override
public AnnotatedCodeWriter add(String str) {
buf.append(str);
offset += str.length();
return this;
}
@Override
public AnnotatedCodeWriter add(char c) {
buf.append(c);
offset++;
return this;
}
@Override
public ICodeWriter add(ICodeWriter cw) {
if ((!(cw instanceof AnnotatedCodeWriter))) {
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, Integer> entry : code.lineMap.entrySet()) {
attachSourceLine(line + entry.getKey(), entry.getValue());
}
line += code.line;
offset = code.offset;
buf.append(code.buf);
return this;
}
@Override
protected void addLine() {
buf.append(NL);
line++;
offset = 0;
}
@Override
protected AnnotatedCodeWriter addLineIndent() {
buf.append(indentStr);
offset += indentStr.length();
return this;
}
@Override
public int getLine() {
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 void attachDefinition(ILineAttributeNode obj) {
if (obj == null) {
return;
}
attachAnnotation(obj);
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset, getLength()));
}
@Override
public void attachAnnotation(Object obj) {
if (obj == null) {
return;
}
attachAnnotation(obj, new CodePosition(line, offset + 1, getLength()));
}
@Override
public void attachLineAnnotation(Object obj) {
if (obj == null) {
return;
}
attachAnnotation(obj, new CodePosition(line, 0, getLength() - offset));
}
private void attachAnnotation(Object obj, CodePosition pos) {
if (annotations.isEmpty()) {
annotations = new HashMap<>();
}
annotations.put(pos, obj);
}
@Override
public void attachSourceLine(int sourceLine) {
if (sourceLine == 0) {
return;
}
attachSourceLine(line, sourceLine);
}
private void attachSourceLine(int decompiledLine, int sourceLine) {
if (lineMap.isEmpty()) {
lineMap = new TreeMap<>();
}
lineMap.put(decompiledLine, sourceLine);
}
@Override
public ICodeInfo finish() {
removeFirstEmptyLine();
processDefinitionAnnotations();
String code = buf.toString();
buf = null;
return new AnnotatedCodeInfo(code, lineMap, annotations);
}
@Override
public Map<CodePosition, Object> 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;
}
return false;
});
}
}
}
@@ -29,6 +29,6 @@ public class InMemoryCodeCache implements ICodeCache {
@Override @Override
public String toString() { public String toString() {
return "InMemoryCodeCache"; return "InMemoryCodeCache: size=" + storage.size();
} }
} }
@@ -9,21 +9,9 @@ import jadx.api.ICodeInfo;
public class SimpleCodeInfo implements ICodeInfo { public class SimpleCodeInfo implements ICodeInfo {
private final String code; private final String code;
private final Map<Integer, Integer> lineMapping;
private final Map<CodePosition, Object> annotations;
public SimpleCodeInfo(String code) { public SimpleCodeInfo(String code) {
this(code, Collections.emptyMap(), Collections.emptyMap());
}
public SimpleCodeInfo(ICodeInfo codeInfo) {
this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations());
}
public SimpleCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<CodePosition, Object> annotations) {
this.code = code; this.code = code;
this.lineMapping = lineMapping;
this.annotations = annotations;
} }
@Override @Override
@@ -33,12 +21,12 @@ public class SimpleCodeInfo implements ICodeInfo {
@Override @Override
public Map<Integer, Integer> getLineMapping() { public Map<Integer, Integer> getLineMapping() {
return lineMapping; return Collections.emptyMap();
} }
@Override @Override
public Map<CodePosition, Object> getAnnotations() { public Map<CodePosition, Object> getAnnotations() {
return annotations; return Collections.emptyMap();
} }
@Override @Override
@@ -0,0 +1,255 @@
package jadx.api.impl;
import java.util.Collections;
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.core.utils.Utils;
/**
* CodeWriter implementation without meta information support (only strings builder)
*/
public class SimpleCodeWriter implements ICodeWriter {
private static final Logger LOG = LoggerFactory.getLogger(SimpleCodeWriter.class);
private static final String[] INDENT_CACHE = {
"",
INDENT_STR,
INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
};
protected StringBuilder buf = new StringBuilder();
protected String indentStr = "";
protected int indent = 0;
private final boolean insertLineNumbers;
public SimpleCodeWriter() {
this.insertLineNumbers = false;
}
public SimpleCodeWriter(JadxArgs args) {
this.insertLineNumbers = args.isInsertDebugLines();
if (insertLineNumbers) {
incIndent(3);
add(indentStr);
}
}
@Override
public boolean isMetadataSupported() {
return false;
}
@Override
public SimpleCodeWriter startLine() {
addLine();
addLineIndent();
return this;
}
@Override
public SimpleCodeWriter startLine(char c) {
startLine();
add(c);
return this;
}
@Override
public SimpleCodeWriter startLine(String str) {
startLine();
add(str);
return this;
}
@Override
public SimpleCodeWriter startLineWithNum(int sourceLine) {
if (sourceLine == 0) {
startLine();
return this;
}
if (this.insertLineNumbers) {
newLine();
attachSourceLine(sourceLine);
int start = getLength();
add("/* ").add(Integer.toString(sourceLine)).add(" */ ");
int len = getLength() - start;
if (indentStr.length() > len) {
add(indentStr.substring(len));
}
} else {
startLine();
attachSourceLine(sourceLine);
}
return this;
}
@Override
public SimpleCodeWriter addMultiLine(String str) {
if (str.contains(NL)) {
buf.append(str.replace(NL, NL + indentStr));
} else {
buf.append(str);
}
return this;
}
@Override
public SimpleCodeWriter add(String str) {
buf.append(str);
return this;
}
@Override
public SimpleCodeWriter add(char c) {
buf.append(c);
return this;
}
@Override
public ICodeWriter add(ICodeWriter cw) {
buf.append(cw.getCodeStr());
return this;
}
@Override
public SimpleCodeWriter newLine() {
addLine();
return this;
}
@Override
public SimpleCodeWriter addIndent() {
add(INDENT_STR);
return this;
}
protected void addLine() {
buf.append(NL);
}
protected SimpleCodeWriter addLineIndent() {
buf.append(indentStr);
return this;
}
private void updateIndent() {
int curIndent = indent;
if (curIndent < INDENT_CACHE.length) {
this.indentStr = INDENT_CACHE[curIndent];
} else {
this.indentStr = Utils.strRepeat(INDENT_STR, curIndent);
}
}
@Override
public void incIndent() {
incIndent(1);
}
@Override
public void decIndent() {
decIndent(1);
}
private void incIndent(int c) {
this.indent += c;
updateIndent();
}
private void decIndent(int c) {
this.indent -= c;
if (this.indent < 0) {
LOG.warn("Indent < 0");
this.indent = 0;
}
updateIndent();
}
@Override
public int getIndent() {
return indent;
}
@Override
public void setIndent(int indent) {
this.indent = indent;
updateIndent();
}
@Override
public int getLine() {
return 0;
}
@Override
public void attachDefinition(ILineAttributeNode obj) {
// no op
}
@Override
public void attachAnnotation(Object obj) {
// no op
}
@Override
public void attachLineAnnotation(Object obj) {
// no op
}
@Override
public void attachSourceLine(int sourceLine) {
// no op
}
@Override
public ICodeInfo finish() {
removeFirstEmptyLine();
String code = buf.toString();
buf = null;
return new SimpleCodeInfo(code);
}
protected void removeFirstEmptyLine() {
int len = NL.length();
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
buf.delete(0, len);
}
}
@Override
public int getLength() {
return buf.length();
}
@Override
public StringBuilder getRawBuf() {
return buf;
}
@Override
public Map<CodePosition, Object> getRawAnnotations() {
return Collections.emptyMap();
}
@Override
public String getCodeStr() {
removeFirstEmptyLine();
return buf.toString();
}
@Override
public String toString() {
return getCodeStr();
}
}
@@ -6,6 +6,7 @@ public class Consts {
public static final boolean DEBUG_USAGE = false; public static final boolean DEBUG_USAGE = false;
public static final boolean DEBUG_TYPE_INFERENCE = false; public static final boolean DEBUG_TYPE_INFERENCE = false;
public static final boolean DEBUG_OVERLOADED_CASTS = false; public static final boolean DEBUG_OVERLOADED_CASTS = false;
public static final boolean DEBUG_EXC_HANDLERS = false;
public static final String CLASS_OBJECT = "java.lang.Object"; public static final String CLASS_OBJECT = "java.lang.Object";
public static final String CLASS_STRING = "java.lang.String"; public static final String CLASS_STRING = "java.lang.String";
@@ -15,12 +16,7 @@ public class Consts {
public static final String CLASS_ENUM = "java.lang.Enum"; public static final String CLASS_ENUM = "java.lang.Enum";
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder"; public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
public static final String OVERRIDE_ANNOTATION = "Ljava/lang/Override;";
public static final String DALVIK_ANNOTATION_PKG = "Ldalvik/annotation/";
public static final String DALVIK_SIGNATURE = "Ldalvik/annotation/Signature;";
public static final String DALVIK_INNER_CLASS = "Ldalvik/annotation/InnerClass;";
public static final String DALVIK_THROWS = "Ldalvik/annotation/Throws;";
public static final String DALVIK_ANNOTATION_DEFAULT = "Ldalvik/annotation/AnnotationDefault;";
public static final String DEFAULT_PACKAGE_NAME = "defpackage"; public static final String DEFAULT_PACKAGE_NAME = "defpackage";
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass"; public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
+49 -21
View File
@@ -10,9 +10,13 @@ import java.util.jar.Manifest;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.core.dex.visitors.AnonymousClassVisitor;
import jadx.core.dex.visitors.AttachCommentsVisitor;
import jadx.core.dex.visitors.AttachMethodDetails; import jadx.core.dex.visitors.AttachMethodDetails;
import jadx.core.dex.visitors.AttachTryCatchVisitor; import jadx.core.dex.visitors.AttachTryCatchVisitor;
import jadx.core.dex.visitors.CheckCode;
import jadx.core.dex.visitors.ClassModifier; import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.ConstInlineVisitor; import jadx.core.dex.visitors.ConstInlineVisitor;
import jadx.core.dex.visitors.ConstructorVisitor; import jadx.core.dex.visitors.ConstructorVisitor;
@@ -26,7 +30,6 @@ import jadx.core.dex.visitors.GenericTypesVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.InitCodeVariables; import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.InlineMethods; import jadx.core.dex.visitors.InlineMethods;
import jadx.core.dex.visitors.MarkFinallyVisitor;
import jadx.core.dex.visitors.MarkMethodsForInline; import jadx.core.dex.visitors.MarkMethodsForInline;
import jadx.core.dex.visitors.MethodInvokeVisitor; import jadx.core.dex.visitors.MethodInvokeVisitor;
import jadx.core.dex.visitors.ModVisitor; import jadx.core.dex.visitors.ModVisitor;
@@ -35,17 +38,17 @@ import jadx.core.dex.visitors.OverrideMethodVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen; import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ProcessAnonymous; import jadx.core.dex.visitors.ProcessAnonymous;
import jadx.core.dex.visitors.ProcessInstructionsVisitor; import jadx.core.dex.visitors.ProcessInstructionsVisitor;
import jadx.core.dex.visitors.ProcessMethodsForInline;
import jadx.core.dex.visitors.ReSugarCode; import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.RenameVisitor;
import jadx.core.dex.visitors.ShadowFieldVisitor; import jadx.core.dex.visitors.ShadowFieldVisitor;
import jadx.core.dex.visitors.SignatureProcessor; import jadx.core.dex.visitors.SignatureProcessor;
import jadx.core.dex.visitors.SimplifyVisitor; import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler; import jadx.core.dex.visitors.blocks.BlockProcessor;
import jadx.core.dex.visitors.blocksmaker.BlockFinish; import jadx.core.dex.visitors.blocks.BlockSplitter;
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor; import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor; import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
import jadx.core.dex.visitors.regions.CheckRegions; import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions; import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor; import jadx.core.dex.visitors.regions.IfRegionVisitor;
@@ -53,6 +56,8 @@ import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.RegionMakerVisitor; import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.regions.ReturnVisitor; import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.regions.variables.ProcessVariables; import jadx.core.dex.visitors.regions.variables.ProcessVariables;
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.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
@@ -71,8 +76,9 @@ public class Jadx {
} }
public static List<IDexTreeVisitor> getFallbackPassesList() { public static List<IDexTreeVisitor> getFallbackPassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>(3); List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new AttachTryCatchVisitor()); passes.add(new AttachTryCatchVisitor());
passes.add(new AttachCommentsVisitor());
passes.add(new ProcessInstructionsVisitor()); passes.add(new ProcessInstructionsVisitor());
passes.add(new FallbackModeVisitor()); passes.add(new FallbackModeVisitor());
return passes; return passes;
@@ -81,8 +87,11 @@ public class Jadx {
public static List<IDexTreeVisitor> getPreDecompilePassesList() { public static List<IDexTreeVisitor> getPreDecompilePassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>(); List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new SignatureProcessor()); passes.add(new SignatureProcessor());
passes.add(new OverrideMethodVisitor());
passes.add(new RenameVisitor()); passes.add(new RenameVisitor());
passes.add(new UsageInfoVisitor()); passes.add(new UsageInfoVisitor());
passes.add(new ProcessAnonymous());
passes.add(new ProcessMethodsForInline());
return passes; return passes;
} }
@@ -90,40 +99,50 @@ public class Jadx {
if (args.isFallbackMode()) { if (args.isFallbackMode()) {
return getFallbackPassesList(); return getFallbackPassesList();
} }
List<IDexTreeVisitor> passes = new ArrayList<>(); List<IDexTreeVisitor> passes = new ArrayList<>();
// instructions IR
passes.add(new CheckCode());
if (args.isDebugInfo()) { if (args.isDebugInfo()) {
passes.add(new DebugInfoAttachVisitor()); passes.add(new DebugInfoAttachVisitor());
} }
passes.add(new AttachTryCatchVisitor()); passes.add(new AttachTryCatchVisitor());
if (args.getCommentsLevel() != CommentsLevel.NONE) {
passes.add(new AttachCommentsVisitor());
}
passes.add(new AttachMethodDetails());
passes.add(new ProcessInstructionsVisitor()); passes.add(new ProcessInstructionsVisitor());
// blocks IR
passes.add(new BlockSplitter()); passes.add(new BlockSplitter());
passes.add(new BlockProcessor());
if (args.isRawCFGOutput()) { if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw()); passes.add(DotGraphVisitor.dumpRaw());
} }
passes.add(new BlockProcessor());
passes.add(new BlockExceptionHandler());
passes.add(new BlockFinish());
passes.add(new AttachMethodDetails());
passes.add(new OverrideMethodVisitor());
passes.add(new SSATransform()); passes.add(new SSATransform());
passes.add(new MoveInlineVisitor()); passes.add(new MoveInlineVisitor());
passes.add(new ConstructorVisitor()); passes.add(new ConstructorVisitor());
passes.add(new InitCodeVariables()); passes.add(new InitCodeVariables());
passes.add(new MarkFinallyVisitor()); if (args.isExtractFinally()) {
passes.add(new MarkFinallyVisitor());
}
passes.add(new ConstInlineVisitor()); passes.add(new ConstInlineVisitor());
passes.add(new TypeInferenceVisitor()); passes.add(new TypeInferenceVisitor());
if (args.isDebugInfo()) { if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor()); passes.add(new DebugInfoApplyVisitor());
} }
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
passes.add(new InlineMethods()); passes.add(new ProcessKotlinInternals());
}
passes.add(new CodeRenameVisitor());
if (args.isInlineMethods()) {
passes.add(new InlineMethods());
}
passes.add(new GenericTypesVisitor()); passes.add(new GenericTypesVisitor());
passes.add(new ShadowFieldVisitor()); passes.add(new ShadowFieldVisitor());
passes.add(new DeboxingVisitor()); passes.add(new DeboxingVisitor());
passes.add(new AnonymousClassVisitor());
passes.add(new ModVisitor()); passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor()); passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode()); passes.add(new ReSugarCode());
@@ -131,6 +150,7 @@ public class Jadx {
passes.add(DotGraphVisitor.dump()); passes.add(DotGraphVisitor.dump());
} }
// regions IR
passes.add(new RegionMakerVisitor()); passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor()); passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor()); passes.add(new ReturnVisitor());
@@ -144,12 +164,12 @@ public class Jadx {
passes.add(new EnumVisitor()); passes.add(new EnumVisitor());
passes.add(new ExtractFieldInit()); passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers()); passes.add(new FixAccessModifiers());
passes.add(new ProcessAnonymous());
passes.add(new ClassModifier()); passes.add(new ClassModifier());
passes.add(new LoopRegionVisitor()); passes.add(new LoopRegionVisitor());
passes.add(new MarkMethodsForInline()); if (args.isInlineMethods()) {
passes.add(new MarkMethodsForInline());
}
passes.add(new ProcessVariables()); passes.add(new ProcessVariables());
passes.add(new PrepareForCodeGen()); passes.add(new PrepareForCodeGen());
if (args.isCfgOutput()) { if (args.isCfgOutput()) {
@@ -158,7 +178,14 @@ public class Jadx {
return passes; return passes;
} }
public static final String VERSION_DEV = "dev";
private static String version;
public static String getVersion() { public static String getVersion() {
if (version != null) {
return version;
}
try { try {
ClassLoader classLoader = Jadx.class.getClassLoader(); ClassLoader classLoader = Jadx.class.getClassLoader();
if (classLoader != null) { if (classLoader != null) {
@@ -168,6 +195,7 @@ public class Jadx {
Manifest manifest = new Manifest(is); Manifest manifest = new Manifest(is);
String ver = manifest.getMainAttributes().getValue("jadx-version"); String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null) { if (ver != null) {
version = ver;
return ver; return ver;
} }
} }
@@ -176,6 +204,6 @@ public class Jadx {
} catch (Exception e) { } catch (Exception e) {
LOG.error("Can't get manifest file", e); LOG.error("Can't get manifest file", e);
} }
return "dev"; return VERSION_DEV;
} }
} }
@@ -33,15 +33,18 @@ public final class ProcessClass {
try { try {
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) { if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
cls.remove(AFlag.CLASS_DEEP_RELOAD); cls.remove(AFlag.CLASS_DEEP_RELOAD);
cls.unload();
cls.deepUnload(); cls.deepUnload();
cls.add(AFlag.CLASS_UNLOADED);
}
if (cls.contains(AFlag.CLASS_UNLOADED)) {
cls.root().runPreDecompileStageForClass(cls); cls.root().runPreDecompileStageForClass(cls);
cls.remove(AFlag.CLASS_UNLOADED);
}
if (cls.getState() == GENERATED_AND_UNLOADED) {
// force loading code again
cls.setState(NOT_LOADED);
} }
if (codegen) { if (codegen) {
if (cls.getState() == GENERATED_AND_UNLOADED) {
// allow to run code generation again
cls.setState(NOT_LOADED);
}
cls.setLoadStage(LoadStage.CODEGEN_STAGE); cls.setLoadStage(LoadStage.CODEGEN_STAGE);
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) { if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE); cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
@@ -68,10 +71,14 @@ public final class ProcessClass {
} }
return code; return code;
} }
return null;
} catch (Throwable e) { } catch (Throwable e) {
if (codegen) {
throw e;
}
cls.addError("Class process error: " + e.getClass().getSimpleName(), e); cls.addError("Class process error: " + e.getClass().getSimpleName(), e);
return null;
} }
return null;
} }
} }
@@ -85,6 +92,12 @@ public final class ProcessClass {
for (ClassNode depCls : cls.getDependencies()) { for (ClassNode depCls : cls.getDependencies()) {
process(depCls, false); process(depCls, false);
} }
if (!cls.getCodegenDeps().isEmpty()) {
process(cls, false);
for (ClassNode codegenDep : cls.getCodegenDeps()) {
process(codegenDep, false);
}
}
ICodeInfo code = process(cls, true); ICodeInfo code = process(cls, true);
if (code == null) { if (code == null) {
throw new JadxRuntimeException("Codegen failed"); throw new JadxRuntimeException("Codegen failed");
@@ -39,8 +39,6 @@ import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
import static jadx.core.utils.Utils.notEmpty;
/** /**
* Classes list for import into classpath graph * Classes list for import into classpath graph
*/ */
@@ -49,7 +47,7 @@ public class ClsSet {
private static final String CLST_EXTENSION = ".jcst"; private static final String CLST_EXTENSION = ".jcst";
private static final String CLST_FILENAME = "core" + CLST_EXTENSION; private static final String CLST_FILENAME = "core" + CLST_EXTENSION;
private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/'); private static final String CLST_PATH = "/clst/" + CLST_FILENAME;
private static final String JADX_CLS_SET_HEADER = "jadx-cst"; private static final String JADX_CLS_SET_HEADER = "jadx-cst";
private static final int VERSION = 3; private static final int VERSION = 3;
@@ -78,9 +76,9 @@ public class ClsSet {
public void loadFromClstFile() throws IOException, DecodeException { public void loadFromClstFile() throws IOException, DecodeException {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) { try (InputStream input = ClsSet.class.getResourceAsStream(CLST_PATH)) {
if (input == null) { if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME); throw new JadxRuntimeException("Can't load classpath file: " + CLST_PATH);
} }
load(input); load(input);
} }
@@ -131,25 +129,13 @@ public class ClsSet {
private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) { private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) {
AccessInfo accessFlags = mth.getAccessFlags(); AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isPrivate()) { if (accessFlags.isPrivate() || accessFlags.isSynthetic() || accessFlags.isBridge()) {
return; return;
} }
ArgType genericRetType = mth.getReturnType(); ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(), mth.getArgTypes(),
boolean varArgs = accessFlags.isVarArgs(); mth.getReturnType(), mth.getTypeParameters(),
List<ArgType> throwList = mth.getThrows(); mth.getThrows(), accessFlags.rawValue());
List<ArgType> typeParameters = mth.getTypeParameters(); methods.add(clspMethod);
// add only methods with additional info
if (varArgs
|| notEmpty(throwList)
|| notEmpty(typeParameters)
|| genericRetType.containsGeneric()
|| mth.containsGenericArgs()
|| mth.isArgsOverloaded()) {
ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(),
mth.getArgTypes(), genericRetType,
typeParameters, varArgs, throwList);
methods.add(clspMethod);
}
} }
public static ArgType[] makeParentsArray(ClassNode cls) { public static ArgType[] makeParentsArray(ClassNode cls) {
@@ -197,7 +183,7 @@ public class ClsSet {
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path)); try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) { ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME; String clst = CLST_PATH;
boolean clstReplaced = false; boolean clstReplaced = false;
ZipEntry entry = in.getNextEntry(); ZipEntry entry = in.getNextEntry();
while (entry != null) { while (entry != null) {
@@ -245,7 +231,7 @@ public class ClsSet {
} }
} }
int methodsCount = Stream.of(classes).mapToInt(c -> c.getMethodsMap().size()).sum(); int methodsCount = Stream.of(classes).mapToInt(c -> c.getMethodsMap().size()).sum();
LOG.info("Classes: {}, methods: {}, file size: {}B", classes.length, methodsCount, out.size()); LOG.info("Classes: {}, methods: {}, file size: {} bytes", classes.length, methodsCount, out.size());
} }
private static void writeMethod(DataOutputStream out, ClspMethod method, Map<String, ClspClass> names) throws IOException { private static void writeMethod(DataOutputStream out, ClspMethod method, Map<String, ClspClass> names) throws IOException {
@@ -257,7 +243,7 @@ public class ClsSet {
writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names); writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names);
writeArgType(out, method.getReturnType(), names); writeArgType(out, method.getReturnType(), names);
writeArgTypesList(out, method.getTypeParameters(), names); writeArgTypesList(out, method.getTypeParameters(), names);
out.writeBoolean(method.isVarArg()); out.writeInt(method.getRawAccessFlags());
writeArgTypesList(out, method.getThrows(), names); writeArgTypesList(out, method.getThrows(), names);
} }
@@ -392,12 +378,12 @@ public class ClsSet {
genericRetType = retType; genericRetType = retType;
} }
List<ArgType> typeParameters = readArgTypesList(in); List<ArgType> typeParameters = readArgTypesList(in);
boolean varArgs = in.readBoolean(); int accFlags = in.readInt();
List<ArgType> throwList = readArgTypesList(in); List<ArgType> throwList = readArgTypesList(in);
MethodInfo methodInfo = MethodInfo.fromDetails(root, clsInfo, name, argTypes, retType); MethodInfo methodInfo = MethodInfo.fromDetails(root, clsInfo, name, argTypes, retType);
return new ClspMethod(methodInfo, return new ClspMethod(methodInfo,
genericArgTypes, genericRetType, genericArgTypes, genericRetType,
typeParameters, varArgs, throwList); typeParameters, throwList, accFlags);
} }
private List<ArgType> readArgTypesList(DataInputStream in) throws IOException { private List<ArgType> readArgTypesList(DataInputStream in) throws IOException {
@@ -8,9 +8,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.WeakHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -30,8 +28,9 @@ public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class); private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
private final RootNode root; private final RootNode root;
private final Map<String, Set<String>> superTypesCache = Collections.synchronizedMap(new WeakHashMap<>());
private Map<String, ClspClass> nameMap; private Map<String, ClspClass> nameMap;
private Map<String, Set<String>> superTypesCache;
private Map<String, List<String>> implementsCache;
private final Set<String> missingClasses = new HashSet<>(); private final Set<String> missingClasses = new HashSet<>();
@@ -63,6 +62,11 @@ public class ClspGraph {
} }
} }
public void initCache() {
fillSuperTypesCache();
fillImplementsCache();
}
public boolean isClsKnown(String fullName) { public boolean isClsKnown(String fullName) {
return nameMap.containsKey(fullName); return nameMap.containsKey(fullName);
} }
@@ -91,7 +95,7 @@ public class ClspGraph {
} }
} }
} }
// all other methods in known ClspClass are 'simple' // unknown method
return new SimpleMethodDetails(methodInfo); return new SimpleMethodDetails(methodInfo);
} }
@@ -116,13 +120,20 @@ public class ClspGraph {
} }
public List<String> getImplementations(String clsName) { public List<String> getImplementations(String clsName) {
List<String> list = new ArrayList<>(); List<String> list = implementsCache.get(clsName);
for (String cls : nameMap.keySet()) { return list == null ? Collections.emptyList() : list;
if (isImplements(cls, clsName)) { }
list.add(cls);
private void fillImplementsCache() {
Map<String, List<String>> map = new HashMap<>(nameMap.size());
List<String> classes = new ArrayList<>(nameMap.keySet());
Collections.sort(classes);
for (String cls : classes) {
for (String st : getSuperTypes(cls)) {
map.computeIfAbsent(st, v -> new ArrayList<>()).add(cls);
} }
} }
return list; implementsCache = map;
} }
public String getCommonAncestor(String clsName, String implClsName) { public String getCommonAncestor(String clsName, String implClsName) {
@@ -159,29 +170,26 @@ public class ClspGraph {
} }
public Set<String> getSuperTypes(String clsName) { public Set<String> getSuperTypes(String clsName) {
Set<String> fromCache = superTypesCache.get(clsName); Set<String> result = superTypesCache.get(clsName);
if (fromCache != null) { return result == null ? Collections.emptySet() : result;
return fromCache;
}
ClspClass cls = nameMap.get(clsName);
if (cls == null) {
missingClasses.add(clsName);
return Collections.emptySet();
}
Set<String> result = new HashSet<>();
addSuperTypes(cls, result);
return putInSuperTypesCache(clsName, result);
} }
@NotNull private void fillSuperTypesCache() {
private Set<String> putInSuperTypesCache(String clsName, Set<String> result) { Map<String, Set<String>> map = new HashMap<>(nameMap.size());
if (result.isEmpty()) { Set<String> tmpSet = new HashSet<>();
Set<String> empty = Collections.emptySet(); for (Map.Entry<String, ClspClass> entry : nameMap.entrySet()) {
superTypesCache.put(clsName, result); ClspClass cls = entry.getValue();
return empty; tmpSet.clear();
addSuperTypes(cls, tmpSet);
Set<String> result;
if (tmpSet.isEmpty()) {
result = Collections.emptySet();
} else {
result = new HashSet<>(tmpSet);
}
map.put(cls.getName(), result);
} }
superTypesCache.put(clsName, result); superTypesCache = map;
return result;
} }
private void addSuperTypes(ClspClass cls, Set<String> result) { private void addSuperTypes(ClspClass cls, Set<String> result) {
@@ -203,9 +211,7 @@ public class ClspGraph {
private ClspClass getClspClass(ArgType clsType) { private ClspClass getClspClass(ArgType clsType) {
ClspClass clspClass = nameMap.get(clsType.getObject()); ClspClass clspClass = nameMap.get(clsType.getObject());
if (clspClass == null) { if (clspClass == null) {
if (LOG.isDebugEnabled()) { missingClasses.add(clsType.getObject());
LOG.debug("External class not found: {}", clsType.getObject());
}
} }
return clspClass; return clspClass;
} }
@@ -5,6 +5,7 @@ import java.util.Objects;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.IMethodDetails;
@@ -20,18 +21,17 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
private final ArgType returnType; private final ArgType returnType;
private final List<ArgType> typeParameters; private final List<ArgType> typeParameters;
private final List<ArgType> throwList; private final List<ArgType> throwList;
private final boolean varArg; private final int accFlags;
public ClspMethod(MethodInfo methodInfo, public ClspMethod(MethodInfo methodInfo,
List<ArgType> argTypes, ArgType returnType, List<ArgType> argTypes, ArgType returnType,
List<ArgType> typeParameters, List<ArgType> typeParameters, List<ArgType> throwList, int accFlags) {
boolean varArgs, List<ArgType> throwList) {
this.methodInfo = methodInfo; this.methodInfo = methodInfo;
this.argTypes = argTypes; this.argTypes = argTypes;
this.returnType = returnType; this.returnType = returnType;
this.typeParameters = typeParameters; this.typeParameters = typeParameters;
this.throwList = throwList; this.throwList = throwList;
this.varArg = varArgs; this.accFlags = accFlags;
} }
@Override @Override
@@ -69,7 +69,12 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
@Override @Override
public boolean isVarArg() { public boolean isVarArg() {
return varArg; return (accFlags & AccessFlags.VARARGS) != 0;
}
@Override
public int getRawAccessFlags() {
return accFlags;
} }
@Override @Override
@@ -94,6 +99,11 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
return this.methodInfo.compareTo(other.methodInfo); return this.methodInfo.compareTo(other.methodInfo);
} }
@Override
public String toAttrString() {
return IMethodDetails.super.toAttrString() + " (c)";
}
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@@ -3,10 +3,15 @@ package jadx.core.clsp;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.IMethodDetails;
/**
* Method details build from MethodInfo.
* Note: some fields have unknown values.
*/
public class SimpleMethodDetails implements IMethodDetails { public class SimpleMethodDetails implements IMethodDetails {
private final MethodInfo methodInfo; private final MethodInfo methodInfo;
@@ -45,6 +50,16 @@ public class SimpleMethodDetails implements IMethodDetails {
return false; return false;
} }
@Override
public int getRawAccessFlags() {
return AccessFlags.PUBLIC;
}
@Override
public String toAttrString() {
return IMethodDetails.super.toAttrString() + " (s)";
}
@Override @Override
public String toString() { public String toString() {
return "SimpleMethodDetails{" + methodInfo + '}'; return "SimpleMethodDetails{" + methodInfo + '}';
@@ -7,14 +7,16 @@ import java.util.Map.Entry;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.IFieldData; import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.IFieldRef;
import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation; import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr;
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode; import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
@@ -34,24 +36,24 @@ public class AnnotationGen {
this.classGen = classGen; this.classGen = classGen;
} }
public void addForClass(CodeWriter code) { public void addForClass(ICodeWriter code) {
add(cls, code); add(cls, code);
} }
public void addForMethod(CodeWriter code, MethodNode mth) { public void addForMethod(ICodeWriter code, MethodNode mth) {
add(mth, code); add(mth, code);
} }
public void addForField(CodeWriter code, FieldNode field) { public void addForField(ICodeWriter code, FieldNode field) {
add(field, code); add(field, code);
} }
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) { public void addForParameter(ICodeWriter code, AnnotationMethodParamsAttr paramsAnnotations, int n) {
List<AnnotationsList> paramList = paramsAnnotations.getParamList(); List<AnnotationsAttr> paramList = paramsAnnotations.getParamList();
if (n >= paramList.size()) { if (n >= paramList.size()) {
return; return;
} }
AnnotationsList aList = paramList.get(n); AnnotationsAttr aList = paramList.get(n);
if (aList == null || aList.isEmpty()) { if (aList == null || aList.isEmpty()) {
return; return;
} }
@@ -61,21 +63,21 @@ public class AnnotationGen {
} }
} }
private void add(IAttributeNode node, CodeWriter code) { private void add(IAttributeNode node, ICodeWriter code) {
AnnotationsList aList = node.get(AType.ANNOTATION_LIST); AnnotationsAttr aList = node.get(JadxAttrType.ANNOTATION_LIST);
if (aList == null || aList.isEmpty()) { if (aList == null || aList.isEmpty()) {
return; return;
} }
for (IAnnotation a : aList.getAll()) { for (IAnnotation a : aList.getAll()) {
String aCls = a.getAnnotationClass(); String aCls = a.getAnnotationClass();
if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) { if (!aCls.equals(Consts.OVERRIDE_ANNOTATION)) {
code.startLine(); code.startLine();
formatAnnotation(code, a); formatAnnotation(code, a);
} }
} }
} }
private void formatAnnotation(CodeWriter code, IAnnotation a) { private void formatAnnotation(ICodeWriter code, IAnnotation a) {
code.add('@'); code.add('@');
ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass()); ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass());
if (annCls != null) { if (annCls != null) {
@@ -116,7 +118,7 @@ public class AnnotationGen {
return paramName; return paramName;
} }
public void addThrows(MethodNode mth, CodeWriter code) { public void addThrows(MethodNode mth, ICodeWriter code) {
List<ArgType> throwList = mth.getThrows(); List<ArgType> throwList = mth.getThrows();
if (!throwList.isEmpty()) { if (!throwList.isEmpty()) {
code.add(" throws "); code.add(" throws ");
@@ -130,20 +132,16 @@ public class AnnotationGen {
} }
} }
public EncodedValue getAnnotationDefaultValue(String name) { public EncodedValue getAnnotationDefaultValue(MethodNode mth) {
IAnnotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT); AnnotationDefaultAttr defaultAttr = mth.get(JadxAttrType.ANNOTATION_DEFAULT);
if (an != null) { if (defaultAttr == null) {
EncodedValue defValue = an.getDefaultValue(); return null;
if (defValue != null) {
IAnnotation defAnnotation = (IAnnotation) defValue.getValue();
return defAnnotation.getValues().get(name);
}
} }
return null; return defaultAttr.getValue();
} }
// TODO: refactor this boilerplate code // TODO: refactor this boilerplate code
public void encodeValue(RootNode root, CodeWriter code, EncodedValue encodedValue) { public void encodeValue(RootNode root, ICodeWriter code, EncodedValue encodedValue) {
if (encodedValue == null) { if (encodedValue == null) {
code.add("null"); code.add("null");
return; return;
@@ -187,9 +185,9 @@ public class AnnotationGen {
case ENCODED_ENUM: case ENCODED_ENUM:
case ENCODED_FIELD: case ENCODED_FIELD:
// must be a static field // must be a static field
if (value instanceof IFieldData) { if (value instanceof IFieldRef) {
FieldInfo field = FieldInfo.fromData(root, (IFieldData) value); FieldInfo fieldInfo = FieldInfo.fromRef(root, (IFieldRef) value);
InsnGen.makeStaticFieldAccess(code, field, classGen); InsnGen.makeStaticFieldAccess(code, fieldInfo, classGen);
} else if (value instanceof FieldInfo) { } else if (value instanceof FieldInfo) {
InsnGen.makeStaticFieldAccess(code, (FieldInfo) value, classGen); InsnGen.makeStaticFieldAccess(code, (FieldInfo) value, classGen);
} else { } else {
@@ -9,29 +9,33 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.api.CommentsLevel;
import jadx.api.ICodeInfo; import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.plugins.input.data.AccessFlags; import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedType; import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode; import jadx.core.dex.attributes.FieldInitInsnAttr;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.attributes.FieldInitAttr.InitType;
import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField; import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
@@ -40,8 +44,9 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils; import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.ErrorsCounter; import jadx.core.utils.EncodedValueUtils;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -55,10 +60,13 @@ public class ClassGen {
private final boolean showInconsistentCode; private final boolean showInconsistentCode;
private final Set<ClassInfo> imports = new HashSet<>(); private final Set<ClassInfo> imports = new HashSet<>();
private int clsDeclLine; private int clsDeclOffset;
private boolean bodyGenStarted; private boolean bodyGenStarted;
@Nullable
private NameGen outerNameGen;
public ClassGen(ClassNode cls, JadxArgs jadxArgs) { public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode()); this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
} }
@@ -82,10 +90,10 @@ public class ClassGen {
} }
public ICodeInfo makeClass() throws CodegenException { public ICodeInfo makeClass() throws CodegenException {
CodeWriter clsBody = new CodeWriter(); ICodeWriter clsBody = cls.root().makeCodeWriter();
addClassCode(clsBody); addClassCode(clsBody);
CodeWriter clsCode = new CodeWriter(); ICodeWriter clsCode = cls.root().makeCodeWriter();
if (!"".equals(cls.getPackage())) { if (!"".equals(cls.getPackage())) {
clsCode.add("package ").add(cls.getPackage()).add(';'); clsCode.add("package ").add(cls.getPackage()).add(';');
clsCode.newLine(); clsCode.newLine();
@@ -110,20 +118,20 @@ public class ClassGen {
return clsCode.finish(); return clsCode.finish();
} }
public void addClassCode(CodeWriter code) throws CodegenException { public void addClassCode(ICodeWriter code) throws CodegenException {
if (cls.contains(AFlag.DONT_GENERATE)) { if (cls.contains(AFlag.DONT_GENERATE)) {
return; return;
} }
if (Consts.DEBUG_USAGE) { if (Consts.DEBUG_USAGE) {
addClassUsageInfo(code, cls); addClassUsageInfo(code, cls);
} }
CodeGenUtils.addComments(code, cls); CodeGenUtils.addErrorsAndComments(code, cls);
insertDecompilationProblems(code, cls); CodeGenUtils.addSourceFileInfo(code, cls);
addClassDeclaration(code); addClassDeclaration(code);
addClassBody(code); addClassBody(code);
} }
public void addClassDeclaration(CodeWriter clsCode) { public void addClassDeclaration(ICodeWriter clsCode) {
AccessInfo af = cls.getAccessFlags(); AccessInfo af = cls.getAccessFlags();
if (af.isInterface()) { if (af.isInterface()) {
af = af.remove(AccessFlags.ABSTRACT) af = af.remove(AccessFlags.ABSTRACT)
@@ -141,9 +149,8 @@ public class ClassGen {
annotationGen.addForClass(clsCode); annotationGen.addForClass(clsCode);
insertRenameInfo(clsCode, cls); insertRenameInfo(clsCode, cls);
CodeGenUtils.addSourceFileInfo(clsCode, cls); CodeGenUtils.addInputFileInfo(clsCode, cls);
clsCode.startLineWithNum(cls.getSourceLine()); clsCode.startLineWithNum(cls.getSourceLine()).add(af.makeString(cls.checkCommentsLevel(CommentsLevel.INFO)));
clsCode.add(af.makeString());
if (af.isInterface()) { if (af.isInterface()) {
if (af.isAnnotation()) { if (af.isAnnotation()) {
clsCode.add('@'); clsCode.add('@');
@@ -188,7 +195,7 @@ public class ClassGen {
} }
} }
public boolean addGenericTypeParameters(CodeWriter code, List<ArgType> generics, boolean classDeclaration) { public boolean addGenericTypeParameters(ICodeWriter code, List<ArgType> generics, boolean classDeclaration) {
if (generics == null || generics.isEmpty()) { if (generics == null || generics.isEmpty()) {
return false; return false;
} }
@@ -229,7 +236,7 @@ public class ClassGen {
return true; return true;
} }
public void addClassBody(CodeWriter clsCode) throws CodegenException { public void addClassBody(ICodeWriter clsCode) throws CodegenException {
addClassBody(clsCode, false); addClassBody(clsCode, false);
} }
@@ -237,25 +244,24 @@ public class ClassGen {
* @param printClassName allows to print the original class name as comment (e.g. for inlined * @param printClassName allows to print the original class name as comment (e.g. for inlined
* classes) * classes)
*/ */
public void addClassBody(CodeWriter clsCode, boolean printClassName) throws CodegenException { public void addClassBody(ICodeWriter clsCode, boolean printClassName) throws CodegenException {
clsCode.add('{'); clsCode.add('{');
setBodyGenStarted(true); if (printClassName && cls.checkCommentsLevel(CommentsLevel.INFO)) {
clsDeclLine = clsCode.getLine(); clsCode.add(" // from class: " + cls.getClassInfo().getFullName());
clsCode.incIndent();
if (printClassName) {
clsCode.startLine();
clsCode.add("/* class " + cls.getFullName() + " */");
} }
setBodyGenStarted(true);
clsDeclOffset = clsCode.getLength();
clsCode.incIndent();
addFields(clsCode); addFields(clsCode);
addInnerClsAndMethods(clsCode); addInnerClsAndMethods(clsCode);
clsCode.decIndent(); clsCode.decIndent();
clsCode.startLine('}'); clsCode.startLine('}');
} }
private void addInnerClsAndMethods(CodeWriter clsCode) { private void addInnerClsAndMethods(ICodeWriter clsCode) {
Stream.of(cls.getInnerClasses(), cls.getMethods()) Stream.of(cls.getInnerClasses(), cls.getMethods())
.flatMap(Collection::stream) .flatMap(Collection::stream)
.filter(node -> !node.contains(AFlag.DONT_GENERATE)) .filter(node -> !node.contains(AFlag.DONT_GENERATE) || fallback)
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine)) .sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
.forEach(node -> { .forEach(node -> {
if (node instanceof ClassNode) { if (node instanceof ClassNode) {
@@ -266,7 +272,7 @@ public class ClassGen {
}); });
} }
private void addInnerClass(CodeWriter code, ClassNode innerCls) { private void addInnerClass(ICodeWriter code, ClassNode innerCls) {
try { try {
ClassGen inClGen = new ClassGen(innerCls, getParentGen()); ClassGen inClGen = new ClassGen(innerCls, getParentGen());
code.newLine(); code.newLine();
@@ -279,15 +285,18 @@ public class ClassGen {
private boolean isInnerClassesPresents() { private boolean isInnerClassesPresents() {
for (ClassNode innerCls : cls.getInnerClasses()) { for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) { if (!innerCls.contains(AType.ANONYMOUS_CLASS)) {
return true; return true;
} }
} }
return false; return false;
} }
private void addMethod(CodeWriter code, MethodNode mth) { private void addMethod(ICodeWriter code, MethodNode mth) {
if (code.getLine() != clsDeclLine) { if (skipMethod(mth)) {
return;
}
if (code.getLength() != clsDeclOffset) {
code.newLine(); code.newLine();
} }
int savedIndent = code.getIndent(); int savedIndent = code.getIndent();
@@ -297,15 +306,35 @@ public class ClassGen {
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) { if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw new JadxRuntimeException("Method generation error", e); throw new JadxRuntimeException("Method generation error", e);
} }
code.newLine().add("/*"); mth.addError("Method generation error", e);
code.newLine().addMultiLine(ErrorsCounter.error(mth, "Method generation error", e)); CodeGenUtils.addErrors(code, mth);
Utils.appendStackTrace(code, e);
code.newLine().add("*/");
code.setIndent(savedIndent); code.setIndent(savedIndent);
mth.addError("Method generation error: " + e.getMessage(), e);
} }
} }
/**
* Additional checks for inlined methods
*/
private boolean skipMethod(MethodNode mth) {
MethodInlineAttr inlineAttr = mth.get(AType.METHOD_INLINE);
if (inlineAttr == null || inlineAttr.notNeeded()) {
return false;
}
if (mth.getUseIn().isEmpty()) {
mth.add(AFlag.DONT_GENERATE);
return true;
}
List<MethodNode> useInCompleted = mth.getUseIn().stream()
.filter(m -> m.getTopParentClass().getState().isProcessComplete())
.collect(Collectors.toList());
if (useInCompleted.isEmpty()) {
mth.add(AFlag.DONT_GENERATE);
return true;
}
mth.addDebugComment("Method not inlined, still used in: " + useInCompleted);
return false;
}
private boolean isMethodsPresents() { private boolean isMethodsPresents() {
for (MethodNode mth : cls.getMethods()) { for (MethodNode mth : cls.getMethods()) {
if (!mth.contains(AFlag.DONT_GENERATE)) { if (!mth.contains(AFlag.DONT_GENERATE)) {
@@ -315,17 +344,15 @@ public class ClassGen {
return false; return false;
} }
public void addMethodCode(CodeWriter code, MethodNode mth) throws CodegenException { public void addMethodCode(ICodeWriter code, MethodNode mth) throws CodegenException {
CodeGenUtils.addComments(code, mth); CodeGenUtils.addErrorsAndComments(code, mth);
if (mth.isNoCode()) { if (mth.isNoCode()) {
MethodGen mthGen = new MethodGen(this, mth); MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code); mthGen.addDefinition(code);
code.add(';'); code.add(';');
} else { } else {
insertDecompilationProblems(code, mth);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE); boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode && showInconsistentCode) { if (badCode && showInconsistentCode) {
mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false; badCode = false;
} }
MethodGen mthGen; MethodGen mthGen;
@@ -345,35 +372,14 @@ public class ClassGen {
} }
} }
public void insertDecompilationProblems(CodeWriter code, AttrNode node) { private void addFields(ICodeWriter code) throws CodegenException {
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
if (!errors.isEmpty()) {
errors.stream().distinct().sorted().forEach(err -> {
code.startLine("/* JADX ERROR: ").add(err.getError());
Throwable cause = err.getCause();
if (cause != null) {
code.incIndent();
Utils.appendStackTrace(code, cause);
code.decIndent();
}
code.add("*/");
});
}
List<String> warns = node.getAll(AType.JADX_WARN);
if (!warns.isEmpty()) {
warns.stream().distinct().sorted()
.forEach(warn -> code.startLine("/* JADX WARNING: ").addMultiLine(warn).add(" */"));
}
}
private void addFields(CodeWriter code) throws CodegenException {
addEnumFields(code); addEnumFields(code);
for (FieldNode f : cls.getFields()) { for (FieldNode f : cls.getFields()) {
addField(code, f); addField(code, f);
} }
} }
public void addField(CodeWriter code, FieldNode f) { public void addField(ICodeWriter code, FieldNode f) {
if (f.contains(AFlag.DONT_GENERATE)) { if (f.contains(AFlag.DONT_GENERATE)) {
return; return;
} }
@@ -383,28 +389,40 @@ public class ClassGen {
CodeGenUtils.addComments(code, f); CodeGenUtils.addComments(code, f);
annotationGen.addForField(code, f); annotationGen.addForField(code, f);
if (f.getFieldInfo().isRenamed()) { boolean addInfoComments = f.checkCommentsLevel(CommentsLevel.INFO);
if (f.getFieldInfo().isRenamed() && addInfoComments) {
code.newLine(); code.newLine();
CodeGenUtils.addRenamedComment(code, f, f.getName()); CodeGenUtils.addRenamedComment(code, f, f.getName());
} }
code.startLine(f.getAccessFlags().makeString()); code.startLine(f.getAccessFlags().makeString(addInfoComments));
useType(code, f.getType()); useType(code, f.getType());
code.add(' '); code.add(' ');
code.attachDefinition(f); code.attachDefinition(f);
code.add(f.getAlias()); code.add(f.getAlias());
FieldInitAttr fv = f.get(AType.FIELD_INIT);
if (fv != null) { FieldInitInsnAttr initInsnAttr = f.get(AType.FIELD_INIT_INSN);
if (initInsnAttr != null) {
InsnGen insnGen = makeInsnGen(initInsnAttr.getInsnMth());
code.add(" = "); code.add(" = ");
if (fv.getValueType() == InitType.CONST) { addInsnBody(insnGen, code, initInsnAttr.getInsn());
EncodedValue encodedValue = fv.getEncodedValue(); } else {
if (encodedValue.getType() == EncodedType.ENCODED_NULL) { EncodedValue constVal = f.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null) {
code.add(" = ");
if (constVal.getType() == EncodedType.ENCODED_NULL) {
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback)); code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
} else { } else {
annotationGen.encodeValue(cls.root(), code, encodedValue); Object val = EncodedValueUtils.convertToConstValue(constVal);
if (val instanceof LiteralArg) {
long lit = ((LiteralArg) val).getLiteral();
if (!AndroidResourcesUtils.handleResourceFieldValue(cls, code, lit, f.getType())) {
// force literal type to be same as field (java bytecode can use different type)
code.add(TypeGen.literalToString(lit, f.getType(), cls, fallback));
}
} else {
annotationGen.encodeValue(cls.root(), code, constVal);
}
} }
} else if (fv.getValueType() == InitType.INSN) {
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
addInsnBody(insnGen, code, fv.getInsn());
} }
} }
code.add(';'); code.add(';');
@@ -419,7 +437,7 @@ public class ClassGen {
return false; return false;
} }
private void addEnumFields(CodeWriter code) throws CodegenException { private void addEnumFields(ICodeWriter code) throws CodegenException {
EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS); EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS);
if (enumFields == null) { if (enumFields == null) {
return; return;
@@ -427,6 +445,8 @@ public class ClassGen {
InsnGen igen = null; InsnGen igen = null;
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext();) { for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext();) {
EnumField f = it.next(); EnumField f = it.next();
CodeGenUtils.addComments(code, f.getField());
code.startLine(f.getField().getAlias()); code.startLine(f.getField().getAlias());
ConstructorInsn constrInsn = f.getConstrInsn(); ConstructorInsn constrInsn = f.getConstrInsn();
MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth()); MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth());
@@ -439,7 +459,7 @@ public class ClassGen {
} }
if (f.getCls() != null) { if (f.getCls() != null) {
code.add(' '); code.add(' ');
new ClassGen(f.getCls(), this).addClassBody(code); new ClassGen(f.getCls(), this).addClassBody(code, true);
} }
if (it.hasNext()) { if (it.hasNext()) {
code.add(','); code.add(',');
@@ -471,7 +491,7 @@ public class ClassGen {
return new InsnGen(mthGen, false); return new InsnGen(mthGen, false);
} }
private void addInsnBody(InsnGen insnGen, CodeWriter code, InsnNode insn) { private void addInsnBody(InsnGen insnGen, ICodeWriter code, InsnNode insn) {
try { try {
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP); insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
} catch (Exception e) { } catch (Exception e) {
@@ -479,7 +499,7 @@ public class ClassGen {
} }
} }
public void useType(CodeWriter code, ArgType type) { public void useType(ICodeWriter code, ArgType type) {
PrimitiveType stype = type.getPrimitiveType(); PrimitiveType stype = type.getPrimitiveType();
if (stype == null) { if (stype == null) {
code.add(type.toString()); code.add(type.toString());
@@ -497,21 +517,51 @@ public class ClassGen {
} }
} }
public void useClass(CodeWriter code, String rawCls) { public void useClass(ICodeWriter code, String rawCls) {
useClass(code, ArgType.object(rawCls)); useClass(code, ArgType.object(rawCls));
} }
public void useClass(CodeWriter code, ArgType type) { public void useClass(ICodeWriter code, ArgType type) {
ArgType outerType = type.getOuterType(); ArgType outerType = type.getOuterType();
if (outerType != null) { if (outerType != null) {
useClass(code, outerType); useClass(code, outerType);
code.add('.'); code.add('.');
// import not needed, force use short name addInnerType(code, type);
useClassShortName(code, type.getObject());
return; return;
} }
useClass(code, ClassInfo.fromType(cls.root(), type)); useClass(code, ClassInfo.fromType(cls.root(), type));
addGenerics(code, type);
}
private void addInnerType(ICodeWriter code, ArgType baseType) {
ArgType innerType = baseType.getInnerType();
ArgType outerType = innerType.getOuterType();
if (outerType != null) {
useClassWithShortName(code, baseType, outerType);
code.add('.');
addInnerType(code, innerType);
return;
}
useClassWithShortName(code, baseType, innerType);
}
private void useClassWithShortName(ICodeWriter code, ArgType baseType, ArgType type) {
String fullNameObj;
if (type.getObject().contains(".")) {
fullNameObj = type.getObject();
} else {
fullNameObj = baseType.getObject();
}
ClassInfo classInfo = ClassInfo.fromName(cls.root(), fullNameObj);
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
code.add(classInfo.getAliasShortName());
addGenerics(code, type);
}
private void addGenerics(ICodeWriter code, ArgType type) {
List<ArgType> generics = type.getGenericTypes(); List<ArgType> generics = type.getGenericTypes();
if (generics != null) { if (generics != null) {
code.add('<'); code.add('<');
@@ -536,16 +586,7 @@ public class ClassGen {
} }
} }
private void useClassShortName(CodeWriter code, String object) { public void useClass(ICodeWriter code, ClassInfo classInfo) {
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(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.root().resolveClass(classInfo); ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) { if (classNode != null) {
useClass(code, classNode); useClass(code, classNode);
@@ -554,12 +595,12 @@ public class ClassGen {
} }
} }
public void useClass(CodeWriter code, ClassNode classNode) { public void useClass(ICodeWriter code, ClassNode classNode) {
code.attachAnnotation(classNode); code.attachAnnotation(classNode);
addClsName(code, classNode.getClassInfo()); addClsName(code, classNode.getClassInfo());
} }
private void addClsName(CodeWriter code, ClassInfo classInfo) { public void addClsName(ICodeWriter code, ClassInfo classInfo) {
String clsName = useClassInternal(cls.getClassInfo(), classInfo); String clsName = useClassInternal(cls.getClassInfo(), classInfo);
code.add(clsName); code.add(clsName);
} }
@@ -570,6 +611,9 @@ public class ClassGen {
return fullName; return fullName;
} }
String shortName = extClsInfo.getAliasShortName(); String shortName = extClsInfo.getAliasShortName();
if (useCls.equals(extClsInfo)) {
return shortName;
}
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) { if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
return shortName; return shortName;
} }
@@ -579,6 +623,9 @@ public class ClassGen {
if (extClsInfo.isInner()) { if (extClsInfo.isInner()) {
return expandInnerClassName(useCls, extClsInfo); return expandInnerClassName(useCls, extClsInfo);
} }
if (searchCollision(cls.root(), useCls, extClsInfo)) {
return fullName;
}
if (isBothClassesInOneTopClass(useCls, extClsInfo)) { if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
return shortName; return shortName;
} }
@@ -586,9 +633,6 @@ public class ClassGen {
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) { if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
return shortName; return shortName;
} }
if (searchCollision(cls.root(), useCls, extClsInfo)) {
return fullName;
}
// ignore classes from default package // ignore classes from default package
if (extClsInfo.isDefaultPackage()) { if (extClsInfo.isDefaultPackage()) {
return shortName; return shortName;
@@ -685,14 +729,14 @@ public class ClassGen {
return searchCollision(root, useCls.getParentClass(), searchCls); return searchCollision(root, useCls.getParentClass(), searchCls);
} }
private void insertRenameInfo(CodeWriter code, ClassNode cls) { private void insertRenameInfo(ICodeWriter code, ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo(); ClassInfo classInfo = cls.getClassInfo();
if (classInfo.hasAlias()) { if (classInfo.hasAlias() && cls.checkCommentsLevel(CommentsLevel.INFO)) {
CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject()); CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject());
} }
} }
private static void addClassUsageInfo(CodeWriter code, ClassNode cls) { private static void addClassUsageInfo(ICodeWriter code, ClassNode cls) {
List<ClassNode> deps = cls.getDependencies(); List<ClassNode> deps = cls.getDependencies();
code.startLine("// deps - ").add(Integer.toString(deps.size())); code.startLine("// deps - ").add(Integer.toString(deps.size()));
for (ClassNode depCls : deps) { for (ClassNode depCls : deps) {
@@ -710,7 +754,7 @@ public class ClassGen {
} }
} }
static void addMthUsageInfo(CodeWriter code, MethodNode mth) { static void addMthUsageInfo(ICodeWriter code, MethodNode mth) {
List<MethodNode> useInMths = mth.getUseIn(); List<MethodNode> useInMths = mth.getUseIn();
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size())); code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
for (MethodNode useMth : useInMths) { for (MethodNode useMth : useInMths) {
@@ -718,7 +762,7 @@ public class ClassGen {
} }
} }
private static void addFieldUsageInfo(CodeWriter code, FieldNode fieldNode) { private static void addFieldUsageInfo(ICodeWriter code, FieldNode fieldNode) {
List<MethodNode> useInMths = fieldNode.getUseIn(); List<MethodNode> useInMths = fieldNode.getUseIn();
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size())); code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
for (MethodNode useMth : useInMths) { for (MethodNode useMth : useInMths) {
@@ -745,4 +789,13 @@ public class ClassGen {
public void setBodyGenStarted(boolean bodyGenStarted) { public void setBodyGenStarted(boolean bodyGenStarted) {
this.bodyGenStarted = bodyGenStarted; this.bodyGenStarted = bodyGenStarted;
} }
@Nullable
public NameGen getOuterNameGen() {
return outerNameGen;
}
public void setOuterNameGen(@NotNull NameGen outerNameGen) {
this.outerNameGen = outerNameGen;
}
} }
@@ -1,291 +0,0 @@
package jadx.core.codegen;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.impl.SimpleCodeInfo;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
public class CodeWriter {
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
public static final String NL = System.getProperty("line.separator");
public static final String INDENT_STR = " ";
private static final boolean ADD_LINE_NUMBERS = false;
private static final String[] INDENT_CACHE = {
"",
INDENT_STR,
INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
};
private StringBuilder buf;
@Nullable
private String code;
private String indentStr;
private int indent;
private int line = 1;
private int offset = 0;
private Map<CodePosition, Object> annotations = Collections.emptyMap();
private Map<Integer, Integer> lineMap = Collections.emptyMap();
public CodeWriter() {
this.buf = new StringBuilder();
this.indent = 0;
this.indentStr = "";
if (ADD_LINE_NUMBERS) {
incIndent(3);
add(indentStr);
}
}
public CodeWriter startLine() {
addLine();
addLineIndent();
return this;
}
public CodeWriter startLine(char c) {
addLine();
addLineIndent();
add(c);
return this;
}
public CodeWriter startLine(String str) {
addLine();
addLineIndent();
add(str);
return this;
}
public CodeWriter startLineWithNum(int sourceLine) {
if (sourceLine == 0) {
startLine();
return this;
}
if (ADD_LINE_NUMBERS) {
newLine();
attachSourceLine(sourceLine);
String ln = "/* " + sourceLine + " */ ";
add(ln);
if (indentStr.length() > ln.length()) {
add(indentStr.substring(ln.length()));
}
} else {
startLine();
attachSourceLine(sourceLine);
}
return this;
}
public CodeWriter addMultiLine(String str) {
if (str.contains(NL)) {
buf.append(str.replace(NL, NL + indentStr));
line += StringUtils.countMatches(str, NL);
offset = 0;
} else {
buf.append(str);
}
return this;
}
public CodeWriter add(String str) {
buf.append(str);
offset += str.length();
return this;
}
public CodeWriter add(char c) {
buf.append(c);
offset++;
return this;
}
CodeWriter add(CodeWriter code) {
line--;
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
CodePosition pos = entry.getKey();
attachAnnotation(entry.getValue(), new CodePosition(line + pos.getLine(), pos.getOffset()));
}
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
attachSourceLine(line + entry.getKey(), entry.getValue());
}
line += code.line;
offset = code.offset;
buf.append(code.buf);
return this;
}
public CodeWriter newLine() {
addLine();
return this;
}
public CodeWriter addIndent() {
add(INDENT_STR);
return this;
}
private void addLine() {
buf.append(NL);
line++;
offset = 0;
}
private CodeWriter addLineIndent() {
buf.append(indentStr);
offset += indentStr.length();
return this;
}
private void updateIndent() {
int curIndent = indent;
if (curIndent < INDENT_CACHE.length) {
this.indentStr = INDENT_CACHE[curIndent];
} else {
this.indentStr = Utils.strRepeat(INDENT_STR, curIndent);
}
}
public void incIndent() {
incIndent(1);
}
public void decIndent() {
decIndent(1);
}
public void incIndent(int c) {
this.indent += c;
updateIndent();
}
public void decIndent(int c) {
this.indent -= c;
if (this.indent < 0) {
LOG.warn("Indent < 0");
this.indent = 0;
}
updateIndent();
}
public int getIndent() {
return indent;
}
public void setIndent(int indent) {
this.indent = indent;
updateIndent();
}
public int getLine() {
return line;
}
private static class DefinitionWrapper {
private final LineAttrNode node;
private DefinitionWrapper(LineAttrNode node) {
this.node = node;
}
public LineAttrNode getNode() {
return node;
}
}
public void attachDefinition(LineAttrNode obj) {
attachAnnotation(obj);
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
}
public void attachAnnotation(Object obj) {
attachAnnotation(obj, new CodePosition(line, offset + 1));
}
public void attachLineAnnotation(Object obj) {
attachAnnotation(obj, new CodePosition(line, 0));
}
private Object attachAnnotation(Object obj, CodePosition pos) {
if (annotations.isEmpty()) {
annotations = new HashMap<>();
}
return annotations.put(pos, obj);
}
public void attachSourceLine(int sourceLine) {
if (sourceLine == 0) {
return;
}
attachSourceLine(line, sourceLine);
}
private void attachSourceLine(int decompiledLine, int sourceLine) {
if (lineMap.isEmpty()) {
lineMap = new TreeMap<>();
}
lineMap.put(decompiledLine, sourceLine);
}
public ICodeInfo finish() {
removeFirstEmptyLine();
processDefinitionAnnotations();
code = buf.toString();
buf = null;
return new SimpleCodeInfo(code, lineMap, annotations);
}
private void removeFirstEmptyLine() {
int len = NL.length();
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
buf.delete(0, len);
}
}
private void processDefinitionAnnotations() {
if (!annotations.isEmpty()) {
annotations.entrySet().removeIf(entry -> {
Object v = entry.getValue();
if (v instanceof DefinitionWrapper) {
LineAttrNode l = ((DefinitionWrapper) v).getNode();
l.setDecompiledLine(entry.getKey().getLine());
return true;
}
return false;
});
}
}
public int bufLength() {
return buf.length();
}
public String getCodeStr() {
if (code == null) {
finish();
}
return code;
}
@Override
public String toString() {
return code != null ? code : buf.toString();
}
}
@@ -4,6 +4,7 @@ import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Queue; import java.util.Queue;
import jadx.api.ICodeWriter;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.IfOp; import jadx.core.dex.instructions.IfOp;
@@ -41,15 +42,15 @@ public class ConditionGen extends InsnGen {
super(insnGen.mgen, insnGen.fallback); super(insnGen.mgen, insnGen.fallback);
} }
void add(CodeWriter code, IfCondition condition) throws CodegenException { public void add(ICodeWriter code, IfCondition condition) throws CodegenException {
add(code, new CondStack(), condition); add(code, new CondStack(), condition);
} }
void wrap(CodeWriter code, IfCondition condition) throws CodegenException { void wrap(ICodeWriter code, IfCondition condition) throws CodegenException {
wrap(code, new CondStack(), condition); wrap(code, new CondStack(), condition);
} }
private void add(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException { private void add(ICodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
stack.push(condition); stack.push(condition);
switch (condition.getMode()) { switch (condition.getMode()) {
case COMPARE: case COMPARE:
@@ -75,7 +76,7 @@ public class ConditionGen extends InsnGen {
stack.pop(); stack.pop();
} }
private void wrap(CodeWriter code, CondStack stack, IfCondition cond) throws CodegenException { private void wrap(ICodeWriter code, CondStack stack, IfCondition cond) throws CodegenException {
boolean wrap = isWrapNeeded(cond); boolean wrap = isWrapNeeded(cond);
if (wrap) { if (wrap) {
code.add('('); code.add('(');
@@ -86,7 +87,7 @@ public class ConditionGen extends InsnGen {
} }
} }
private void wrap(CodeWriter code, InsnArg firstArg) throws CodegenException { private void wrap(ICodeWriter code, InsnArg firstArg) throws CodegenException {
boolean wrap = isArgWrapNeeded(firstArg); boolean wrap = isArgWrapNeeded(firstArg);
if (wrap) { if (wrap) {
code.add('('); code.add('(');
@@ -97,7 +98,7 @@ public class ConditionGen extends InsnGen {
} }
} }
private void addCompare(CodeWriter code, CondStack stack, Compare compare) throws CodegenException { private void addCompare(ICodeWriter code, CondStack stack, Compare compare) throws CodegenException {
IfOp op = compare.getOp(); IfOp op = compare.getOp();
InsnArg firstArg = compare.getA(); InsnArg firstArg = compare.getA();
InsnArg secondArg = compare.getB(); InsnArg secondArg = compare.getB();
@@ -130,7 +131,7 @@ public class ConditionGen extends InsnGen {
addArg(code, secondArg, isArgWrapNeeded(secondArg)); addArg(code, secondArg, isArgWrapNeeded(secondArg));
} }
private void addTernary(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException { private void addTernary(ICodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
add(code, stack, condition.first()); add(code, stack, condition.first());
code.add(" ? "); code.add(" ? ");
add(code, stack, condition.second()); add(code, stack, condition.second());
@@ -138,12 +139,12 @@ public class ConditionGen extends InsnGen {
add(code, stack, condition.third()); add(code, stack, condition.third());
} }
private void addNot(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException { private void addNot(ICodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
code.add('!'); code.add('!');
wrap(code, stack, condition.getArgs().get(0)); wrap(code, stack, condition.getArgs().get(0));
} }
private void addAndOr(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException { private void addAndOr(ICodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
String mode = condition.getMode() == Mode.AND ? " && " : " || "; String mode = condition.getMode() == Mode.AND ? " && " : " || ";
Iterator<IfCondition> it = condition.getArgs().iterator(); Iterator<IfCondition> it = condition.getArgs().iterator();
while (it.hasNext()) { while (it.hasNext()) {
@@ -9,7 +9,12 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.core.deobf.NameMapper; 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.plugins.input.data.MethodHandleType;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
@@ -30,6 +35,7 @@ import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeCustomNode;
import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType; import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.NewArrayNode; import jadx.core.dex.instructions.NewArrayNode;
@@ -49,6 +55,7 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.RegionUtils; import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -62,7 +69,6 @@ public class InsnGen {
protected final MethodNode mth; protected final MethodNode mth;
protected final RootNode root; protected final RootNode root;
protected final boolean fallback; protected final boolean fallback;
protected final boolean attachInsns;
protected enum Flags { protected enum Flags {
BODY_ONLY, BODY_ONLY,
@@ -75,32 +81,39 @@ public class InsnGen {
this.mth = mgen.getMethodNode(); this.mth = mgen.getMethodNode();
this.root = mth.root(); this.root = mth.root();
this.fallback = fallback; this.fallback = fallback;
this.attachInsns = root.getArgs().isJsonOutput();
} }
private boolean isFallback() { private boolean isFallback() {
return fallback; return fallback;
} }
public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException { public void addArgDot(ICodeWriter code, InsnArg arg) throws CodegenException {
int len = code.bufLength(); int len = code.getLength();
addArg(code, arg, true); addArg(code, arg, true);
if (len != code.bufLength()) { if (len != code.getLength()) {
code.add('.'); code.add('.');
} }
} }
public void addArg(CodeWriter code, InsnArg arg) throws CodegenException { public void addArg(ICodeWriter code, InsnArg arg) throws CodegenException {
addArg(code, arg, true); addArg(code, arg, true);
} }
public void addArg(CodeWriter code, InsnArg arg, boolean wrap) throws CodegenException { public void addArg(ICodeWriter code, InsnArg arg, boolean wrap) throws CodegenException {
addArg(code, arg, wrap ? BODY_ONLY_FLAG : BODY_ONLY_NOWRAP_FLAGS);
}
public void addArg(ICodeWriter code, InsnArg arg, Set<Flags> flags) throws CodegenException {
if (arg.isRegister()) { if (arg.isRegister()) {
code.add(mgen.getNameGen().useArg((RegisterArg) arg)); RegisterArg reg = (RegisterArg) arg;
if (code.isMetadataSupported()) {
code.attachAnnotation(VarRef.get(mth, reg));
}
code.add(mgen.getNameGen().useArg(reg));
} else if (arg.isLiteral()) { } else if (arg.isLiteral()) {
code.add(lit((LiteralArg) arg)); addLiteralArg(code, (LiteralArg) arg, flags);
} else if (arg.isInsnWrap()) { } else if (arg.isInsnWrap()) {
addWrappedArg(code, (InsnWrapArg) arg, wrap); addWrappedArg(code, (InsnWrapArg) arg, flags);
} else if (arg.isNamed()) { } else if (arg.isNamed()) {
code.add(((Named) arg).getName()); code.add(((Named) arg).getName());
} else { } else {
@@ -108,19 +121,27 @@ public class InsnGen {
} }
} }
private void addWrappedArg(CodeWriter code, InsnWrapArg arg, boolean wrap) throws CodegenException { 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(); InsnNode wrapInsn = arg.getWrapInsn();
if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) { if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
code.add('('); code.add('(');
makeInsn(wrapInsn, code, Flags.INLINE); makeInsn(wrapInsn, code, Flags.INLINE);
code.add(')'); code.add(')');
} else { } else {
Flags flags = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP; makeInsnBody(code, wrapInsn, flags);
makeInsn(wrapInsn, code, flags);
} }
} }
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException { public void assignVar(ICodeWriter code, InsnNode insn) throws CodegenException {
RegisterArg arg = insn.getResult(); RegisterArg arg = insn.getResult();
if (insn.contains(AFlag.DECLARE_VAR)) { if (insn.contains(AFlag.DECLARE_VAR)) {
declareVar(code, arg); declareVar(code, arg);
@@ -129,16 +150,19 @@ public class InsnGen {
} }
} }
public void declareVar(CodeWriter code, RegisterArg arg) { public void declareVar(ICodeWriter code, RegisterArg arg) {
declareVar(code, arg.getSVar().getCodeVar()); declareVar(code, arg.getSVar().getCodeVar());
} }
public void declareVar(CodeWriter code, CodeVar codeVar) { public void declareVar(ICodeWriter code, CodeVar codeVar) {
if (codeVar.isFinal()) { if (codeVar.isFinal()) {
code.add("final "); code.add("final ");
} }
useType(code, codeVar.getType()); useType(code, codeVar.getType());
code.add(' '); code.add(' ');
if (code.isMetadataSupported()) {
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
}
code.add(mgen.getNameGen().assignArg(codeVar)); code.add(mgen.getNameGen().assignArg(codeVar));
} }
@@ -146,9 +170,9 @@ public class InsnGen {
return TypeGen.literalToString(arg, mth, fallback); return TypeGen.literalToString(arg, mth, fallback);
} }
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException { private void instanceField(ICodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
ClassNode pCls = mth.getParentClass(); ClassNode pCls = mth.getParentClass();
FieldNode fieldNode = pCls.root().deepResolveField(field); FieldNode fieldNode = pCls.root().resolveField(field);
if (fieldNode != null) { if (fieldNode != null) {
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE); FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
if (replace != null) { if (replace != null) {
@@ -175,7 +199,7 @@ public class InsnGen {
} }
} }
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) { public static void makeStaticFieldAccess(ICodeWriter code, FieldInfo field, ClassGen clsGen) {
ClassInfo declClass = field.getDeclClass(); ClassInfo declClass = field.getDeclClass();
// TODO // TODO
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass); boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
@@ -186,7 +210,7 @@ public class InsnGen {
} }
code.add('.'); code.add('.');
} }
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field); FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
if (fieldNode != null) { if (fieldNode != null) {
code.attachAnnotation(fieldNode); code.attachAnnotation(fieldNode);
} }
@@ -197,23 +221,23 @@ public class InsnGen {
} }
} }
protected void staticField(CodeWriter code, FieldInfo field) { protected void staticField(ICodeWriter code, FieldInfo field) {
makeStaticFieldAccess(code, field, mgen.getClassGen()); makeStaticFieldAccess(code, field, mgen.getClassGen());
} }
public void useClass(CodeWriter code, ArgType type) { public void useClass(ICodeWriter code, ArgType type) {
mgen.getClassGen().useClass(code, type); mgen.getClassGen().useClass(code, type);
} }
public void useClass(CodeWriter code, ClassInfo cls) { public void useClass(ICodeWriter code, ClassInfo cls) {
mgen.getClassGen().useClass(code, cls); mgen.getClassGen().useClass(code, cls);
} }
protected void useType(CodeWriter code, ArgType type) { protected void useType(ICodeWriter code, ArgType type) {
mgen.getClassGen().useType(code, type); mgen.getClassGen().useType(code, type);
} }
public void makeInsn(InsnNode insn, CodeWriter code) throws CodegenException { public void makeInsn(InsnNode insn, ICodeWriter code) throws CodegenException {
makeInsn(insn, code, null); makeInsn(insn, code, null);
} }
@@ -221,7 +245,7 @@ public class InsnGen {
private static final Set<Flags> BODY_ONLY_FLAG = EnumSet.of(Flags.BODY_ONLY); private static final Set<Flags> BODY_ONLY_FLAG = EnumSet.of(Flags.BODY_ONLY);
private static final Set<Flags> BODY_ONLY_NOWRAP_FLAGS = EnumSet.of(Flags.BODY_ONLY_NOWRAP); private static final Set<Flags> BODY_ONLY_NOWRAP_FLAGS = EnumSet.of(Flags.BODY_ONLY_NOWRAP);
protected void makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException { protected void makeInsn(InsnNode insn, ICodeWriter code, Flags flag) throws CodegenException {
if (insn.getType() == InsnType.REGION_ARG) { if (insn.getType() == InsnType.REGION_ARG) {
return; return;
} }
@@ -231,9 +255,7 @@ public class InsnGen {
} else { } else {
if (flag != Flags.INLINE) { if (flag != Flags.INLINE) {
code.startLineWithNum(insn.getSourceLine()); code.startLineWithNum(insn.getSourceLine());
if (attachInsns) { InsnCodeOffset.attach(code, insn);
code.attachLineAnnotation(insn);
}
if (insn.contains(AFlag.COMMENT_OUT)) { if (insn.contains(AFlag.COMMENT_OUT)) {
code.add("// "); code.add("// ");
} }
@@ -249,6 +271,7 @@ public class InsnGen {
makeInsnBody(code, insn, EMPTY_FLAGS); makeInsnBody(code, insn, EMPTY_FLAGS);
if (flag != Flags.INLINE) { if (flag != Flags.INLINE) {
code.add(';'); code.add(';');
CodeGenUtils.addCodeComments(code, mth, insn);
} }
} }
} catch (Exception e) { } catch (Exception e) {
@@ -256,7 +279,7 @@ public class InsnGen {
} }
} }
private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException { private void makeInsnBody(ICodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
switch (insn.getType()) { switch (insn.getType()) {
case CONST_STR: case CONST_STR:
String str = ((ConstStringNode) insn).getString(); String str = ((ConstStringNode) insn).getString();
@@ -371,11 +394,15 @@ public class InsnGen {
ArgType arrayType = ((NewArrayNode) insn).getArrayType(); ArgType arrayType = ((NewArrayNode) insn).getArrayType();
code.add("new "); code.add("new ");
useType(code, arrayType.getArrayRootElement()); useType(code, arrayType.getArrayRootElement());
code.add('['); int k = 0;
addArg(code, insn.getArg(0)); int argsCount = insn.getArgsCount();
code.add(']'); for (; k < argsCount; k++) {
code.add('[');
addArg(code, insn.getArg(k), false);
code.add(']');
}
int dim = arrayType.getArrayDimension(); int dim = arrayType.getArrayDimension();
for (int i = 0; i < dim - 1; i++) { for (; k < dim - 1; k++) {
code.add("[]"); code.add("[]");
} }
break; break;
@@ -478,7 +505,7 @@ public class InsnGen {
break; break;
case ONE_ARG: case ONE_ARG:
addArg(code, insn.getArg(0)); addArg(code, insn.getArg(0), state);
break; break;
/* fallback mode instructions */ /* fallback mode instructions */
@@ -545,7 +572,7 @@ public class InsnGen {
case FILL_ARRAY_DATA: case FILL_ARRAY_DATA:
fallbackOnlyInsn(insn); fallbackOnlyInsn(insn);
code.add("fill-array " + insn.toString()); code.add("fill-array " + insn);
break; break;
case SWITCH_DATA: case SWITCH_DATA:
@@ -553,6 +580,17 @@ public class InsnGen {
code.add(insn.toString()); code.add(insn.toString());
break; break;
case MOVE_MULTI:
fallbackOnlyInsn(insn);
int len = insn.getArgsCount();
for (int i = 0; i < len - 1; i += 2) {
addArg(code, insn.getArg(i));
code.add(" = ");
addArg(code, insn.getArg(i + 1));
code.add("; ");
}
break;
default: default:
throw new CodegenException(mth, "Unknown instruction: " + insn.getType()); throw new CodegenException(mth, "Unknown instruction: " + insn.getType());
} }
@@ -562,8 +600,10 @@ public class InsnGen {
* In most cases must be combined with new array instructions. * In most cases must be combined with new array instructions.
* Use one by one array fill (can be replaced with System.arrayCopy) * Use one by one array fill (can be replaced with System.arrayCopy)
*/ */
private void fillArray(CodeWriter code, FillArrayInsn arrayNode) throws CodegenException { private void fillArray(ICodeWriter code, FillArrayInsn arrayNode) throws CodegenException {
code.add("// fill-array-data instruction"); if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
code.add("// fill-array-data instruction");
}
code.startLine(); code.startLine();
InsnArg arrArg = arrayNode.getArg(0); InsnArg arrArg = arrayNode.getArg(0);
ArgType arrayType = arrArg.getType(); ArgType arrayType = arrArg.getType();
@@ -586,7 +626,7 @@ public class InsnGen {
} }
} }
private void oneArgInsn(CodeWriter code, InsnNode insn, Set<Flags> state, char op) throws CodegenException { private void oneArgInsn(ICodeWriter code, InsnNode insn, Set<Flags> state, char op) throws CodegenException {
boolean wrap = state.contains(Flags.BODY_ONLY); boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) { if (wrap) {
code.add('('); code.add('(');
@@ -608,23 +648,29 @@ public class InsnGen {
} }
} }
private void filledNewArray(FilledNewArrayNode insn, CodeWriter code) throws CodegenException { private void filledNewArray(FilledNewArrayNode insn, ICodeWriter code) throws CodegenException {
if (!insn.contains(AFlag.DECLARE_VAR)) { if (!insn.contains(AFlag.DECLARE_VAR)) {
code.add("new "); code.add("new ");
useType(code, insn.getArrayType()); useType(code, insn.getArrayType());
} }
code.add('{'); code.add('{');
int c = insn.getArgsCount(); int c = insn.getArgsCount();
int wrap = 0;
for (int i = 0; i < c; i++) { for (int i = 0; i < c; i++) {
addArg(code, insn.getArg(i), false); addArg(code, insn.getArg(i), false);
if (i + 1 < c) { if (i + 1 < c) {
code.add(", "); code.add(", ");
} }
wrap++;
if (wrap == 1000) {
code.startLine();
wrap = 0;
}
} }
code.add('}'); code.add('}');
} }
private void makeConstructor(ConstructorInsn insn, CodeWriter code) throws CodegenException { private void makeConstructor(ConstructorInsn insn, ICodeWriter code) throws CodegenException {
ClassNode cls = mth.root().resolveClass(insn.getClassType()); ClassNode cls = mth.root().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous() && !fallback) { if (cls != null && cls.isAnonymous() && !fallback) {
cls.ensureProcessed(); cls.ensureProcessed();
@@ -635,13 +681,22 @@ public class InsnGen {
if (insn.isSelf()) { if (insn.isSelf()) {
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!"); throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
} }
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
if (insn.isSuper()) { if (insn.isSuper()) {
code.attachAnnotation(callMth);
code.add("super"); code.add("super");
} else if (insn.isThis()) { } else if (insn.isThis()) {
code.attachAnnotation(callMth);
code.add("this"); code.add("this");
} else { } else {
code.add("new "); code.add("new ");
useClass(code, insn.getClassType()); if (callMth == null || callMth.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);
}
mgen.getClassGen().addClsName(code, insn.getClassType());
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO); GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
if (genericInfoAttr != null) { if (genericInfoAttr != null) {
code.add('<'); code.add('<');
@@ -659,26 +714,18 @@ public class InsnGen {
code.add('>'); code.add('>');
} }
} }
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
generateMethodArguments(code, insn, 0, callMth); generateMethodArguments(code, insn, 0, callMth);
} }
private void inlineAnonymousConstructor(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException { private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
if (this.mth.getParentClass() == cls) { if (this.mth.getParentClass() == cls) {
cls.remove(AFlag.ANONYMOUS_CLASS); cls.remove(AType.ANONYMOUS_CLASS);
cls.remove(AFlag.DONT_GENERATE); cls.remove(AFlag.DONT_GENERATE);
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN); mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
throw new CodegenException("Anonymous inner class unlimited recursion detected." throw new CodegenException("Anonymous inner class unlimited recursion detected."
+ " Convert class to inner: " + cls.getClassInfo().getFullName()); + " Convert class to inner: " + cls.getClassInfo().getFullName());
} }
ArgType parent = cls.get(AType.ANONYMOUS_CLASS).getBaseType();
cls.add(AFlag.DONT_GENERATE);
ArgType parent;
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
} else {
parent = cls.getSuperClass();
}
// hide empty anonymous constructors // hide empty anonymous constructors
for (MethodNode ctor : cls.getMethods()) { for (MethodNode ctor : cls.getMethods()) {
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR) if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
@@ -686,32 +733,46 @@ public class InsnGen {
ctor.add(AFlag.DONT_GENERATE); ctor.add(AFlag.DONT_GENERATE);
} }
} }
code.add("new "); code.add("new ");
if (parent == null) { useClass(code, parent);
code.add("Object");
} else {
useClass(code, parent);
}
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth()); MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
if (callMth != null) {
// copy var names
List<RegisterArg> mthArgs = callMth.getArgRegs();
int argsCount = Math.min(insn.getArgsCount(), mthArgs.size());
for (int i = 0; i < argsCount; i++) {
InsnArg arg = insn.getArg(i);
if (arg.isRegister()) {
RegisterArg mthArg = mthArgs.get(i);
RegisterArg insnArg = (RegisterArg) arg;
mthArg.getSVar().setCodeVar(insnArg.getSVar().getCodeVar());
}
}
}
generateMethodArguments(code, insn, 0, callMth); generateMethodArguments(code, insn, 0, callMth);
code.add(' '); code.add(' ');
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code, true);
ClassGen classGen = new ClassGen(cls, mgen.getClassGen().getParentGen());
classGen.setOuterNameGen(mgen.getNameGen());
classGen.addClassBody(code, true);
} }
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException { private void makeInvoke(InvokeNode insn, ICodeWriter code) throws CodegenException {
InvokeType type = insn.getInvokeType();
if (type == InvokeType.CUSTOM) {
makeInvokeLambda(code, (InvokeCustomNode) insn);
return;
}
MethodInfo callMth = insn.getCallMth(); MethodInfo callMth = insn.getCallMth();
MethodNode callMthNode = mth.root().deepResolveMethod(callMth); MethodNode callMthNode = mth.root().resolveMethod(callMth);
int k = 0; int k = 0;
InvokeType type = insn.getInvokeType();
switch (type) { switch (type) {
case DIRECT: case DIRECT:
case VIRTUAL: case VIRTUAL:
case INTERFACE: case INTERFACE:
InsnArg arg = insn.getArg(0); InsnArg arg = insn.getArg(0);
// FIXME: add 'this' for equals methods in scope if (needInvokeArg(arg)) {
if (!arg.isThis()) {
addArgDot(code, arg); addArgDot(code, arg);
} }
k++; k++;
@@ -746,8 +807,137 @@ public class InsnGen {
generateMethodArguments(code, insn, k, callMthNode); 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);
return;
}
if (fallback || !customNode.isInlineInsn()) {
makeSimpleLambda(code, customNode);
return;
}
MethodNode callMth = (MethodNode) customNode.getCallInsn().get(AType.METHOD_DETAILS);
makeInlinedLambdaMethod(code, customNode, callMth);
}
private void makeRefLambda(ICodeWriter code, InvokeCustomNode customNode) {
InsnNode callInsn = customNode.getCallInsn();
if (callInsn instanceof ConstructorInsn) {
MethodInfo callMth = ((ConstructorInsn) callInsn).getCallMth();
useClass(code, callMth.getDeclClass());
code.add("::new");
return;
}
if (callInsn instanceof InvokeNode) {
InvokeNode invokeInsn = (InvokeNode) callInsn;
MethodInfo callMth = invokeInsn.getCallMth();
if (customNode.getHandleType() == MethodHandleType.INVOKE_STATIC) {
useClass(code, callMth.getDeclClass());
} else {
code.add("this");
}
code.add("::").add(callMth.getAlias());
}
}
private void makeSimpleLambda(ICodeWriter code, InvokeCustomNode customNode) {
try {
InsnNode callInsn = customNode.getCallInsn();
MethodInfo implMthInfo = customNode.getImplMthInfo();
int implArgsCount = implMthInfo.getArgsCount();
if (implArgsCount == 0) {
code.add("()");
} else {
code.add('(');
int callArgsCount = callInsn.getArgsCount();
int startArg = callArgsCount - implArgsCount;
if (customNode.getHandleType() != MethodHandleType.INVOKE_STATIC
&& customNode.getArgsCount() > 0
&& customNode.getArg(0).isThis()) {
callInsn.getArg(0).add(AFlag.THIS);
}
if (startArg >= 0) {
for (int i = startArg; i < callArgsCount; i++) {
if (i != startArg) {
code.add(", ");
}
addArg(code, callInsn.getArg(i));
}
} else {
code.add("/* ERROR: " + startArg + " */");
}
code.add(')');
}
code.add(" -> {");
if (fallback) {
code.add(" // ").add(implMthInfo.toString());
}
code.incIndent();
code.startLine();
if (!implMthInfo.getReturnType().isVoid()) {
code.add("return ");
}
makeInsn(callInsn, code, Flags.INLINE);
code.add(";");
code.decIndent();
code.startLine('}');
} catch (Exception e) {
throw new JadxRuntimeException("Failed to generate 'invoke-custom' instruction: " + e.getMessage(), e);
}
}
private void makeInlinedLambdaMethod(ICodeWriter code, InvokeCustomNode customNode, MethodNode callMth) throws CodegenException {
MethodGen callMthGen = new MethodGen(mgen.getClassGen(), callMth);
NameGen nameGen = callMthGen.getNameGen();
nameGen.inheritUsedNames(this.mgen.getNameGen());
List<ArgType> implArgs = customNode.getImplMthInfo().getArgumentsTypes();
List<RegisterArg> callArgs = callMth.getArgRegs();
if (implArgs.isEmpty()) {
code.add("()");
} else {
int callArgsCount = callArgs.size();
int startArg = callArgsCount - implArgs.size();
for (int i = startArg; i < callArgsCount; i++) {
if (i != startArg) {
code.add(", ");
}
CodeVar argCodeVar = callArgs.get(i).getSVar().getCodeVar();
code.add(nameGen.assignArg(argCodeVar));
}
}
// force set external arg names into call method args
int extArgsCount = customNode.getArgsCount();
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());
}
code.add(" -> {");
code.incIndent();
callMthGen.addInstructions(code);
code.decIndent();
code.startLine('}');
}
@Nullable @Nullable
private ClassInfo getClassForSuperCall(CodeWriter code, MethodInfo callMth) { private ClassInfo getClassForSuperCall(ICodeWriter code, MethodInfo callMth) {
ClassNode useCls = mth.getParentClass(); ClassNode useCls = mth.getParentClass();
ClassInfo insnCls = useCls.getClassInfo(); ClassInfo insnCls = useCls.getClassInfo();
ClassInfo declClass = callMth.getDeclClass(); ClassInfo declClass = callMth.getDeclClass();
@@ -776,7 +966,7 @@ public class InsnGen {
return useCls.getParentClass().getClassInfo(); return useCls.getParentClass().getClassInfo();
} }
void generateMethodArguments(CodeWriter code, BaseInvokeNode insn, int startArgNum, void generateMethodArguments(ICodeWriter code, BaseInvokeNode insn, int startArgNum,
@Nullable MethodNode mthNode) throws CodegenException { @Nullable MethodNode mthNode) throws CodegenException {
int k = startArgNum; int k = startArgNum;
if (mthNode != null && mthNode.contains(AFlag.SKIP_FIRST_ARG)) { if (mthNode != null && mthNode.contains(AFlag.SKIP_FIRST_ARG)) {
@@ -813,7 +1003,7 @@ public class InsnGen {
/** /**
* Expand varArgs from filled array. * Expand varArgs from filled array.
*/ */
private boolean processVarArg(CodeWriter code, BaseInvokeNode invokeInsn, InsnArg lastArg) throws CodegenException { private boolean processVarArg(ICodeWriter code, BaseInvokeNode invokeInsn, InsnArg lastArg) throws CodegenException {
if (!invokeInsn.contains(AFlag.VARARG_CALL)) { if (!invokeInsn.contains(AFlag.VARARG_CALL)) {
return false; return false;
} }
@@ -835,7 +1025,7 @@ public class InsnGen {
return true; return true;
} }
private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException { private void makeTernary(TernaryInsn insn, ICodeWriter code, Set<Flags> state) throws CodegenException {
boolean wrap = state.contains(Flags.BODY_ONLY); boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) { if (wrap) {
code.add('('); code.add('(');
@@ -848,7 +1038,6 @@ public class InsnGen {
} else { } else {
condGen.wrap(code, insn.getCondition()); condGen.wrap(code, insn.getCondition());
code.add(" ? "); code.add(" ? ");
addCastIfNeeded(code, first, second);
addArg(code, first, false); addArg(code, first, false);
code.add(" : "); code.add(" : ");
addArg(code, second, false); addArg(code, second, false);
@@ -858,34 +1047,7 @@ public class InsnGen {
} }
} }
private void addCastIfNeeded(CodeWriter code, InsnArg first, InsnArg second) { private void makeArith(ArithNode insn, ICodeWriter code, Set<Flags> state) throws CodegenException {
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, CodeWriter code, Set<Flags> state) throws CodegenException {
if (insn.contains(AFlag.ARITH_ONEARG)) { if (insn.contains(AFlag.ARITH_ONEARG)) {
makeArithOneArg(insn, code); makeArithOneArg(insn, code);
return; return;
@@ -905,7 +1067,7 @@ public class InsnGen {
} }
} }
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException { private void makeArithOneArg(ArithNode insn, ICodeWriter code) throws CodegenException {
ArithOp op = insn.getOp(); ArithOp op = insn.getOp();
InsnArg resArg = insn.getArg(0); InsnArg resArg = insn.getArg(0);
InsnArg arg = insn.getArg(1); InsnArg arg = insn.getArg(1);
@@ -3,17 +3,25 @@ package jadx.core.codegen;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.plugins.input.data.AccessFlags; import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.Jadx; import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.MethodParameters; import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JumpInfo; import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr; import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
@@ -24,7 +32,6 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar; import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.CatchAttr;
@@ -34,7 +41,6 @@ import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.InsnUtils; import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxOverflowException; import jadx.core.utils.exceptions.JadxOverflowException;
import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP; import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP;
@@ -53,7 +59,7 @@ public class MethodGen {
this.mth = mth; this.mth = mth;
this.classGen = classGen; this.classGen = classGen;
this.annotationGen = classGen.getAnnotationGen(); this.annotationGen = classGen.getAnnotationGen();
this.nameGen = new NameGen(mth, classGen.isFallbackMode()); this.nameGen = new NameGen(mth, classGen);
} }
public ClassGen getClassGen() { public ClassGen getClassGen() {
@@ -68,7 +74,7 @@ public class MethodGen {
return mth; return mth;
} }
public boolean addDefinition(CodeWriter code) { public boolean addDefinition(ICodeWriter code) {
if (mth.getMethodInfo().isClassInit()) { if (mth.getMethodInfo().isClassInit()) {
code.attachDefinition(mth); code.attachDefinition(mth);
code.startLine("static"); code.startLine("static");
@@ -97,20 +103,31 @@ public class MethodGen {
if (clsAccFlags.isAnnotation()) { if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.PUBLIC); ai = ai.remove(AccessFlags.PUBLIC);
} }
if (mth.getMethodInfo().isConstructor() && mth.getParentClass().isEnum()) {
ai = ai.remove(AccessInfo.VISIBILITY_FLAGS);
}
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) { if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
CodeGenUtils.addRenamedComment(code, mth, mth.getName()); CodeGenUtils.addRenamedComment(code, mth, mth.getName());
} }
if (mth.contains(AFlag.INCONSISTENT_CODE)) { 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()); code.startLineWithNum(mth.getSourceLine());
code.add(ai.makeString()); code.add(ai.makeString(mth.checkCommentsLevel(CommentsLevel.INFO)));
if (Consts.DEBUG) { if (clsAccFlags.isInterface() && !mth.isNoCode() && !mth.getAccessFlags().isStatic()) {
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
}
if (clsAccFlags.isInterface() && !mth.isNoCode()) {
// add 'default' for method with code in interface // add 'default' for method with code in interface
code.add("default "); code.add("default ");
} }
@@ -137,7 +154,7 @@ public class MethodGen {
} else if (args.size() > 2) { } else if (args.size() > 2) {
args = args.subList(2, args.size()); args = args.subList(2, args.size());
} else { } else {
mth.addComment("JADX WARN: Incorrect number of args for enum constructor: " + args.size() + " (expected >= 2)"); mth.addWarnComment("Incorrect number of args for enum constructor: " + args.size() + " (expected >= 2)");
} }
} else if (mth.contains(AFlag.SKIP_FIRST_ARG)) { } else if (mth.contains(AFlag.SKIP_FIRST_ARG)) {
args = args.subList(1, args.size()); args = args.subList(1, args.size());
@@ -147,9 +164,9 @@ public class MethodGen {
annotationGen.addThrows(mth, code); annotationGen.addThrows(mth, code);
// add default value if in annotation class // add default value for annotation class
if (mth.getParentClass().getAccessFlags().isAnnotation()) { if (mth.getParentClass().getAccessFlags().isAnnotation()) {
EncodedValue def = annotationGen.getAnnotationDefaultValue(mth.getName()); EncodedValue def = annotationGen.getAnnotationDefaultValue(mth);
if (def != null) { if (def != null) {
code.add(" default "); code.add(" default ");
annotationGen.encodeValue(mth.root(), code, def); annotationGen.encodeValue(mth.root(), code, def);
@@ -158,25 +175,27 @@ public class MethodGen {
return true; return true;
} }
private void addOverrideAnnotation(CodeWriter code, MethodNode mth) { private void addOverrideAnnotation(ICodeWriter code, MethodNode mth) {
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE); MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr == null) { if (overrideAttr == null) {
return; return;
} }
code.startLine("@Override"); if (!overrideAttr.getBaseMethods().contains(mth)) {
code.add(" // "); code.startLine("@Override");
Iterator<IMethodDetails> it = overrideAttr.getOverrideList().iterator(); if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
while (it.hasNext()) { code.add(" // ");
IMethodDetails methodDetails = it.next(); code.add(Utils.listToString(overrideAttr.getOverrideList(), ", ",
code.add(methodDetails.getMethodInfo().getDeclClass().getAliasFullName()); md -> md.getMethodInfo().getDeclClass().getAliasFullName()));
if (it.hasNext()) {
code.add(", ");
} }
} }
if (Consts.DEBUG) {
code.startLine("// related by override: ");
code.add(Utils.listToString(overrideAttr.getRelatedMthNodes(), ", ", m -> m.getParentClass().getFullName()));
}
} }
private void addMethodArguments(CodeWriter code, List<RegisterArg> args) { private void addMethodArguments(ICodeWriter code, List<RegisterArg> args) {
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS); AnnotationMethodParamsAttr paramsAnnotation = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS);
int i = 0; int i = 0;
Iterator<RegisterArg> it = args.iterator(); Iterator<RegisterArg> it = args.iterator();
while (it.hasNext()) { while (it.hasNext()) {
@@ -184,7 +203,7 @@ public class MethodGen {
SSAVar ssaVar = mthArg.getSVar(); SSAVar ssaVar = mthArg.getSVar();
CodeVar var; CodeVar var;
if (ssaVar == null) { if (ssaVar == null) {
// null for abstract or interface methods // abstract or interface methods
var = CodeVar.fromMthArg(mthArg, classGen.isFallbackMode()); var = CodeVar.fromMthArg(mthArg, classGen.isFallbackMode());
} else { } else {
var = ssaVar.getCodeVar(); var = ssaVar.getCodeVar();
@@ -212,14 +231,18 @@ public class MethodGen {
classGen.useType(code, elType); classGen.useType(code, elType);
code.add("..."); code.add("...");
} else { } else {
mth.addComment("JADX INFO: Last argument in varargs method is not array: " + var); mth.addWarnComment("Last argument in varargs method is not array: " + var);
classGen.useType(code, argType); classGen.useType(code, argType);
} }
} else { } else {
classGen.useType(code, argType); classGen.useType(code, argType);
} }
code.add(' '); code.add(' ');
code.add(nameGen.assignArg(var)); String varName = nameGen.assignArg(var);
if (code.isMetadataSupported() && ssaVar != null /* for fallback mode */) {
code.attachDefinition(VarDeclareRef.get(mth, var));
}
code.add(varName);
i++; i++;
if (it.hasNext()) { if (it.hasNext()) {
@@ -228,7 +251,7 @@ public class MethodGen {
} }
} }
public void addInstructions(CodeWriter code) throws CodegenException { public void addInstructions(ICodeWriter code) throws CodegenException {
if (mth.root().getArgs().isFallbackMode()) { if (mth.root().getArgs().isFallbackMode()) {
addFallbackMethodCode(code, FALLBACK_MODE); addFallbackMethodCode(code, FALLBACK_MODE);
} else if (classGen.isFallbackMode()) { } else if (classGen.isFallbackMode()) {
@@ -238,29 +261,30 @@ public class MethodGen {
} }
} }
public void addRegionInsns(CodeWriter code) throws CodegenException { public void addRegionInsns(ICodeWriter code) throws CodegenException {
try { try {
RegionGen regionGen = new RegionGen(this); RegionGen regionGen = new RegionGen(this);
regionGen.makeRegion(code, mth.getRegion()); regionGen.makeRegion(code, mth.getRegion());
} catch (StackOverflowError | BootstrapMethodError e) { } catch (StackOverflowError | BootstrapMethodError e) {
mth.addError("Method code generation error", new JadxOverflowException("StackOverflow")); mth.addError("Method code generation error", new JadxOverflowException("StackOverflow"));
classGen.insertDecompilationProblems(code, mth); CodeGenUtils.addErrors(code, mth);
dumpInstructions(code); dumpInstructions(code);
} catch (Exception e) { } catch (Exception e) {
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) { if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw e; throw e;
} }
mth.addError("Method code generation error", e); mth.addError("Method code generation error", e);
classGen.insertDecompilationProblems(code, mth); CodeGenUtils.addErrors(code, mth);
dumpInstructions(code); dumpInstructions(code);
} }
} }
public void dumpInstructions(CodeWriter code) { public void dumpInstructions(ICodeWriter code) {
code.startLine("/*"); if (mth.checkCommentsLevel(CommentsLevel.ERROR)) {
addFallbackMethodCode(code, COMMENTED_DUMP); code.startLine("/*");
code.startLine("*/"); addFallbackMethodCode(code, COMMENTED_DUMP);
code.startLine("*/");
}
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ") code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(mth.getParentClass().getClassInfo().getAliasFullName()) .add(mth.getParentClass().getClassInfo().getAliasFullName())
.add('.') .add('.')
@@ -272,27 +296,46 @@ public class MethodGen {
.add("\");"); .add("\");");
} }
public void addFallbackMethodCode(CodeWriter code, FallbackOption fallbackOption) { public void addFallbackMethodCode(ICodeWriter code, FallbackOption fallbackOption) {
// load original instructions if (fallbackOption != FALLBACK_MODE) {
try { List<JadxError> errors = mth.getAll(AType.JADX_ERROR); // preserve error before unload
mth.unload(); try {
mth.load(); // load original instructions
for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) { mth.unload();
DepthTraversal.visit(visitor, mth); mth.load();
for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) {
DepthTraversal.visit(visitor, mth);
}
errors.forEach(err -> mth.addAttr(AType.JADX_ERROR, err));
} catch (Exception e) {
LOG.error("Error reload instructions in fallback mode:", e);
code.startLine("// Can't load method instructions: " + e.getMessage());
return;
} finally {
errors.forEach(err -> mth.addAttr(AType.JADX_ERROR, err));
} }
} catch (DecodeException e) {
LOG.error("Error reload instructions in fallback mode:", e);
code.startLine("// Can't load method instructions: " + e.getMessage());
return;
} }
InsnNode[] insnArr = mth.getInstructions(); InsnNode[] insnArr = mth.getInstructions();
if (insnArr == null) { if (insnArr == null) {
code.startLine("// Can't load method instructions."); code.startLine("// Can't load method instructions.");
return; return;
} }
if (insnArr.length > 100) { if (fallbackOption == COMMENTED_DUMP && mth.getCommentsLevel() != CommentsLevel.DEBUG) {
code.startLine("// Method dump skipped, instructions count: " + insnArr.length); long insnCountEstimate = Stream.of(insnArr)
return; .filter(Objects::nonNull)
.filter(insn -> insn.getType() != InsnType.NOP)
.count();
if (insnCountEstimate > 100) {
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;
}
} }
code.incIndent(); code.incIndent();
if (mth.getThisArg() != null) { if (mth.getThisArg() != null) {
@@ -308,15 +351,20 @@ public class MethodGen {
COMMENTED_DUMP COMMENTED_DUMP
} }
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) { public static void addFallbackInsns(ICodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
int startIndent = code.getIndent(); int startIndent = code.getIndent();
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true); InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
boolean attachInsns = mth.root().getArgs().isJsonOutput();
InsnNode prevInsn = null; InsnNode prevInsn = null;
for (InsnNode insn : insnArr) { for (InsnNode insn : insnArr) {
if (insn == null) { if (insn == null) {
continue; continue;
} }
if (insn.contains(AType.JADX_ERROR)) {
for (JadxError error : insn.getAll(AType.JADX_ERROR)) {
code.startLine("// ").add(error.getError());
}
continue;
}
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) { if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
code.decIndent(); code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ':'); code.startLine(getLabelName(insn.getOffset()) + ':');
@@ -332,11 +380,9 @@ public class MethodGen {
code.startLine("*/"); code.startLine("*/");
code.startLine("// "); code.startLine("// ");
} else { } else {
code.startLine(); code.startLineWithNum(insn.getSourceLine());
}
if (attachInsns) {
code.attachLineAnnotation(insn);
} }
InsnCodeOffset.attach(code, insn);
RegisterArg resArg = insn.getResult(); RegisterArg resArg = insn.getResult();
if (resArg != null) { if (resArg != null) {
ArgType varType = resArg.getInitType(); ArgType varType = resArg.getInitType();
@@ -350,10 +396,11 @@ public class MethodGen {
code.incIndent(); code.incIndent();
} }
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK); CatchAttr catchAttr = insn.get(AType.EXC_CATCH);
if (catchAttr != null) { if (catchAttr != null) {
code.add(" // " + catchAttr); code.add(" // " + catchAttr);
} }
CodeGenUtils.addCodeComments(code, mth, insn);
} catch (Exception e) { } catch (Exception e) {
LOG.debug("Error generate fallback instruction: ", e.getCause()); LOG.debug("Error generate fallback instruction: ", e.getCause());
code.setIndent(startIndent); code.setIndent(startIndent);
@@ -53,16 +53,26 @@ public class NameGen {
"java.lang.Exception", "exc"); "java.lang.Exception", "exc");
} }
public NameGen(MethodNode mth, boolean fallback) { public NameGen(MethodNode mth, ClassGen classGen) {
this.mth = mth; this.mth = mth;
this.fallback = fallback; this.fallback = classGen.isFallbackMode();
NameGen outerNameGen = classGen.getOuterNameGen();
if (outerNameGen != null) {
inheritUsedNames(outerNameGen);
}
addNamesUsedInClass(); addNamesUsedInClass();
} }
public void inheritUsedNames(NameGen otherNameGen) {
varNames.addAll(otherNameGen.varNames);
}
private void addNamesUsedInClass() { private void addNamesUsedInClass() {
ClassNode parentClass = mth.getParentClass(); ClassNode parentClass = mth.getParentClass();
for (FieldNode field : parentClass.getFields()) { for (FieldNode field : parentClass.getFields()) {
varNames.add(field.getAlias()); if (field.isStatic()) {
varNames.add(field.getAlias());
}
} }
for (ClassNode innerClass : parentClass.getInnerClasses()) { for (ClassNode innerClass : parentClass.getInnerClasses()) {
varNames.add(innerClass.getClassInfo().getAliasShortName()); varNames.add(innerClass.getClassInfo().getAliasShortName());
@@ -127,9 +137,6 @@ public class NameGen {
if (!NameMapper.isValidAndPrintable(name)) { if (!NameMapper.isValidAndPrintable(name)) {
name = getFallbackName(var); name = getFallbackName(var);
} }
if (Consts.DEBUG) {
name += '_' + getFallbackName(var);
}
return name; return name;
} }
@@ -155,8 +162,7 @@ public class NameGen {
InsnNode assignInsn = assignArg.getParentInsn(); InsnNode assignInsn = assignArg.getParentInsn();
if (assignInsn != null) { if (assignInsn != null) {
String name = makeNameFromInsn(assignInsn); String name = makeNameFromInsn(assignInsn);
if (name != null && !NameMapper.isReserved(name)) { if (name != null && NameMapper.isValidAndPrintable(name)) {
assignArg.setName(name);
return name; return name;
} }
} }
@@ -195,7 +201,11 @@ public class NameGen {
return vName; return vName;
} }
if (shortName != null) { if (shortName != null) {
return StringUtils.escape(shortName.toLowerCase()); String lower = StringUtils.escape(shortName.toLowerCase());
if (shortName.equals(lower)) {
return lower + "Var";
}
return lower;
} }
} }
return StringUtils.escape(type.toString()); return StringUtils.escape(type.toString());
@@ -8,9 +8,14 @@ import java.util.Objects;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr; import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.attributes.nodes.LoopLabelAttr;
@@ -25,7 +30,6 @@ import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IBlock; import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.Region; import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.SwitchRegion; import jadx.core.dex.regions.SwitchRegion;
@@ -40,7 +44,9 @@ import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.regions.loops.LoopType; import jadx.core.dex.regions.loops.LoopType;
import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.RegionUtils; import jadx.core.utils.RegionUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -51,56 +57,30 @@ public class RegionGen extends InsnGen {
super(mgen, false); super(mgen, false);
} }
public void makeRegion(CodeWriter code, IContainer cont) throws CodegenException { public void makeRegion(ICodeWriter code, IContainer cont) throws CodegenException {
if (cont instanceof IBlock) { declareVars(code, cont);
makeSimpleBlock((IBlock) cont, code); cont.generate(this, code);
} else if (cont instanceof IRegion) {
if (cont instanceof Region) {
makeSimpleRegion(code, (Region) cont);
} else {
declareVars(code, cont);
if (cont instanceof IfRegion) {
makeIf((IfRegion) cont, code, true);
} else if (cont instanceof SwitchRegion) {
makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code);
} else if (cont instanceof TryCatchRegion) {
makeTryCatch((TryCatchRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code);
}
}
} else {
throw new CodegenException("Not processed container: " + cont);
}
} }
private void declareVars(CodeWriter code, IContainer cont) { private void declareVars(ICodeWriter code, IContainer cont) {
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES); DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
if (declVars != null) { if (declVars != null) {
for (CodeVar v : declVars.getVars()) { for (CodeVar v : declVars.getVars()) {
code.startLine(); code.startLine();
declareVar(code, v); declareVar(code, v);
code.add(';'); code.add(';');
CodeGenUtils.addCodeComments(code, mth, v.getAnySsaVar().getAssign());
} }
} }
} }
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException { private void makeRegionIndent(ICodeWriter code, IContainer region) throws CodegenException {
declareVars(code, region);
for (IContainer c : region.getSubBlocks()) {
makeRegion(code, c);
}
}
public void makeRegionIndent(CodeWriter code, IContainer region) throws CodegenException {
code.incIndent(); code.incIndent();
makeRegion(code, region); makeRegion(code, region);
code.decIndent(); code.decIndent();
} }
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException { public void makeSimpleBlock(IBlock block, ICodeWriter code) throws CodegenException {
if (block.contains(AFlag.DONT_GENERATE)) { if (block.contains(AFlag.DONT_GENERATE)) {
return; return;
} }
@@ -116,22 +96,12 @@ public class RegionGen extends InsnGen {
} }
} }
private void makeIf(IfRegion region, CodeWriter code, boolean newLine) throws CodegenException { public void makeIf(IfRegion region, ICodeWriter code, boolean newLine) throws CodegenException {
if (newLine) { if (newLine) {
code.startLineWithNum(region.getSourceLine()); code.startLineWithNum(region.getSourceLine());
} else { } else {
code.attachSourceLine(region.getSourceLine()); code.attachSourceLine(region.getSourceLine());
} }
if (attachInsns) {
List<BlockNode> conditionBlocks = region.getConditionBlocks();
if (!conditionBlocks.isEmpty()) {
BlockNode blockNode = conditionBlocks.get(0);
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
if (lastInsn != null) {
code.attachLineAnnotation(lastInsn);
}
}
}
boolean comment = region.contains(AFlag.COMMENT_OUT); boolean comment = region.contains(AFlag.COMMENT_OUT);
if (comment) { if (comment) {
code.add("// "); code.add("// ");
@@ -140,6 +110,15 @@ public class RegionGen extends InsnGen {
code.add("if ("); code.add("if (");
new ConditionGen(this).add(code, region.getCondition()); new ConditionGen(this).add(code, region.getCondition());
code.add(") {"); code.add(") {");
if (code.isMetadataSupported()) {
List<BlockNode> conditionBlocks = region.getConditionBlocks();
if (!conditionBlocks.isEmpty()) {
BlockNode blockNode = conditionBlocks.get(0);
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
InsnCodeOffset.attach(code, lastInsn);
CodeGenUtils.addCodeComments(code, mth, lastInsn);
}
}
makeRegionIndent(code, region.getThenRegion()); makeRegionIndent(code, region.getThenRegion());
if (comment) { if (comment) {
code.startLine("// }"); code.startLine("// }");
@@ -166,7 +145,7 @@ public class RegionGen extends InsnGen {
/** /**
* Connect if-else-if block * Connect if-else-if block
*/ */
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException { private boolean connectElseIf(ICodeWriter code, IContainer els) throws CodegenException {
if (els.contains(AFlag.ELSE_IF_CHAIN) && els instanceof Region) { if (els.contains(AFlag.ELSE_IF_CHAIN) && els instanceof Region) {
List<IContainer> subBlocks = ((Region) els).getSubBlocks(); List<IContainer> subBlocks = ((Region) els).getSubBlocks();
if (subBlocks.size() == 1) { if (subBlocks.size() == 1) {
@@ -181,82 +160,95 @@ public class RegionGen extends InsnGen {
return false; return false;
} }
private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException { public void makeLoop(LoopRegion region, ICodeWriter code) throws CodegenException {
code.startLineWithNum(region.getSourceLine());
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL); LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
if (labelAttr != null) { if (labelAttr != null) {
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':'); code.add(mgen.getNameGen().getLoopLabel(labelAttr)).add(": ");
} }
IfCondition condition = region.getCondition(); IfCondition condition = region.getCondition();
if (condition == null) { if (condition == null) {
// infinite loop // infinite loop
code.startLine("while (true) {"); code.add("while (true) {");
makeRegionIndent(code, region.getBody()); makeRegionIndent(code, region.getBody());
code.startLine('}'); code.startLine('}');
return code; return;
} }
InsnNode condInsn = condition.getFirstInsn();
InsnCodeOffset.attach(code, condInsn);
ConditionGen conditionGen = new ConditionGen(this); ConditionGen conditionGen = new ConditionGen(this);
LoopType type = region.getType(); LoopType type = region.getType();
if (type != null) { if (type != null) {
if (type instanceof ForLoop) { if (type instanceof ForLoop) {
ForLoop forLoop = (ForLoop) type; ForLoop forLoop = (ForLoop) type;
code.startLine("for ("); code.add("for (");
makeInsn(forLoop.getInitInsn(), code, Flags.INLINE); makeInsn(forLoop.getInitInsn(), code, Flags.INLINE);
code.add("; "); code.add("; ");
conditionGen.add(code, condition); conditionGen.add(code, condition);
code.add("; "); code.add("; ");
makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE); makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE);
code.add(") {"); code.add(") {");
CodeGenUtils.addCodeComments(code, mth, condInsn);
makeRegionIndent(code, region.getBody()); makeRegionIndent(code, region.getBody());
code.startLine('}'); code.startLine('}');
return code; return;
} }
if (type instanceof ForEachLoop) { if (type instanceof ForEachLoop) {
ForEachLoop forEachLoop = (ForEachLoop) type; ForEachLoop forEachLoop = (ForEachLoop) type;
code.startLine("for ("); code.add("for (");
declareVar(code, forEachLoop.getVarArg()); declareVar(code, forEachLoop.getVarArg());
code.add(" : "); code.add(" : ");
addArg(code, forEachLoop.getIterableArg(), false); addArg(code, forEachLoop.getIterableArg(), false);
code.add(") {"); code.add(") {");
CodeGenUtils.addCodeComments(code, mth, condInsn);
makeRegionIndent(code, region.getBody()); makeRegionIndent(code, region.getBody());
code.startLine('}'); code.startLine('}');
return code; return;
} }
throw new JadxRuntimeException("Unknown loop type: " + type.getClass()); throw new JadxRuntimeException("Unknown loop type: " + type.getClass());
} }
if (region.isConditionAtEnd()) { if (region.isConditionAtEnd()) {
code.startLine("do {"); code.add("do {");
CodeGenUtils.addCodeComments(code, mth, condInsn);
makeRegionIndent(code, region.getBody()); makeRegionIndent(code, region.getBody());
code.startLineWithNum(region.getConditionSourceLine()); code.startLineWithNum(region.getSourceLine());
code.add("} while ("); code.add("} while (");
conditionGen.add(code, condition); conditionGen.add(code, condition);
code.add(");"); code.add(");");
} else { } else {
code.startLineWithNum(region.getConditionSourceLine());
code.add("while ("); code.add("while (");
conditionGen.add(code, condition); conditionGen.add(code, condition);
code.add(") {"); code.add(") {");
CodeGenUtils.addCodeComments(code, mth, condInsn);
makeRegionIndent(code, region.getBody()); makeRegionIndent(code, region.getBody());
code.startLine('}'); code.startLine('}');
} }
return code;
} }
private void makeSynchronizedRegion(SynchronizedRegion cont, CodeWriter code) throws CodegenException { public void makeSynchronizedRegion(SynchronizedRegion cont, ICodeWriter code) throws CodegenException {
code.startLine("synchronized ("); code.startLine("synchronized (");
addArg(code, cont.getEnterInsn().getArg(0)); InsnNode monitorEnterInsn = cont.getEnterInsn();
addArg(code, monitorEnterInsn.getArg(0));
code.add(") {"); code.add(") {");
InsnCodeOffset.attach(code, monitorEnterInsn);
CodeGenUtils.addCodeComments(code, mth, monitorEnterInsn);
makeRegionIndent(code, cont.getRegion()); makeRegionIndent(code, cont.getRegion());
code.startLine('}'); code.startLine('}');
} }
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException { public void makeSwitch(SwitchRegion sw, ICodeWriter code) throws CodegenException {
SwitchInsn insn = (SwitchInsn) BlockUtils.getLastInsn(sw.getHeader()); SwitchInsn insn = (SwitchInsn) BlockUtils.getLastInsn(sw.getHeader());
Objects.requireNonNull(insn, "Switch insn not found in header"); Objects.requireNonNull(insn, "Switch insn not found in header");
InsnArg arg = insn.getArg(0); InsnArg arg = insn.getArg(0);
code.startLine("switch ("); code.startLine("switch (");
addArg(code, arg, false); addArg(code, arg, false);
code.add(") {"); code.add(") {");
InsnCodeOffset.attach(code, insn);
CodeGenUtils.addCodeComments(code, mth, insn);
code.incIndent(); code.incIndent();
for (CaseInfo caseInfo : sw.getCases()) { for (CaseInfo caseInfo : sw.getCases()) {
@@ -275,22 +267,20 @@ public class RegionGen extends InsnGen {
} }
code.decIndent(); code.decIndent();
code.startLine('}'); code.startLine('}');
return code;
} }
private void addCaseKey(CodeWriter code, InsnArg arg, Object k) { private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) {
if (k instanceof FieldNode) { if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k; FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) { if (fn.getParentClass().isEnum()) {
code.add(fn.getAlias()); code.add(fn.getAlias());
} else { } else {
staticField(code, fn.getFieldInfo()); staticField(code, fn.getFieldInfo());
// print original value, sometimes replaced with incorrect field if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT); // print original value, sometimes replaced with incorrect field
if (valueAttr != null && valueAttr.getValueType() == FieldInitAttr.InitType.CONST) { EncodedValue constVal = fn.get(JadxAttrType.CONSTANT_VALUE);
Object value = valueAttr.getEncodedValue(); if (constVal != null && constVal.getValue() != null) {
if (value != null) { code.add(" /* ").add(constVal.getValue().toString()).add(" */");
code.add(" /*").add(value.toString()).add("*/");
} }
} }
} }
@@ -301,8 +291,13 @@ public class RegionGen extends InsnGen {
} }
} }
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException { public void makeTryCatch(TryCatchRegion region, ICodeWriter code) throws CodegenException {
code.startLine("try {"); code.startLine("try {");
InsnNode insn = BlockUtils.getFirstInsn(Utils.first(region.getTryCatchBlock().getBlocks()));
InsnCodeOffset.attach(code, insn);
CodeGenUtils.addCodeComments(code, mth, insn);
makeRegionIndent(code, region.getTryRegion()); makeRegionIndent(code, region.getTryRegion());
// TODO: move search of 'allHandler' to 'TryCatchRegion' // TODO: move search of 'allHandler' to 'TryCatchRegion'
ExceptionHandler allHandler = null; ExceptionHandler allHandler = null;
@@ -328,7 +323,7 @@ public class RegionGen extends InsnGen {
code.startLine('}'); code.startLine('}');
} }
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler) throws CodegenException { private void makeCatchBlock(ICodeWriter code, ExceptionHandler handler) throws CodegenException {
IContainer region = handler.getHandlerRegion(); IContainer region = handler.getHandlerRegion();
if (region == null) { if (region == null) {
return; return;
@@ -351,14 +346,21 @@ public class RegionGen extends InsnGen {
if (arg == null) { if (arg == null) {
code.add("unknown"); // throwing exception is too late at this point code.add("unknown"); // throwing exception is too late at this point
} else if (arg instanceof RegisterArg) { } else if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg; CodeVar codeVar = ((RegisterArg) arg).getSVar().getCodeVar();
code.add(mgen.getNameGen().assignArg(reg.getSVar().getCodeVar())); if (code.isMetadataSupported()) {
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
}
code.add(mgen.getNameGen().assignArg(codeVar));
} else if (arg instanceof NamedArg) { } else if (arg instanceof NamedArg) {
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg)); code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
} else { } else {
throw new JadxRuntimeException("Unexpected arg type in catch block: " + arg + ", class: " + arg.getClass().getSimpleName()); throw new JadxRuntimeException("Unexpected arg type in catch block: " + arg + ", class: " + arg.getClass().getSimpleName());
} }
code.add(") {"); code.add(") {");
InsnCodeOffset.attach(code, handler.getHandlerOffset());
CodeGenUtils.addCodeComments(code, mth, handler.getHandlerBlock());
makeRegionIndent(code, region); makeRegionIndent(code, region);
} }
} }
@@ -1,5 +1,6 @@
package jadx.core.codegen; package jadx.core.codegen;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -97,6 +98,43 @@ public class TypeGen {
} }
} }
@Nullable
public static String literalToRawString(LiteralArg arg) {
ArgType type = arg.getType();
if (type == null) {
return null;
}
long lit = arg.getLiteral();
switch (type.getPrimitiveType()) {
case BOOLEAN:
return lit == 0 ? "false" : "true";
case CHAR:
return String.valueOf((char) lit);
case BYTE:
case SHORT:
case INT:
case LONG:
return Long.toString(lit);
case FLOAT:
return Float.toString(Float.intBitsToFloat((int) lit));
case DOUBLE:
return Double.toString(Double.longBitsToDouble(lit));
case OBJECT:
case ARRAY:
if (lit != 0) {
LOG.warn("Wrong object literal: {} for type: {}", lit, type);
return Long.toString(lit);
}
return "null";
default:
return null;
}
}
public static String formatShort(long l, boolean cast) { public static String formatShort(long l, boolean cast) {
if (l == Short.MAX_VALUE) { if (l == Short.MAX_VALUE) {
return "Short.MAX_VALUE"; return "Short.MAX_VALUE";
@@ -14,9 +14,12 @@ import com.google.gson.GsonBuilder;
import jadx.api.CodePosition; import jadx.api.CodePosition;
import jadx.api.ICodeInfo; import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.SimpleCodeWriter;
import jadx.core.codegen.ClassGen; import jadx.core.codegen.ClassGen;
import jadx.core.codegen.CodeWriter;
import jadx.core.codegen.MethodGen; import jadx.core.codegen.MethodGen;
import jadx.core.codegen.json.cls.JsonClass; import jadx.core.codegen.json.cls.JsonClass;
import jadx.core.codegen.json.cls.JsonCodeLine; import jadx.core.codegen.json.cls.JsonCodeLine;
@@ -27,7 +30,6 @@ import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils; import jadx.core.utils.CodeGenUtils;
@@ -82,9 +84,8 @@ public class JsonCodeGen {
jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), this::getTypeAlias)); jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), this::getTypeAlias));
} }
CodeWriter cw = new CodeWriter(); ICodeWriter cw = new SimpleCodeWriter();
CodeGenUtils.addComments(cw, cls); CodeGenUtils.addErrorsAndComments(cw, cls);
classGen.insertDecompilationProblems(cw, cls);
classGen.addClassDeclaration(cw); classGen.addClassDeclaration(cw);
jsonCls.setDeclaration(cw.getCodeStr()); jsonCls.setDeclaration(cw.getCodeStr());
@@ -127,11 +128,10 @@ public class JsonCodeGen {
jsonField.setAlias(field.getAlias()); jsonField.setAlias(field.getAlias());
} }
CodeWriter cw = new CodeWriter(); ICodeWriter cw = new SimpleCodeWriter();
classGen.addField(cw, field); classGen.addField(cw, field);
jsonField.setDeclaration(cw.getCodeStr()); jsonField.setDeclaration(cw.getCodeStr());
jsonField.setAccessFlags(field.getAccessFlags().rawValue()); jsonField.setAccessFlags(field.getAccessFlags().rawValue());
jsonCls.getFields().add(jsonField); jsonCls.getFields().add(jsonField);
} }
} }
@@ -152,7 +152,7 @@ public class JsonCodeGen {
jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), this::getTypeAlias)); jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), this::getTypeAlias));
MethodGen mthGen = new MethodGen(classGen, mth); MethodGen mthGen = new MethodGen(classGen, mth);
CodeWriter cw = new CodeWriter(); ICodeWriter cw = new AnnotatedCodeWriter();
mthGen.addDefinition(cw); mthGen.addDefinition(cw);
jsonMth.setDeclaration(cw.getCodeStr()); jsonMth.setDeclaration(cw.getCodeStr());
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue()); jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
@@ -167,7 +167,7 @@ public class JsonCodeGen {
return Collections.emptyList(); return Collections.emptyList();
} }
CodeWriter cw = new CodeWriter(); ICodeWriter cw = mth.root().makeCodeWriter();
try { try {
mthGen.addInstructions(cw); mthGen.addInstructions(cw);
} catch (Exception e) { } catch (Exception e) {
@@ -179,7 +179,7 @@ public class JsonCodeGen {
return Collections.emptyList(); return Collections.emptyList();
} }
String[] lines = codeStr.split(CodeWriter.NL); String[] lines = codeStr.split(ICodeWriter.NL);
Map<Integer, Integer> lineMapping = code.getLineMapping(); Map<Integer, Integer> lineMapping = code.getLineMapping();
Map<CodePosition, Object> annotations = code.getAnnotations(); Map<CodePosition, Object> annotations = code.getAnnotations();
long mthCodeOffset = mth.getMethodCodeOffset() + 16; long mthCodeOffset = mth.getMethodCodeOffset() + 16;
@@ -192,9 +192,9 @@ public class JsonCodeGen {
JsonCodeLine jsonCodeLine = new JsonCodeLine(); JsonCodeLine jsonCodeLine = new JsonCodeLine();
jsonCodeLine.setCode(codeLine); jsonCodeLine.setCode(codeLine);
jsonCodeLine.setSourceLine(lineMapping.get(line)); jsonCodeLine.setSourceLine(lineMapping.get(line));
Object obj = annotations.get(new CodePosition(line, 0)); Object obj = annotations.get(new CodePosition(line));
if (obj instanceof InsnNode) { if (obj instanceof InsnCodeOffset) {
long offset = ((InsnNode) obj).getOffset(); long offset = ((InsnCodeOffset) obj).getOffset();
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2)); jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
} }
codeLines.add(jsonCodeLine); codeLines.add(jsonCodeLine);
@@ -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;
}
}
@@ -1,9 +1,11 @@
package jadx.core.deobf; package jadx.core.deobf;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@@ -13,35 +15,57 @@ import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.files.FileUtils;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
class DeobfPresets { public class DeobfPresets {
private static final Logger LOG = LoggerFactory.getLogger(DeobfPresets.class); private static final Logger LOG = LoggerFactory.getLogger(DeobfPresets.class);
private static final Charset MAP_FILE_CHARSET = UTF_8; private static final Charset MAP_FILE_CHARSET = UTF_8;
private final Deobfuscator deobfuscator;
private final Path deobfMapFile; private final Path deobfMapFile;
private final Map<String, String> pkgPresetMap = new HashMap<>();
private final Map<String, String> clsPresetMap = new HashMap<>(); private final Map<String, String> clsPresetMap = new HashMap<>();
private final Map<String, String> fldPresetMap = new HashMap<>(); private final Map<String, String> fldPresetMap = new HashMap<>();
private final Map<String, String> mthPresetMap = new HashMap<>(); private final Map<String, String> mthPresetMap = new HashMap<>();
public DeobfPresets(Deobfuscator deobfuscator, Path deobfMapFile) { public static DeobfPresets build(RootNode root) {
this.deobfuscator = deobfuscator; Path deobfMapPath = getPathDeobfMapPath(root);
if (root.getArgs().getDeobfuscationMapFileMode() != DeobfuscationMapFileMode.IGNORE) {
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
}
return new DeobfPresets(deobfMapPath);
}
private static Path getPathDeobfMapPath(RootNode root) {
JadxArgs jadxArgs = root.getArgs();
File deobfMapFile = jadxArgs.getDeobfuscationMapFile();
if (deobfMapFile != null) {
return deobfMapFile.toPath();
}
Path inputFilePath = jadxArgs.getInputFiles().get(0).toPath().toAbsolutePath();
String baseName = FileUtils.getPathBaseName(inputFilePath);
return inputFilePath.getParent().resolve(baseName + ".jobf");
}
private DeobfPresets(Path deobfMapFile) {
this.deobfMapFile = deobfMapFile; this.deobfMapFile = deobfMapFile;
} }
/** /**
* Loads deobfuscator presets * Loads deobfuscator presets
*/ */
public void load() { public boolean load() {
if (!Files.exists(deobfMapFile)) { if (!Files.exists(deobfMapFile)) {
return; return false;
} }
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath()); LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
try { try {
@@ -57,18 +81,28 @@ class DeobfPresets {
} }
String origName = va[0]; String origName = va[0];
String alias = va[1]; String alias = va[1];
if (l.startsWith("p ")) { switch (l.charAt(0)) {
deobfuscator.addPackagePreset(origName, alias); case 'p':
} else if (l.startsWith("c ")) { pkgPresetMap.put(origName, alias);
clsPresetMap.put(origName, alias); break;
} else if (l.startsWith("f ")) { case 'c':
fldPresetMap.put(origName, alias); clsPresetMap.put(origName, alias);
} else if (l.startsWith("m ")) { break;
mthPresetMap.put(origName, alias); case 'f':
fldPresetMap.put(origName, alias);
break;
case 'm':
mthPresetMap.put(origName, alias);
break;
case 'v':
// deprecated
break;
} }
} }
} catch (IOException e) { return true;
} catch (Exception e) {
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e); LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
return false;
} }
} }
@@ -80,75 +114,50 @@ class DeobfPresets {
return v; return v;
} }
public void save(boolean forceSave) { public void save() throws IOException {
try {
if (Files.exists(deobfMapFile)) {
if (forceSave) {
dumpMapping();
} else {
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
deobfMapFile.toAbsolutePath());
}
} else {
dumpMapping();
}
} catch (IOException e) {
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
}
}
/**
* Saves DefaultDeobfuscator presets
*/
private void dumpMapping() throws IOException {
List<String> list = new ArrayList<>(); List<String> list = new ArrayList<>();
// packages for (Map.Entry<String, String> pkgEntry : pkgPresetMap.entrySet()) {
for (PackageNode p : deobfuscator.getRootPackage().getInnerPackages()) { list.add(String.format("p %s = %s", pkgEntry.getKey(), pkgEntry.getValue()));
for (PackageNode pp : p.getInnerPackages()) {
dfsPackageName(list, p.getName(), pp);
}
if (p.hasAlias()) {
list.add(String.format("p %s = %s", p.getName(), p.getAlias()));
}
} }
// classes for (Map.Entry<String, String> clsEntry : clsPresetMap.entrySet()) {
for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) { list.add(String.format("c %s = %s", clsEntry.getKey(), clsEntry.getValue()));
if (deobfClsInfo.getAlias() != null) {
list.add(String.format("c %s = %s",
deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias()));
}
} }
for (FieldInfo fld : deobfuscator.getFldMap().keySet()) { for (Map.Entry<String, String> fldEntry : fldPresetMap.entrySet()) {
list.add(String.format("f %s = %s", fld.getRawFullId(), fld.getAlias())); list.add(String.format("f %s = %s", fldEntry.getKey(), fldEntry.getValue()));
} }
for (MethodInfo mth : deobfuscator.getMthMap().keySet()) { for (Map.Entry<String, String> mthEntry : mthPresetMap.entrySet()) {
list.add(String.format("m %s = %s", mth.getRawFullId(), mth.getAlias())); list.add(String.format("m %s = %s", mthEntry.getKey(), mthEntry.getValue()));
} }
Collections.sort(list); Collections.sort(list);
Files.write(deobfMapFile, list, MAP_FILE_CHARSET); if (list.isEmpty()) {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile); LOG.debug("Deobfuscation map is empty, not saving it");
} }
} return;
private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
for (PackageNode pp : node.getInnerPackages()) {
dfsPackageName(list, prefix + '.' + node.getName(), pp);
}
if (node.hasAlias()) {
list.add(String.format("p %s.%s = %s", prefix, node.getName(), node.getAlias()));
} }
Files.write(deobfMapFile, list, MAP_FILE_CHARSET,
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
LOG.info("Deobfuscation map file saved as: {}", deobfMapFile);
} }
public String getForCls(ClassInfo cls) { public String getForCls(ClassInfo cls) {
if (clsPresetMap.isEmpty()) {
return null;
}
return clsPresetMap.get(cls.makeRawFullName()); return clsPresetMap.get(cls.makeRawFullName());
} }
public String getForFld(FieldInfo fld) { public String getForFld(FieldInfo fld) {
if (fldPresetMap.isEmpty()) {
return null;
}
return fldPresetMap.get(fld.getRawFullId()); return fldPresetMap.get(fld.getRawFullId());
} }
public String getForMth(MethodInfo mth) { public String getForMth(MethodInfo mth) {
if (mthPresetMap.isEmpty()) {
return null;
}
return mthPresetMap.get(mth.getRawFullId()); return mthPresetMap.get(mth.getRawFullId());
} }
@@ -158,6 +167,14 @@ class DeobfPresets {
mthPresetMap.clear(); mthPresetMap.clear();
} }
public Path getDeobfMapFile() {
return deobfMapFile;
}
public Map<String, String> getPkgPresetMap() {
return pkgPresetMap;
}
public Map<String, String> getClsPresetMap() { public Map<String, String> getClsPresetMap() {
return clsPresetMap; return clsPresetMap;
} }
@@ -1,14 +1,13 @@
package jadx.core.deobf; package jadx.core.deobf;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.NavigableSet;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
@@ -17,9 +16,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.SourceFileAttr; import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
@@ -46,13 +48,12 @@ public class Deobfuscator {
private final Map<FieldInfo, String> fldMap = new HashMap<>(); private final Map<FieldInfo, String> fldMap = new HashMap<>();
private final Map<MethodInfo, String> mthMap = new HashMap<>(); private final Map<MethodInfo, String> mthMap = new HashMap<>();
private final Map<MethodInfo, OverridedMethodsNode> ovrdMap = new HashMap<>();
private final List<OverridedMethodsNode> ovrd = new ArrayList<>();
private final PackageNode rootPackage = new PackageNode(""); private final PackageNode rootPackage = new PackageNode("");
private final Set<String> pkgSet = new TreeSet<>(); private final Set<String> pkgSet = new TreeSet<>();
private final Set<String> reservedClsNames = new HashSet<>(); private final Set<String> reservedClsNames = new HashSet<>();
private final NavigableSet<MethodNode> mthProcessQueue = new TreeSet<>();
private final int maxLength; private final int maxLength;
private final int minLength; private final int minLength;
private final boolean useSourceNameAsAlias; private final boolean useSourceNameAsAlias;
@@ -63,28 +64,87 @@ public class Deobfuscator {
private int fldIndex = 0; private int fldIndex = 0;
private int mthIndex = 0; private int mthIndex = 0;
public Deobfuscator(JadxArgs args, RootNode root, Path deobfMapFile) { public Deobfuscator(RootNode root) {
this.args = args;
this.root = root; this.root = root;
this.args = root.getArgs();
this.minLength = args.getDeobfuscationMinLength(); this.minLength = args.getDeobfuscationMinLength();
this.maxLength = args.getDeobfuscationMaxLength(); this.maxLength = args.getDeobfuscationMaxLength();
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias(); this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
this.parseKotlinMetadata = args.isParseKotlinMetadata(); this.parseKotlinMetadata = args.isParseKotlinMetadata();
this.deobfPresets = new DeobfPresets(this, deobfMapFile); this.deobfPresets = DeobfPresets.build(root);
} }
public void execute() { public void execute() {
if (!args.isDeobfuscationForceSave()) { if (args.getDeobfuscationMapFileMode().shouldRead()) {
deobfPresets.load(); if (deobfPresets.load()) {
initIndexes(); for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
}
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
initIndexes();
}
} }
process(); process();
} }
public void savePresets() { public void savePresets() {
deobfPresets.save(args.isDeobfuscationForceSave()); DeobfuscationMapFileMode mode = args.getDeobfuscationMapFileMode();
if (!mode.shouldWrite()) {
return;
}
Path deobfMapFile = deobfPresets.getDeobfMapFile();
if (mode == DeobfuscationMapFileMode.READ_OR_SAVE && Files.exists(deobfMapFile)) {
return;
}
try {
deobfPresets.clear();
fillDeobfPresets();
deobfPresets.save();
} catch (Exception e) {
LOG.error("Failed to save deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
}
}
private void fillDeobfPresets() {
for (PackageNode p : getRootPackage().getInnerPackages()) {
for (PackageNode pp : p.getInnerPackages()) {
dfsPackageName(p.getName(), pp);
}
if (p.hasAlias()) {
deobfPresets.getPkgPresetMap().put(p.getName(), p.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.getFldPresetMap().put(methodInfo.getRawFullId(), methodInfo.getAlias());
}
}
}
}
private void dfsPackageName(String prefix, PackageNode node) {
for (PackageNode pp : node.getInnerPackages()) {
dfsPackageName(prefix + '.' + node.getName(), pp);
}
if (node.hasAlias()) {
deobfPresets.getPkgPresetMap().put(node.getName(), node.getAlias());
}
} }
public void clear() { public void clear() {
@@ -92,9 +152,6 @@ public class Deobfuscator {
clsMap.clear(); clsMap.clear();
fldMap.clear(); fldMap.clear();
mthMap.clear(); mthMap.clear();
ovrd.clear();
ovrdMap.clear();
} }
private void initIndexes() { private void initIndexes() {
@@ -121,99 +178,12 @@ public class Deobfuscator {
for (ClassNode cls : root.getClasses()) { for (ClassNode cls : root.getClasses()) {
processClass(cls); processClass(cls);
} }
postProcess(); while (true) {
} MethodNode next = mthProcessQueue.pollLast();
if (next == null) {
private void postProcess() { break;
int id = 1;
for (OverridedMethodsNode o : ovrd) {
boolean aliasFromPreset = false;
String aliasToUse = null;
for (MethodInfo mth : o.getMethods()) {
if (mth.isAliasFromPreset()) {
aliasToUse = mth.getAlias();
aliasFromPreset = true;
}
}
for (MethodInfo mth : o.getMethods()) {
if (aliasToUse == null) {
if (mth.hasAlias() && !mth.isAliasFromPreset()) {
mth.setAlias(String.format("mo%d%s", id, prepareNamePart(mth.getName())));
}
aliasToUse = mth.getAlias();
}
mth.setAlias(aliasToUse);
mth.setAliasFromPreset(aliasFromPreset);
}
id++;
}
}
private void resolveOverriding(MethodNode mth) {
Set<ClassNode> clsParents = new LinkedHashSet<>();
collectClassHierarchy(mth.getParentClass(), clsParents);
String mthSignature = mth.getMethodInfo().makeSignature(false);
Set<MethodInfo> overrideSet = new LinkedHashSet<>();
for (ClassNode classNode : clsParents) {
MethodInfo methodInfo = getMthOverride(classNode.getMethods(), mthSignature);
if (methodInfo != null) {
overrideSet.add(methodInfo);
}
}
if (overrideSet.isEmpty()) {
return;
}
OverridedMethodsNode overrideNode = getOverrideMethodsNode(overrideSet);
if (overrideNode == null) {
overrideNode = new OverridedMethodsNode(overrideSet);
ovrd.add(overrideNode);
}
for (MethodInfo overrideMth : overrideSet) {
if (!ovrdMap.containsKey(overrideMth)) {
ovrdMap.put(overrideMth, overrideNode);
overrideNode.add(overrideMth);
}
}
}
private OverridedMethodsNode getOverrideMethodsNode(Set<MethodInfo> overrideSet) {
for (MethodInfo overrideMth : overrideSet) {
OverridedMethodsNode node = ovrdMap.get(overrideMth);
if (node != null) {
return node;
}
}
return null;
}
private MethodInfo getMthOverride(List<MethodNode> methods, String mthSignature) {
for (MethodNode m : methods) {
MethodInfo mthInfo = m.getMethodInfo();
if (mthInfo.getShortId().startsWith(mthSignature)) {
return mthInfo;
}
}
return null;
}
private void collectClassHierarchy(ClassNode cls, Set<ClassNode> collected) {
boolean added = collected.add(cls);
if (added) {
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
ClassNode superNode = cls.root().resolveClass(superClass);
if (superNode != null) {
collectClassHierarchy(superNode, collected);
}
}
for (ArgType argType : cls.getInterfaces()) {
ClassNode interfaceNode = cls.root().resolveClass(argType);
if (interfaceNode != null) {
collectClassHierarchy(interfaceNode, collected);
}
} }
renameMethod(next);
} }
} }
@@ -242,9 +212,8 @@ public class Deobfuscator {
} }
renameField(field); renameField(field);
} }
for (MethodNode mth : cls.getMethods()) { mthProcessQueue.addAll(cls.getMethods());
renameMethod(mth);
}
for (ClassNode innerCls : cls.getInnerClasses()) { for (ClassNode innerCls : cls.getInnerClasses()) {
processClass(innerCls); processClass(innerCls);
} }
@@ -265,20 +234,35 @@ public class Deobfuscator {
private void renameMethod(MethodNode mth) { private void renameMethod(MethodNode mth) {
String alias = getMethodAlias(mth); String alias = getMethodAlias(mth);
if (alias != null) { if (alias != null) {
mth.getMethodInfo().setAlias(alias); applyMethodAlias(mth, alias);
}
if (mth.isVirtual()) {
resolveOverriding(mth);
} }
} }
public void forceRenameMethod(MethodNode mth) { public void forceRenameMethod(MethodNode mth) {
mth.getMethodInfo().setAlias(makeMethodAlias(mth)); String alias = makeMethodAlias(mth);
if (mth.isVirtual()) { applyMethodAlias(mth, alias);
resolveOverriding(mth); }
private void applyMethodAlias(MethodNode mth, String alias) {
setSingleMethodAlias(mth, alias);
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr != null) {
for (MethodNode ovrdMth : overrideAttr.getRelatedMthNodes()) {
if (ovrdMth != mth) {
setSingleMethodAlias(ovrdMth, alias);
}
}
} }
} }
private void setSingleMethodAlias(MethodNode mth, String alias) {
MethodInfo mthInfo = mth.getMethodInfo();
mthInfo.setAlias(alias);
mthMap.put(mthInfo, alias);
mthProcessQueue.remove(mth);
}
public void addPackagePreset(String origPkgName, String pkgAlias) { public void addPackagePreset(String origPkgName, String pkgAlias) {
PackageNode pkg = getPackageNode(origPkgName, true); PackageNode pkg = getPackageNode(origPkgName, true);
pkg.setAlias(pkgAlias); pkg.setAlias(pkgAlias);
@@ -287,8 +271,10 @@ public class Deobfuscator {
/** /**
* Gets package node for full package name * Gets package node for full package name
* *
* @param fullPkgName full package name * @param fullPkgName
* @param create if {@code true} then will create all absent objects * full package name
* @param create
* if {@code true} then will create all absent objects
* @return package node object or {@code null} if no package found and <b>create</b> set to * @return package node object or {@code null} if no package found and <b>create</b> set to
* {@code false} * {@code false}
*/ */
@@ -349,7 +335,8 @@ public class Deobfuscator {
} else { } else {
if (!clsMap.containsKey(classInfo)) { if (!clsMap.containsKey(classInfo)) {
String clsShortName = classInfo.getShortName(); String clsShortName = classInfo.getShortName();
boolean badName = shouldRename(clsShortName) || reservedClsNames.contains(clsShortName); boolean badName = shouldRename(clsShortName)
|| (args.isRenameValid() && reservedClsNames.contains(clsShortName));
makeClsAlias(cls, badName); makeClsAlias(cls, badName);
} }
} }
@@ -368,6 +355,21 @@ public class Deobfuscator {
public String getPkgAlias(ClassNode cls) { public String getPkgAlias(ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo(); ClassInfo classInfo = cls.getClassInfo();
if (classInfo.hasAliasPkg()) {
// already renamed
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
// update all parts of package
String[] aliasParts = classInfo.getAliasPkg().split("\\.");
PackageNode subPkg = pkg;
for (int i = aliasParts.length - 1; i >= 0; i--) {
String aliasPart = aliasParts[i];
if (!subPkg.getName().equals(aliasPart)) {
subPkg.setAlias(aliasPart);
}
subPkg = subPkg.getParentPackage();
}
return pkg.getFullAlias();
}
PackageNode pkg; PackageNode pkg;
DeobfClsInfo deobfClsInfo = clsMap.get(classInfo); DeobfClsInfo deobfClsInfo = clsMap.get(classInfo);
if (deobfClsInfo != null) { if (deobfClsInfo != null) {
@@ -388,10 +390,10 @@ public class Deobfuscator {
String alias = null; String alias = null;
String pkgName = null; String pkgName = null;
if (this.parseKotlinMetadata) { if (this.parseKotlinMetadata) {
ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls); ClsAliasPair kotlinCls = KotlinMetadataUtils.getClassAlias(cls);
if (kotlinCls != null) { if (kotlinCls != null) {
alias = prepareNameFull(kotlinCls.getShortName(), "C"); alias = kotlinCls.getName();
pkgName = kotlinCls.getPackage(); pkgName = kotlinCls.getPkg();
} }
} }
if (alias == null && this.useSourceNameAsAlias) { if (alias == null && this.useSourceNameAsAlias) {
@@ -474,7 +476,7 @@ public class Deobfuscator {
@Nullable @Nullable
private String getAliasFromSourceFile(ClassNode cls) { private String getAliasFromSourceFile(ClassNode cls) {
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE); SourceFileAttr sourceFileAttr = cls.get(JadxAttrType.SOURCE_FILE);
if (sourceFileAttr == null) { if (sourceFileAttr == null) {
return null; return null;
} }
@@ -499,7 +501,7 @@ public class Deobfuscator {
if (otherCls != null) { if (otherCls != null) {
return null; return null;
} }
cls.remove(AType.SOURCE_FILE); cls.remove(JadxAttrType.SOURCE_FILE);
return name; return name;
} }
@@ -523,26 +525,32 @@ public class Deobfuscator {
@Nullable @Nullable
private String getMethodAlias(MethodNode mth) { private String getMethodAlias(MethodNode mth) {
if (mth.contains(AFlag.DONT_RENAME)) {
return null;
}
MethodInfo methodInfo = mth.getMethodInfo(); MethodInfo methodInfo = mth.getMethodInfo();
if (methodInfo.isClassInit() || methodInfo.isConstructor()) { if (methodInfo.isClassInit() || methodInfo.isConstructor()) {
return null; return null;
} }
String alias = mthMap.get(methodInfo); String alias = getAssignedAlias(methodInfo);
if (alias != null) { if (alias != null) {
return alias; return alias;
} }
alias = deobfPresets.getForMth(methodInfo);
if (alias != null) {
mthMap.put(methodInfo, alias);
methodInfo.setAliasFromPreset(true);
return alias;
}
if (shouldRename(mth.getName())) { if (shouldRename(mth.getName())) {
return makeMethodAlias(mth); return makeMethodAlias(mth);
} }
return null; return null;
} }
@Nullable
private String getAssignedAlias(MethodInfo methodInfo) {
String alias = mthMap.get(methodInfo);
if (alias != null) {
return alias;
}
return deobfPresets.getForMth(methodInfo);
}
public String makeFieldAlias(FieldNode field) { public String makeFieldAlias(FieldNode field) {
String alias = String.format("f%d%s", fldIndex++, prepareNamePart(field.getName())); String alias = String.format("f%d%s", fldIndex++, prepareNamePart(field.getName()));
fldMap.put(field.getFieldInfo(), alias); fldMap.put(field.getFieldInfo(), alias);
@@ -550,9 +558,13 @@ public class Deobfuscator {
} }
public String makeMethodAlias(MethodNode mth) { public String makeMethodAlias(MethodNode mth) {
String alias = String.format("m%d%s", mthIndex++, prepareNamePart(mth.getName())); String prefix;
mthMap.put(mth.getMethodInfo(), alias); if (mth.contains(AType.METHOD_OVERRIDE)) {
return alias; prefix = "mo";
} else {
prefix = "m";
}
return String.format("%s%d%s", prefix, mthIndex++, prepareNamePart(mth.getName()));
} }
private void processPackageFull(PackageNode pkg, String fullName) { private void processPackageFull(PackageNode pkg, String fullName) {
@@ -573,6 +585,7 @@ public class Deobfuscator {
if (!pkg.hasAlias()) { if (!pkg.hasAlias()) {
String pkgName = pkg.getName(); String pkgName = pkg.getName();
if ((args.isDeobfuscationOn() && shouldRename(pkgName)) if ((args.isDeobfuscationOn() && shouldRename(pkgName))
&& (pkg.getParentPackage() != rootPackage || !TldHelper.contains(pkgName)) // check if first level is a valid tld
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName)) || (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName))
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) { || (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName())); String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
@@ -593,20 +606,6 @@ public class Deobfuscator {
return NameMapper.removeInvalidCharsMiddle(name); return NameMapper.removeInvalidCharsMiddle(name);
} }
private String prepareNameFull(String name, String prefix) {
if (name.length() > maxLength) {
return makeHashName(name, prefix);
}
String result = NameMapper.removeInvalidChars(name, prefix);
if (result.isEmpty()) {
return makeHashName(name, prefix);
}
if (NameMapper.isReserved(result)) {
return prefix + result;
}
return result;
}
private static String makeHashName(String name, String invalidPrefix) { private static String makeHashName(String name, String invalidPrefix) {
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode()); return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
} }
@@ -5,6 +5,8 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import jadx.core.utils.StringUtils;
import static jadx.core.utils.StringUtils.notEmpty; import static jadx.core.utils.StringUtils.notEmpty;
public class NameMapper { public class NameMapper {
@@ -99,35 +101,57 @@ public class NameMapper {
return Character.isJavaIdentifierPart(codePoint); return Character.isJavaIdentifierPart(codePoint);
} }
public static boolean isPrintableChar(int c) { public static boolean isPrintableChar(char c) {
return 32 <= c && c <= 126; return 32 <= c && c <= 126;
} }
public static boolean isPrintableAsciiCodePoint(int c) {
return 32 <= c && c <= 126;
}
public static boolean isPrintableCodePoint(int codePoint) {
if (Character.isISOControl(codePoint)) {
return false;
}
if (Character.isWhitespace(codePoint)) {
// don't print whitespaces other than standard one
return codePoint == ' ';
}
switch (Character.getType(codePoint)) {
case Character.CONTROL:
case Character.FORMAT:
case Character.PRIVATE_USE:
case Character.SURROGATE:
case Character.UNASSIGNED:
return false;
}
return true;
}
public static boolean isAllCharsPrintable(String str) { public static boolean isAllCharsPrintable(String str) {
int len = str.length(); int len = str.length();
for (int i = 0; i < len; i++) { int offset = 0;
if (!isPrintableChar(str.charAt(i))) { while (offset < len) {
int codePoint = str.codePointAt(offset);
if (!isPrintableAsciiCodePoint(codePoint)) {
return false; return false;
} }
offset += Character.charCount(codePoint);
} }
return true; return true;
} }
/** /**
* Return modified string with removed: * Return modified string with removed:
* <p>
* <ul> * <ul>
* <li>not printable chars (including unicode) * <li>not printable chars (including unicode)
* <li>chars not valid for java identifier part * <li>chars not valid for java identifier part
* </ul> * </ul>
* <p>
* Note: this 'middle' method must be used with prefixed string: * Note: this 'middle' method must be used with prefixed string:
* <p>
* <ul> * <ul>
* <li>can leave invalid chars for java identifier start (i.e numbers) * <li>can leave invalid chars for java identifier start (i.e numbers)
* <li>result not checked for reserved words * <li>result not checked for reserved words
* </ul> * </ul>
* <p>
*/ */
public static String removeInvalidCharsMiddle(String name) { public static String removeInvalidCharsMiddle(String name) {
if (isValidIdentifier(name) && isAllCharsPrintable(name)) { if (isValidIdentifier(name) && isAllCharsPrintable(name)) {
@@ -135,12 +159,11 @@ public class NameMapper {
} }
int len = name.length(); int len = name.length();
StringBuilder sb = new StringBuilder(len); StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) { StringUtils.visitCodePoints(name, codePoint -> {
int codePoint = name.codePointAt(i); if (isPrintableAsciiCodePoint(codePoint) && isValidIdentifierPart(codePoint)) {
if (isPrintableChar(codePoint) && isValidIdentifierPart(codePoint)) { sb.appendCodePoint(codePoint);
sb.append((char) codePoint);
} }
} });
return sb.toString(); return sb.toString();
} }
@@ -160,6 +183,16 @@ public class NameMapper {
return result; return result;
} }
public static String removeNonPrintableCharacters(String name) {
StringBuilder sb = new StringBuilder(name.length());
StringUtils.visitCodePoints(name, codePoint -> {
if (isPrintableAsciiCodePoint(codePoint)) {
sb.appendCodePoint(codePoint);
}
});
return sb.toString();
}
private NameMapper() { private NameMapper() {
} }
} }
@@ -1,26 +0,0 @@
package jadx.core.deobf;
import java.util.Set;
import jadx.core.dex.info.MethodInfo;
class OverridedMethodsNode {
private final Set<MethodInfo> methods;
public OverridedMethodsNode(Set<MethodInfo> methodsSet) {
methods = methodsSet;
}
public boolean contains(MethodInfo mth) {
return methods.contains(mth);
}
public void add(MethodInfo mth) {
methods.add(mth);
}
public Set<MethodInfo> getMethods() {
return methods;
}
}
@@ -55,6 +55,7 @@ public class PackageNode {
public void setAlias(String alias) { public void setAlias(String alias) {
packageAlias = alias; packageAlias = alias;
cachedPackageFullAlias = null;
} }
public boolean hasAlias() { public boolean hasAlias() {
@@ -109,7 +110,8 @@ public class PackageNode {
/** /**
* Gets inner package node by name * Gets inner package node by name
* *
* @param name inner package name * @param name
* inner package name
* @return package node or {@code null} * @return package node or {@code null}
*/ */
public PackageNode getInnerPackageByName(String name) { public PackageNode getInnerPackageByName(String name) {
@@ -143,6 +145,9 @@ public class PackageNode {
@Override @Override
public String toString() { public String toString() {
return packageAlias; if (packageAlias != null) {
return packageName + "[alias:" + packageAlias + "]";
}
return packageName;
} }
} }
@@ -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);
}
}
@@ -2,6 +2,8 @@ package jadx.core.dex.attributes;
public enum AFlag { public enum AFlag {
MTH_ENTER_BLOCK, MTH_ENTER_BLOCK,
MTH_EXIT_BLOCK,
TRY_ENTER, TRY_ENTER,
TRY_LEAVE, TRY_LEAVE,
@@ -25,12 +27,14 @@ public enum AFlag {
DONT_RENAME, // do not rename during deobfuscation DONT_RENAME, // do not rename during deobfuscation
ADDED_TO_REGION, ADDED_TO_REGION,
EXC_TOP_SPLITTER,
EXC_BOTTOM_SPLITTER,
FINALLY_INSNS, FINALLY_INSNS,
SKIP_FIRST_ARG, SKIP_FIRST_ARG,
SKIP_ARG, // skip argument in invoke call SKIP_ARG, // skip argument in invoke call
NO_SKIP_ARGS,
ANONYMOUS_CONSTRUCTOR, ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS,
THIS, THIS,
SUPER, SUPER,
@@ -72,11 +76,16 @@ public enum AFlag {
INCONSISTENT_CODE, // warning about incorrect decompilation INCONSISTENT_CODE, // warning about incorrect decompilation
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
REQUEST_CODE_SHRINK,
RERUN_SSA_TRANSFORM,
METHOD_CANDIDATE_FOR_INLINE,
// Class processing flags // Class processing flags
RESTART_CODEGEN, // codegen must be executed again RESTART_CODEGEN, // codegen must be executed again
RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage
CLASS_DEEP_RELOAD, // perform deep class unload (reload) before process CLASS_DEEP_RELOAD, // perform deep class unload (reload) before process
CLASS_UNLOADED, // class was completely unloaded
DONT_UNLOAD_CLASS, // don't unload class after code generation (only for tests and debug!) DONT_UNLOAD_CLASS, // don't unload class after code generation (only for tests and debug!)
} }
@@ -1,11 +1,9 @@
package jadx.core.dex.attributes; package jadx.core.dex.attributes;
import java.util.Arrays; import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import java.util.HashSet; import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import java.util.Set; import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EdgeInsnAttr; import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr;
@@ -13,12 +11,13 @@ import jadx.core.dex.attributes.nodes.EnumMapAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr; import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.GenericInfoAttr; import jadx.core.dex.attributes.nodes.GenericInfoAttr;
import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr; import jadx.core.dex.attributes.nodes.JadxCommentsAttr;
import jadx.core.dex.attributes.nodes.JadxError; import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JumpInfo; import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr; import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
import jadx.core.dex.attributes.nodes.LoopInfo; import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr; import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr; import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr; import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
@@ -26,11 +25,12 @@ import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr; import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr; import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr; import jadx.core.dex.attributes.nodes.SpecialEdgeAttr;
import jadx.core.dex.attributes.nodes.TmpEdgeAttr;
import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.SplitterBlockAttr; import jadx.core.dex.trycatch.TryCatchBlockAttr;
/** /**
* Attribute types enumeration, * Attribute types enumeration,
@@ -38,49 +38,52 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
* *
* @param <T> attribute class implementation * @param <T> attribute class implementation
*/ */
@SuppressWarnings("InstantiationOfUtilityClass") public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
public class AType<T extends IAttribute> {
// class, method, field, insn
public static final AType<AttrList<String>> CODE_COMMENTS = new AType<>();
// class, method, field // class, method, field
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>(); public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
// class, method // class, method
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile completely public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile
public static final AType<AttrList<String>> JADX_WARN = new AType<>(); // mark code as inconsistent (code can be viewed) public static final AType<JadxCommentsAttr> JADX_COMMENTS = new AType<>(); // additional info about decompilation
public static final AType<AttrList<String>> COMMENTS = new AType<>(); // any additional info about decompilation
// class // class
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>(); public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>(); public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
public static final AType<AnonymousClassAttr> ANONYMOUS_CLASS = new AType<>();
// field // field
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>(); public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>(); public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
// method // method
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>(); public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>(); public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>(); public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>(); public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>(); public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>();
public static final AType<MethodBridgeAttr> BRIDGED_BY = new AType<>();
// region // region
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>(); public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
// block // block
public static final AType<PhiListAttr> PHI_LIST = new AType<>(); public static final AType<PhiListAttr> PHI_LIST = new AType<>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>(); public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>(); public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>(); public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
public static final AType<AttrList<SpecialEdgeAttr>> SPECIAL_EDGE = new AType<>();
public static final AType<TmpEdgeAttr> TMP_EDGE = new AType<>();
public static final AType<TryCatchBlockAttr> TRY_BLOCK = new AType<>();
// block or insn // block or insn
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>(); public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
public static final AType<CatchAttr> EXC_CATCH = new AType<>();
// instruction // instruction
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>(); public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
@@ -90,9 +93,4 @@ public class AType<T extends IAttribute> {
// register // register
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>(); public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
public static final Set<AType<?>> SKIP_ON_UNLOAD = new HashSet<>(Arrays.asList(
FIELD_REPLACE,
METHOD_INLINE,
SKIP_MTH_ARGS));
} }
@@ -3,15 +3,16 @@ package jadx.core.dex.attributes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import jadx.core.codegen.CodeWriter; import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
public class AttrList<T> implements IAttribute { public class AttrList<T> implements IJadxAttribute {
private final AType<AttrList<T>> type; private final IJadxAttrType<AttrList<T>> type;
private final List<T> list = new ArrayList<>(); private final List<T> list = new ArrayList<>();
public AttrList(AType<AttrList<T>> type) { public AttrList(IJadxAttrType<AttrList<T>> type) {
this.type = type; this.type = type;
} }
@@ -20,12 +21,12 @@ public class AttrList<T> implements IAttribute {
} }
@Override @Override
public AType<AttrList<T>> getType() { public IJadxAttrType<AttrList<T>> getAttrType() {
return type; return type;
} }
@Override @Override
public String toString() { public String toString() {
return Utils.listToString(list, CodeWriter.NL); return Utils.listToString(list, ", ");
} }
} }

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