Compare commits

..

297 Commits

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

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

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

* Fixed getting method parameters from inlined methods

* fixed generating code for constructor overloads, more cleaning

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

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

* added support for generating a frida snippet for fields

* apply spotless

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

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

* moved the overload check from NodeMethod to FridaAction

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

* fix code formatting
2022-02-07 21:50:01 +00:00
cyqw 4bed9dc358 fix(gui): results in usage search should be sorted by name (PR #1363) 2022-02-07 15:39:57 +00:00
nitram84 e229874195 fix: check if targetSdkVersion is missing in gradle export (#1367)(PR #1370) 2022-02-07 10:39:09 +00:00
Skylot 473b6e31e9 fix: support multi-entry loops (simple case) (#1320) 2022-02-06 18:36:33 +00:00
Jan S b5ce460618 feat(deobf): do not deobfuscate known top level domains with 2 or 3 characters (PR #1369) 2022-02-06 12:56:59 +00:00
Skylot 3c05b05196 fix: check names from Kotlin metadata before use (#1364) 2022-02-05 21:49:36 +00:00
Skylot bdb2efdb6b fix(res): remove static caching map for xml renames (#1364) 2022-02-05 20:23:44 +00:00
Skylot a27ba3ff4b fix(res): skip '.9.png' decode if patch data not found (#1112) 2022-02-05 17:45:08 +00:00
Skylot 4684207b54 fix: remove duplicate classes from decompilation batches (#1361) 2022-02-05 17:45:07 +00:00
Skylot dd1be3039b fix(gui): split decompile and index tasks for correct time counting (#1361) 2022-02-05 17:45:07 +00:00
Skylot 8b30b770cd fix(gui): missing icons and html decorations in usage dialog 2022-02-05 13:36:26 +00:00
Yotam 47caa91e85 fix(cli): fix and add debug log messages in initialization phase (PR #1362)
* Fix log level settings in the CLI
* Add log messages in initialization phase
2022-02-02 19:04:19 +00:00
Skylot d71f3e09df fix: prevent endless loop in path cross search (#1360) 2022-02-01 14:32:44 +00:00
Jan S 06c7415827 fix(res): improved decoding of flag attributes in binary XML files (#1156)(PR #1359) 2022-01-31 18:00:50 +00:00
Skylot bd3e62617e fix: correct inline for enums in j$.time.temporal 2022-01-31 11:49:59 +00:00
Skylot 00b48473a0 test: add internal option to disable file save 2022-01-31 10:27:20 +00:00
Skylot 84facb13d0 fix: don't inline named variables (#1338) 2022-01-28 18:33:38 +00:00
Skylot 96f90e18e8 fix: improve exception handlers attach 2022-01-26 15:43:40 +00:00
Skylot 8ff18e63ee chore: update dependencies 2022-01-25 18:51:43 +00:00
Skylot 381405ea99 fix: always use deep resolve for fields and methods (#1357) 2022-01-25 11:37:36 +00:00
Ahmet Bilal Can ae5c00397a feat(gui): add frida action to copy methods/classes as frida snippets (#1355)(PR #1356)
* add frida action to copy methods/classes as frida snippets
* bug: call toString before comparing
2022-01-24 21:37:12 +00:00
Skylot bd4509f1a7 fix: update field usage on const replace (#1348) 2022-01-24 18:22:43 +00:00
Skylot b8c84886a8 fix: correct use of class names for inner types (#1340) 2022-01-24 14:11:40 +00:00
Skylot 45021389bc fix: correct method arg name if unused 2022-01-24 13:38:49 +00:00
Yotam f674a29a64 fix(deobf): rename classes as anonymous only if they are a number (PR #1354) 2022-01-23 21:16:05 +00:00
Yotam 0c9e3227d0 fix(deobf): collect missing renames for .jobf file (#1350)(PR #1353) 2022-01-23 16:08:54 +00:00
cyqw be7e1479a1 fix(gui): find usage for overridden methods (#1349)(PR #1352) 2022-01-23 16:06:13 +00:00
Skylot 19827fca20 fix: support full class name in inner generic types (#1340) 2022-01-22 18:49:31 +00:00
Skylot 5eb7cc40ed feat: check dex checksum before parsing (#1343) 2022-01-20 19:24:49 +00:00
Skylot d22db30166 fix: use secure xml parser for process manifest 2022-01-20 11:17:12 +00:00
Skylot 6db61e7a59 chore: update dependencies 2022-01-20 10:23:49 +00:00
Skylot 86582de521 feat: use kotlin intrinsic methods for variables rename (#1207) 2022-01-19 17:30:04 +00:00
Skylot a7c63c2eb3 fix: handle method override with several bases (#1234) 2022-01-18 18:27:09 +00:00
Skylot 081a0e21ee fix: precalculate class deps for inline methods (#1339) 2022-01-17 14:38:38 +00:00
Skylot 9ac9c05265 fix: simplify cascading casts (#1336) 2022-01-15 16:31:18 +00:00
Skylot b7daf79b26 fix: add explicit type for non-int constants (#1336) 2022-01-15 14:11:44 +00:00
Skylot b67a3561a4 build: add CodeQL analysis 2022-01-13 22:37:36 +03:00
Skylot 52ac6dbbaf docs: add security.md 2022-01-13 16:45:32 +00:00
Skylot 72381ad8f3 fix: correct literal negate for double and float (#1334) 2022-01-13 14:00:53 +00:00
Skylot 6a065c46f4 chore: update dependencies 2022-01-13 12:12:15 +00:00
Skylot 092d0d7e67 fix(gui): reduce tree focus switching 2022-01-12 19:57:38 +03:00
Skylot 5ca7285558 fix(gui): correct handling for tree row click (#1324) 2022-01-12 16:57:25 +00:00
Skylot 7576f9cd5e fix: wrap negative literals before cast (#1327) 2022-01-12 17:31:40 +03:00
Skylot 46b5725d98 refactor(test): replace inputs with test profiles 2022-01-12 17:31:37 +03:00
Jan S 72542fa6f9 fix(gui): processing threads spinner initialization (#1331)(PR #1332)
* fix: processing threads spinner initialization (#1331)
* fix: processing threads spinner initialization (#1331)
2022-01-12 14:23:07 +00:00
demonlol a250d0461b fix(dbg): support multiple main <action> and <activity-alias> tags (#1322)(PR #1323)
* fix(dgb): support multiple main <action> and <activity-alias> tags in manifest
* Update jadx-gui/src/main/java/jadx/gui/device/debugger/DbgUtils.java
2022-01-02 20:09:24 +03:00
Skylot c7795bfc48 fix: improve anonymous class inline (#523) 2021-12-26 13:06:49 +00:00
Skylot 5de46b7e40 chore: update gradle and dependencies 2021-12-24 12:53:30 +00:00
Skylot 99c70872c1 fix: use debug line numbers only at fixed offsets (#1315) 2021-12-22 22:55:14 +03:00
Skylot 3566669303 chore: update lgtm config 2021-12-22 12:24:01 +00:00
Skylot 4557d05256 fix: use correct type for anonymous class instance (#597) 2021-12-21 17:47:52 +00:00
Skylot fa421d165e build: disable missing warnings from javadoc 2021-12-21 12:52:52 +00:00
Skylot ecf20020d7 chore: cache current working dir in static field, other minor changes 2021-12-20 19:25:07 +00:00
Skylot ae85af61c7 fix: skip input file name checks by zip name validator (#1310) 2021-12-20 18:55:28 +00:00
Skylot 659bbbf4fb fix: correct usage of Path.getParent() 2021-12-20 16:48:50 +00:00
Jan S 427e2dddc4 fix: use relative file paths in .jadx project file (#1312) (PR #1313)
* chore: use relative file paths in .jadx project file (#1312)
* code beautified
* requested changes
2021-12-20 13:52:51 +00:00
skylot d47483f957 docs: use jadx as a library 2021-12-19 20:36:58 +00:00
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
981 changed files with 71903 additions and 11058 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**
- 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
- 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
*.cfg
*.orig
quark.json
cliff.toml
+3 -3
View File
@@ -11,14 +11,14 @@ stages:
java-8:
stage: test
image: openjdk:8
script: ./gradlew clean build dist
script: ./gradlew clean build dist copyExe --warning-mode=all
java-11:
stage: test
image: openjdk:11
script: ./gradlew clean build dist
script: ./gradlew clean build dist copyExe --warning-mode=all
java-latest:
stage: test
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
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
- search existing issues by exception message
+44 -25
View File
@@ -2,19 +2,20 @@
## JADX
[![Build Status](https://travis-ci.com/skylot/jadx.svg?branch=master)](https://travis-ci.com/skylot/jadx)
[![Code Coverage](https://codecov.io/gh/skylot/jadx/branch/master/graph/badge.svg)](https://codecov.io/gh/skylot/jadx)
[![Build status](https://github.com/skylot/jadx/workflows/Build/badge.svg)](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
[![Alerts from lgtm.com](https://img.shields.io/lgtm/alerts/g/skylot/jadx.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/skylot/jadx/alerts/)
[![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)
[![Maven Central](https://img.shields.io/maven-central/v/io.github.skylot/jadx-core)](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
**jadx** - Dex to Java decompiler
Command line and GUI tools for producing Java source code from Android Dex and Apk files
:exclamation::exclamation::exclamation: Please note that in most cases **jadx** can't decompile all 100% of the code, so errors will occur. Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds
**Main features:**
- decompile Dalvik bytecode to java classes from APK, dex, aar 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`
- deobfuscator included
@@ -23,25 +24,25 @@ Command line and GUI tools for producing Java source code from Android Dex and A
- jump to declaration
- find usage
- full text search
- smali debugger, 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)
![jadx-gui screenshot](https://i.imgur.com/h917IBZ.png)
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
### 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 [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:
- `jadx` - command line version
- `jadx-gui` - UI version
On Windows run `.bat` files with double-click\
**Note:** ensure you have installed Java 8 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").
**Note:** ensure you have installed Java 11 or later 64-bit version.
For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
### Install
1. Arch linux
@@ -53,6 +54,9 @@ For windows you can download it from [adoptopenjdk.net](https://adoptopenjdk.net
brew install jadx
```
### Use jadx as a library
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
### Build from source
JDK 8 or higher must be installed:
```
@@ -68,44 +72,62 @@ and also packed to `build/jadx-<version>.zip`
### 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:
-d, --output-dir - output directory
-ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--single-class - decompile a single class
--single-class - decompile a single class, full name, raw or alias
--single-class-output - file or dir for write if decompile a single class
--output-format - can be 'java' or 'json', default: java
-e, --export-gradle - save as android gradle project
-j, --threads-count - processing threads count, default: 4
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info
--add-debug-lines - add comments with debug line numbers if available
--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
--escape-unicode - escape non latin characters in strings (with \u)
--respect-bytecode-access-modifiers - don't change original access modifiers
--deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter, default: 3
--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
--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
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
--use-dx - use dx/d8 to convert java bytecode
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
--log-level - set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG, default: PROGRESS
--version - print jadx version
-h, --help - print this help
Example:
jadx -d out classes.dex
jadx --rename-flags "none" classes.dex
jadx --rename-flags "valid,printable" classes.dex
jadx --log-level error app.apk
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
```
These options also worked on jadx-gui running from command line and override options from preferences dialog
@@ -118,8 +140,5 @@ To support this project you can:
- 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)
### 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*
+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 -67
View File
@@ -1,7 +1,6 @@
plugins {
id 'org.sonarqube' version '3.0'
id 'com.github.ben-manes.versions' version '0.33.0'
id "com.diffplug.spotless" version "5.5.1"
id 'com.github.ben-manes.versions' version '0.42.0'
id 'com.diffplug.spotless' version '6.2.2'
}
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -10,7 +9,6 @@ println("jadx version: ${jadxVersion}")
allprojects {
apply plugin: 'java'
apply plugin: 'jacoco'
apply plugin: 'checkstyle'
version = jadxVersion
@@ -18,12 +16,6 @@ allprojects {
sourceCompatibility = 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 {
options.encoding = "UTF-8"
}
@@ -35,19 +27,19 @@ allprojects {
}
dependencies {
implementation 'org.slf4j:slf4j-api:1.7.30'
compileOnly 'org.jetbrains:annotations:20.1.0'
implementation 'org.slf4j:slf4j-api:1.7.36'
compileOnly 'org.jetbrains:annotations:23.0.0'
testImplementation 'ch.qos.logback:logback-classic:1.2.3'
testImplementation 'ch.qos.logback:logback-classic:1.2.10'
testImplementation 'org.hamcrest:hamcrest-library:2.2'
testImplementation 'org.mockito:mockito-core:3.5.10'
testImplementation 'org.assertj:assertj-core:3.17.2'
testImplementation 'org.mockito:mockito-core:4.3.1'
testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
testCompileOnly 'org.jetbrains:annotations:20.1.0'
testCompileOnly 'org.jetbrains:annotations:23.0.0'
}
test {
@@ -57,31 +49,8 @@ allprojects {
repositories {
mavenLocal()
mavenCentral()
jcenter()
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 {
@@ -95,7 +64,13 @@ spotless {
importOrderFile 'config/code-formatter/eclipse.importorder'
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)
encoding("UTF-8")
@@ -128,36 +103,55 @@ dependencyUpdates {
}
}
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
destinationDir file("$buildDir/jadx")
['jadx-cli', 'jadx-gui'].each {
from tasks.getByPath(":${it}:installDist").destinationDir
task copyArtifacts(type: Copy) {
from tasks.getByPath(":jadx-cli:installDist")
from tasks.getByPath(":jadx-gui:installDist")
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
}
task pack(type: Zip, dependsOn: copyArtifacts) {
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]) {
task dist {
group 'jadx'
description = 'Build jadx distribution zip'
}
task samples(dependsOn: 'jadx-samples:samples') {
group 'jadx'
dependsOn(pack)
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
if (os.isWindows()) {
if (project.hasProperty("bundleJRE")) {
println("Build win bundle with JRE")
dependsOn('distWinBundle')
} else {
dependsOn('copyExe')
}
}
}
task cleanBuildDir(type: Delete) {
@@ -165,6 +159,4 @@ task cleanBuildDir(type: Delete) {
delete buildDir
}
test.dependsOn(samples)
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="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 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
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
distributionSha256Sum=8cc27038d5dbd815759851ba53e70cf62e481b87494cc97cfd97982ada5ba634
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
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");
# 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
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
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.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
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 [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
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."
fi
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.
Please set the JAVA_HOME variable in your environment to match the
@@ -106,80 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
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" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --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
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
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" "$@"
+5 -4
View File
@@ -6,16 +6,17 @@ dependencies {
implementation(project(':jadx-core'))
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-smali-input'))
implementation 'com.beust:jcommander:1.80'
implementation 'ch.qos.logback:logback-classic:1.2.3'
implementation 'com.beust:jcommander:1.82'
implementation 'ch.qos.logback:logback-classic:1.2.10'
}
application {
applicationName = 'jadx'
mainClassName = 'jadx.cli.JadxCLI'
mainClass.set('jadx.cli.JadxCLI')
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
}
@@ -5,8 +5,8 @@ import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
@@ -80,26 +80,37 @@ public class JCommanderWrapper<T> {
}
StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames());
String description = p.getDescription();
addSpaces(opt, maxNamesLen - opt.length() + 3);
opt.append("- ").append(p.getDescription());
if (description.contains("\n")) {
String[] lines = description.split("\n");
opt.append("- ").append(lines[0]);
for (int i = 1; i < lines.length; i++) {
opt.append('\n');
addSpaces(opt, maxNamesLen + 5);
opt.append(lines[i]);
}
} else {
opt.append("- ").append(description);
}
String defaultValue = getDefaultValue(args, f, opt);
if (defaultValue != null) {
if (defaultValue != null && !description.contains("(default)")) {
opt.append(", default: ").append(defaultValue);
}
out.println(opt);
}
out.println("Example:");
out.println("Examples:");
out.println(" jadx -d out classes.dex");
out.println(" jadx --rename-flags \"none\" classes.dex");
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
out.println(" jadx --log-level ERROR app.apk");
}
/**
* Get all declared fields of the specified class and all super classes
*
* @param clazz
* @return
*/
private List<Field> getFields(Class<?> clazz) {
List<Field> fieldList = new LinkedList<>();
List<Field> fieldList = new ArrayList<>();
while (clazz != null) {
fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
@@ -120,7 +131,7 @@ public class JCommanderWrapper<T> {
if (Enum.class.isAssignableFrom(fieldType)) {
Enum<?> val = (Enum<?>) f.get(args);
if (val != null) {
return val.name();
return val.name().toLowerCase(Locale.ROOT);
}
}
} catch (Exception e) {
+19 -3
View File
@@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.impl.NoOpCodeCache;
import jadx.api.impl.SimpleCodeWriter;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.files.FileUtils;
@@ -31,16 +32,20 @@ public class JadxCLI {
public static int execute(String[] args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) {
return processAndSave(jadxArgs.toJadxArgs());
return processAndSave(jadxArgs);
}
return 0;
}
private static int processAndSave(JadxArgs jadxArgs) {
private static int processAndSave(JadxCLIArgs cliArgs) {
JadxArgs jadxArgs = cliArgs.toJadxArgs();
jadxArgs.setCodeCache(new NoOpCodeCache());
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load();
jadx.save();
if (!SingleClassMode.process(jadx, cliArgs)) {
save(jadx);
}
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
jadx.printErrorsReport();
@@ -51,4 +56,15 @@ public class JadxCLI {
}
return 0;
}
private static void save(JadxDecompiler jadx) {
if (LogHelper.getLogLevel() == LogHelper.LogLevelEnum.QUIET) {
jadx.save();
} else {
jadx.save(500, (done, total) -> {
int progress = (int) (done * 100.0 / total);
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
});
}
}
}
+151 -24
View File
@@ -11,15 +11,18 @@ import java.util.stream.Stream;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils;
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);
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
@@ -37,9 +40,12 @@ public class JadxCLIArgs {
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
protected boolean skipSources = false;
@Parameter(names = { "--single-class" }, description = "decompile a single class")
@Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
protected String singleClass = null;
@Parameter(names = { "--single-class-output" }, description = "file or dir for write if decompile a single class")
protected String singleClassOutput = null;
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
protected String outputFormat = "java";
@@ -58,9 +64,15 @@ public class JadxCLIArgs {
@Parameter(names = { "--no-debug-info" }, description = "disable debug info")
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")
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")
protected boolean replaceConsts = true;
@@ -79,7 +91,24 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer")
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;
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
@@ -88,13 +117,21 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
protected boolean deobfuscationParseKotlinMetadata = false;
@Parameter(
names = { "--use-kotlin-methods-for-var-names" },
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
converter = UseKotlinMethodsForVarNamesConverter.class
)
protected UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
@Parameter(
names = { "--rename-flags" },
description = "what to rename, comma-separated,"
+ " 'case' for system case sensitivity,"
+ " 'valid' for java identifiers,"
+ " 'printable' characters,"
+ " 'none' or 'all' (default)",
description = "fix options (comma-separated list of):"
+ "\n 'case' - fix case sensitivity issues (according to --fs-case-sensitive option),"
+ "\n 'valid' - rename java identifiers to make them valid,"
+ "\n 'printable' - remove non-printable chars from identifiers,"
+ "\nor single 'none' - to disable all renames"
+ "\nor single 'all' - to enable all (default)",
converter = RenameConverter.class
)
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
@@ -111,19 +148,29 @@ public class JadxCLIArgs {
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
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)")
protected boolean verbose = false;
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
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")
protected boolean printVersion = false;
@@ -183,9 +230,6 @@ public class JadxCLIArgs {
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
args.setThreadsCount(threadsCount);
args.setSkipSources(skipSources);
if (singleClass != null) {
args.setClassFilter(className -> singleClass.equals(className));
}
args.setSkipResources(skipResources);
args.setFallbackMode(fallbackMode);
args.setShowInconsistentCode(showInconsistentCode);
@@ -193,21 +237,29 @@ public class JadxCLIArgs {
args.setRawCFGOutput(rawCfgOutput);
args.setReplaceConsts(replaceConsts);
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.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject);
args.setUseImports(useImports);
args.setDebugInfo(debugInfo);
args.setInsertDebugLines(addDebugLines);
args.setInlineAnonymousClasses(inlineAnonymousClasses);
args.setRenameCaseSensitive(isRenameCaseSensitive());
args.setRenameValid(isRenameValid());
args.setRenamePrintable(isRenamePrintable());
args.setInlineMethods(inlineMethods);
args.setRenameFlags(renameFlags);
args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel);
args.setUseDxInput(useDx);
return args;
}
@@ -227,6 +279,14 @@ public class JadxCLIArgs {
return outDirRes;
}
public String getSingleClass() {
return singleClass;
}
public String getSingleClassOutput() {
return singleClassOutput;
}
public boolean isSkipResources() {
return skipResources;
}
@@ -243,6 +303,10 @@ public class JadxCLIArgs {
return fallbackMode;
}
public boolean isUseDx() {
return useDx;
}
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
@@ -255,10 +319,18 @@ public class JadxCLIArgs {
return debugInfo;
}
public boolean isAddDebugLines() {
return addDebugLines;
}
public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses;
}
public boolean isInlineMethods() {
return inlineMethods;
}
public boolean isDeobfuscationOn() {
return deobfuscationOn;
}
@@ -271,6 +343,14 @@ public class JadxCLIArgs {
return deobfuscationMaxLength;
}
public String getDeobfuscationMapFile() {
return deobfuscationMapFile;
}
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
return deobfuscationMapFileMode;
}
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
}
@@ -283,6 +363,10 @@ public class JadxCLIArgs {
return deobfuscationParseKotlinMetadata;
}
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public boolean isEscapeUnicode() {
return escapeUnicode;
}
@@ -323,6 +407,10 @@ public class JadxCLIArgs {
return fsCaseSensitive;
}
public CommentsLevel getCommentsLevel() {
return commentsLevel;
}
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
private final String paramName;
@@ -341,7 +429,7 @@ public class JadxCLIArgs {
Set<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
for (String s : value.split(",")) {
try {
set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT)));
set.add(RenameEnum.valueOf(s.trim().toUpperCase(Locale.ROOT)));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
'\'' + s + "' is unknown for parameter " + paramName
@@ -352,9 +440,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) {
return Stream.of(values)
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
.collect(Collectors.joining(", "));
}
}
+17 -2
View File
@@ -1,5 +1,6 @@
package jadx.cli;
import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter;
@@ -31,6 +32,8 @@ public class LogHelper {
}
}
private static LogLevelEnum logLevelValue;
public static void setLogLevelFromArgs(JadxCLIArgs args) {
if (isCustomLogConfig()) {
return;
@@ -46,20 +49,32 @@ public class LogHelper {
}
public static void applyLogLevel(LogLevelEnum logLevel) {
logLevelValue = logLevel;
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(logLevel.getLevel());
if (logLevel != LogLevelEnum.QUIET) {
if (logLevel == LogLevelEnum.PROGRESS) {
// show progress for all levels except quiet
setLevelForClass(JadxCLI.class, Level.INFO);
setLevelForClass(JadxDecompiler.class, Level.INFO);
setLevelForClass(SingleClassMode.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);
}
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=
*/
@@ -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;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -17,7 +16,9 @@ import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.core.clsp.ClsSet;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.SignatureProcessor;
/**
* 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> ");
}
public static void main(String[] args) throws IOException {
public static void main(String[] args) throws Exception {
if (args.length < 2) {
usage();
System.exit(1);
@@ -38,6 +39,7 @@ public class ConvertToClsSet {
Path output = inputPaths.remove(0);
JadxPluginManager pluginManager = new JadxPluginManager();
pluginManager.load();
List<ILoadResult> loadedInputs = new ArrayList<>();
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
@@ -48,11 +50,18 @@ public class ConvertToClsSet {
RootNode root = new RootNode(jadxArgs);
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);
set.loadFrom(root);
set.save(output);
LOG.info("Output: {}, file size: {}B", output, output.toFile().length());
LOG.info("Output: {}", output);
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"),
"Expected convert() to throw, but it didn't");
assertEquals("'wrong' is unknown for parameter someParam, "
+ "possible values are 'case', 'valid', 'printable'",
assertEquals("'wrong' is unknown for parameter someParam, possible values are case, valid, printable",
thrown.getMessage());
}
}
@@ -7,6 +7,7 @@ import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -44,19 +45,20 @@ public class TestInput {
}
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
StringBuilder args = new StringBuilder();
List<String> args = new ArrayList<>();
Path tempDir = FileUtils.createTempDir(tmpDirName);
args.append("-v");
args.append(" -d ").append(tempDir.toAbsolutePath());
args.add("-v");
args.add("-d");
args.add(tempDir.toAbsolutePath().toString());
for (String inputSample : inputSamples) {
URL resource = getClass().getClassLoader().getResource(inputSample);
assertThat(resource).isNotNull();
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);
List<Path> resultJavaFiles = collectJavaFilesInDir(tempDir);
assertThat(resultJavaFiles).isNotEmpty();
+12 -6
View File
@@ -1,20 +1,26 @@
plugins {
id 'java-library'
id 'jadx-library'
}
dependencies {
runtimeOnly files('clsp-data/android-29-clst.jar')
runtimeOnly files('clsp-data/android-29-res.jar')
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-smali-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
testImplementation('tools.profiler:async-profiler:1.8.3')
}
test {
Binary file not shown.
Binary file not shown.
@@ -2,32 +2,27 @@ package jadx.api;
public final class CodePosition {
private final JavaNode node;
private final int line;
private final int offset;
private final int pos;
public CodePosition(JavaNode node, int line, int offset) {
this.node = node;
public CodePosition(int line, int offset, int pos) {
this.line = line;
this.offset = offset;
this.pos = pos;
}
public CodePosition(int line) {
this(line, 0, -1);
}
@Deprecated
public CodePosition(int line, int offset) {
this.node = null;
this.line = line;
this.offset = offset;
this(line, offset, -1);
}
public JavaNode getNode() {
return node;
}
public JavaClass getJavaClass() {
JavaClass parent = node.getDeclaringClass();
if (parent == null && node instanceof JavaClass) {
return (JavaClass) node;
}
return parent;
public int getPos() {
return pos;
}
public int getLine() {
@@ -62,8 +57,8 @@ public final class CodePosition {
if (offset != 0) {
sb.append(':').append(offset);
}
if (node != null) {
sb.append(' ').append(node);
if (pos > 0) {
sb.append('@').append(pos);
}
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);
}
+129 -5
View File
@@ -6,8 +6,12 @@ import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.data.ICodeData;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache;
public class JadxArgs {
@@ -25,6 +29,7 @@ public class JadxArgs {
private File outDirRes;
private ICodeCache codeCache = new InMemoryCodeCache();
private Function<JadxArgs, ICodeWriter> codeWriterProvider = AnnotatedCodeWriter::new;
private int threadsCount = DEFAULT_THREADS_COUNT;
@@ -36,7 +41,10 @@ public class JadxArgs {
private boolean useImports = true;
private boolean debugInfo = true;
private boolean insertDebugLines = false;
private boolean extractFinally = true;
private boolean inlineAnonymousClasses = true;
private boolean inlineMethods = true;
private boolean skipResources = false;
private boolean skipSources = false;
@@ -47,9 +55,11 @@ public class JadxArgs {
private Predicate<String> classFilter = null;
private boolean deobfuscationOn = false;
private boolean deobfuscationForceSave = false;
private boolean useSourceNameAsClassAlias = false;
private boolean parseKotlinMetadata = false;
private File deobfuscationMapFile = null;
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
private int deobfuscationMinLength = 0;
private int deobfuscationMaxLength = Integer.MAX_VALUE;
@@ -73,6 +83,23 @@ public class JadxArgs {
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;
public JadxArgs() {
// use default options
}
@@ -124,7 +151,7 @@ public class JadxArgs {
}
public void setThreadsCount(int threadsCount) {
this.threadsCount = threadsCount;
this.threadsCount = Math.max(1, threadsCount); // make sure threadsCount >= 1
}
public boolean isCfgOutput() {
@@ -175,6 +202,14 @@ public class JadxArgs {
this.debugInfo = debugInfo;
}
public boolean isInsertDebugLines() {
return insertDebugLines;
}
public void setInsertDebugLines(boolean insertDebugLines) {
this.insertDebugLines = insertDebugLines;
}
public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses;
}
@@ -183,6 +218,22 @@ public class JadxArgs {
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() {
return skipResources;
}
@@ -215,12 +266,24 @@ public class JadxArgs {
this.deobfuscationOn = deobfuscationOn;
}
@Deprecated
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
return deobfuscationMapFileMode == DeobfuscationMapFileMode.OVERWRITE;
}
@Deprecated
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
this.deobfuscationForceSave = deobfuscationForceSave;
if (deobfuscationForceSave) {
this.deobfuscationMapFileMode = DeobfuscationMapFileMode.OVERWRITE;
}
}
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
return deobfuscationMapFileMode;
}
public void setDeobfuscationMapFileMode(DeobfuscationMapFileMode deobfuscationMapFileMode) {
this.deobfuscationMapFileMode = deobfuscationMapFileMode;
}
public boolean isUseSourceNameAsClassAlias() {
@@ -255,6 +318,14 @@ public class JadxArgs {
this.deobfuscationMaxLength = deobfuscationMaxLength;
}
public File getDeobfuscationMapFile() {
return deobfuscationMapFile;
}
public void setDeobfuscationMapFile(File deobfuscationMapFile) {
this.deobfuscationMapFile = deobfuscationMapFile;
}
public boolean isEscapeUnicode() {
return escapeUnicode;
}
@@ -355,6 +426,54 @@ public class JadxArgs {
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;
}
@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -370,9 +489,11 @@ public class JadxArgs {
+ ", skipResources=" + skipResources
+ ", skipSources=" + skipSources
+ ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationForceSave=" + deobfuscationForceSave
+ ", deobfuscationMapFile=" + deobfuscationMapFile
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
+ ", deobfuscationMinLength=" + deobfuscationMinLength
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
+ ", escapeUnicode=" + escapeUnicode
@@ -382,7 +503,10 @@ public class JadxArgs {
+ ", fsCaseSensitive=" + fsCaseSensitive
+ ", renameFlags=" + renameFlags
+ ", outputFormat=" + outputFormat
+ ", commentsLevel=" + commentsLevel
+ ", codeCache=" + codeCache
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
+ ", useDxInput=" + useDxInput
+ '}';
}
}
@@ -85,9 +85,6 @@ public class JadxArgsValidator {
if (!file.exists()) {
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) {
@@ -15,14 +15,17 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.data.annotations.VarRef;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin;
@@ -36,9 +39,13 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject;
import jadx.core.utils.DecompilerScheduler;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ProtoXMLParser;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResourcesSaver;
/**
@@ -46,6 +53,7 @@ import jadx.core.xmlgen.ResourcesSaver;
*
* <pre>
* <code>
*
* JadxArgs args = new JadxArgs();
* args.getInputFiles().add(new File("test.apk"));
* args.setOutDir(new File("jadx-test-output"));
@@ -60,6 +68,7 @@ import jadx.core.xmlgen.ResourcesSaver;
*
* <pre>
* <code>
*
* for(JavaClass cls : jadx.getClasses()) {
* System.out.println(cls.getCode());
* }
@@ -77,12 +86,15 @@ public final class JadxDecompiler implements Closeable {
private List<JavaClass> classes;
private List<ResourceFile> resources;
private BinaryXMLParser xmlParser;
private BinaryXMLParser binaryXmlParser;
private ProtoXMLParser protoXmlParser;
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
public JadxDecompiler() {
this(new JadxArgs());
}
@@ -95,6 +107,7 @@ public final class JadxDecompiler implements Closeable {
reset();
JadxArgsValidator.validate(args);
LOG.info("loading ...");
loadPlugins(args);
loadInputFiles();
root = new RootNode(args);
@@ -108,19 +121,22 @@ public final class JadxDecompiler implements Closeable {
private void loadInputFiles() {
loadedInputs.clear();
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
ILoadResult loadResult = inputPlugin.loadFiles(inputPaths);
ILoadResult loadResult = inputPlugin.loadFiles(inputFiles);
if (loadResult != null && !loadResult.isEmpty()) {
loadedInputs.add(loadResult);
}
}
LOG.debug("Loaded using {} inputs plugin", loadedInputs.size());
}
private void reset() {
root = null;
classes = null;
resources = null;
xmlParser = null;
binaryXmlParser = null;
protoXmlParser = null;
classesMap.clear();
methodsMap.clear();
@@ -145,6 +161,15 @@ public final class JadxDecompiler implements Closeable {
reset();
}
private void loadPlugins(JadxArgs args) {
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
pluginManager.load();
if (LOG.isDebugEnabled()) {
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
p -> p.getPluginInfo().getPluginId()));
}
}
public void registerPlugin(JadxPlugin plugin) {
pluginManager.register(plugin);
}
@@ -157,6 +182,27 @@ public final class JadxDecompiler implements Closeable {
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() {
save(true, false);
}
@@ -180,20 +226,44 @@ public final class JadxDecompiler implements Closeable {
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
}
public List<Runnable> getSaveTasks() {
return getSaveTasks(!args.isSkipSources(), !args.isSkipResources());
}
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) {
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 resOutDir;
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();
sourcesOutDir = export.getSrcOutDir();
resOutDir = export.getResOutDir();
@@ -201,16 +271,21 @@ public final class JadxDecompiler implements Closeable {
sourcesOutDir = args.getOutDirSrc();
resOutDir = args.getOutDirRes();
}
List<Runnable> tasks = new ArrayList<>();
// save resources first because decompilation can hang or fail
if (saveResources) {
appendResourcesSave(executor, resOutDir);
appendResourcesSaveTasks(tasks, resOutDir);
}
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());
for (ResourceFile resourceFile : getResources()) {
if (resourceFile.getType() != ResourceType.ARSC
@@ -218,25 +293,38 @@ public final class JadxDecompiler implements Closeable {
// ignore resource made from input file
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();
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)) {
continue;
}
if (classFilter != null && !classFilter.test(cls.getFullName())) {
continue;
}
executor.execute(() -> {
try {
ICodeInfo code = cls.getCodeInfo();
SaveCode.save(outDir, cls.getClassNode(), code);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls.getFullName(), e);
processQueue.add(cls);
}
List<List<JavaClass>> batches;
try {
batches = decompileScheduler.buildBatches(processQueue);
} catch (Exception e) {
throw new JadxRuntimeException("Decompilation batches build failed", e);
}
for (List<JavaClass> decompileBatch : batches) {
tasks.add(() -> {
for (JavaClass cls : decompileBatch) {
try {
ICodeInfo code = cls.getCodeInfo();
SaveCode.save(outDir, cls.getClassNode(), code);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls, e);
}
}
});
}
@@ -323,11 +411,18 @@ public final class JadxDecompiler implements Closeable {
return root;
}
synchronized BinaryXMLParser getXmlParser() {
if (xmlParser == null) {
xmlParser = new BinaryXMLParser(root);
synchronized BinaryXMLParser getBinaryXmlParser() {
if (binaryXmlParser == null) {
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) {
@@ -338,12 +433,33 @@ public final class JadxDecompiler implements Closeable {
classesMap.put(innerCls.getClassNode(), innerCls);
loadJavaClass(innerCls);
}
for (JavaClass inlinedCls : javaClass.getInlinedClasses()) {
classesMap.put(inlinedCls.getClassNode(), inlinedCls);
loadJavaClass(inlinedCls);
}
}
/**
* 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")
private JavaClass getJavaClassByNode(ClassNode cls) {
@ApiStatus.Internal
public JavaClass getJavaClassByNode(ClassNode cls) {
JavaClass javaClass = classesMap.get(cls);
if (javaClass != null) {
if (javaClass != null && javaClass.getClassNode() == cls) {
return javaClass;
}
// load parent class if inner
@@ -373,9 +489,12 @@ public final class JadxDecompiler implements Closeable {
@Nullable
private JavaMethod getJavaMethodByNode(MethodNode mth) {
JavaMethod javaMethod = methodsMap.get(mth);
if (javaMethod != null) {
if (javaMethod != null && javaMethod.getMethodNode() == mth) {
return javaMethod;
}
if (mth.contains(AFlag.DONT_GENERATE)) {
return null;
}
// parent class not loaded yet
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
if (javaClass == null) {
@@ -395,7 +514,7 @@ public final class JadxDecompiler implements Closeable {
@Nullable
private JavaField getJavaFieldByNode(FieldNode fld) {
JavaField javaField = fieldsMap.get(fld);
if (javaField != null) {
if (javaField != null && javaField.getFieldNode() == fld) {
return javaField;
}
// parent class not loaded yet
@@ -414,8 +533,60 @@ public final class JadxDecompiler implements Closeable {
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
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)) {
return null;
}
@@ -424,7 +595,7 @@ public final class JadxDecompiler implements Closeable {
return null;
}
if (obj instanceof ClassNode) {
return getJavaClassByNode((ClassNode) obj);
return convertClassNode((ClassNode) obj);
}
if (obj instanceof MethodNode) {
return getJavaMethodByNode(((MethodNode) obj));
@@ -435,6 +606,23 @@ public final class JadxDecompiler implements Closeable {
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) {
return nodesList.stream()
.map(this::convertNode)
@@ -463,13 +651,25 @@ public final class JadxDecompiler implements Closeable {
if (defLine == 0) {
return null;
}
return new CodePosition(jCls, defLine, 0);
return new CodePosition(defLine, 0, javaNode.getDefPos());
}
public void reloadCodeData() {
root.notifyCodeDataListeners();
}
public JadxArgs getArgs() {
return args;
}
public JadxPluginManager getPluginManager() {
return pluginManager;
}
public IDecompileScheduler getDecompileScheduler() {
return decompileScheduler;
}
@Override
public String toString() {
return "jadx decompiler " + getVersion();
@@ -7,9 +7,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
@@ -22,6 +25,7 @@ public final class JavaClass implements JavaNode {
private final JavaClass parent;
private List<JavaClass> innerClasses = Collections.emptyList();
private List<JavaClass> inlinedClasses = Collections.emptyList();
private List<JavaField> fields = Collections.emptyList();
private List<JavaMethod> methods = Collections.emptyList();
private boolean listsLoaded;
@@ -62,18 +66,23 @@ public final class JavaClass implements JavaNode {
cls.reloadCode();
}
public synchronized String getSmali() {
return cls.getSmali();
public void unload() {
listsLoaded = false;
cls.unloadCode();
}
public synchronized void unload() {
cls.unload();
listsLoaded = false;
public boolean isNoCode() {
return cls.contains(AFlag.DONT_GENERATE);
}
public synchronized String getSmali() {
return cls.getDisassembledCode();
}
/**
* Internal API. Not Stable!
*/
@ApiStatus.Internal
public ClassNode getClassNode() {
return cls;
}
@@ -84,26 +93,37 @@ public final class JavaClass implements JavaNode {
}
listsLoaded = true;
decompile();
JadxDecompiler rootDecompiler = getRootDecompiler();
int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(inner, this);
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists();
list.add(javaClass);
}
}
this.innerClasses = Collections.unmodifiableList(list);
}
int inlinedClsCount = cls.getInlinedClasses().size();
if (inlinedClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
for (ClassNode inner : cls.getInlinedClasses()) {
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists();
list.add(javaClass);
}
this.inlinedClasses = Collections.unmodifiableList(list);
}
int fieldsCount = cls.getFields().size();
if (fieldsCount != 0) {
List<JavaField> flds = new ArrayList<>(fieldsCount);
for (FieldNode f : cls.getFields()) {
if (!f.contains(AFlag.DONT_GENERATE)) {
JavaField javaField = new JavaField(f, this);
JavaField javaField = new JavaField(this, f);
flds.add(javaField);
}
}
@@ -131,7 +151,7 @@ public final class JavaClass implements JavaNode {
return decompiler;
}
private Map<CodePosition, Object> getCodeAnnotations() {
public Map<CodePosition, Object> getCodeAnnotations() {
ICodeInfo code = getCodeInfo();
if (code == null) {
return Collections.emptyMap();
@@ -139,6 +159,10 @@ public final class JavaClass implements JavaNode {
return code.getAnnotations();
}
public Object getAnnotationAt(CodePosition pos) {
return getCodeAnnotations().get(pos);
}
public Map<CodePosition, JavaNode> getUsageMap() {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty() || decompiler == null) {
@@ -156,6 +180,23 @@ public final class JavaClass implements JavaNode {
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
public List<JavaNode> getUseIn() {
return getRootDecompiler().convertNodes(cls.getUseIn());
@@ -202,9 +243,19 @@ public final class JavaClass implements JavaNode {
@Override
public JavaClass getTopParentClass() {
if (cls.contains(AType.ANONYMOUS_CLASS)) {
// moved to usage class
return getParentForAnonymousClass();
}
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() {
return cls.getAccessFlags();
}
@@ -214,6 +265,11 @@ public final class JavaClass implements JavaNode {
return innerClasses;
}
public List<JavaClass> getInlinedClasses() {
loadLists();
return inlinedClasses;
}
public List<JavaField> getFields() {
loadLists();
return fields;
@@ -224,11 +280,30 @@ public final class JavaClass implements JavaNode {
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
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
@Override
public int getDefPos() {
return cls.getDefPosition();
}
@Override
public boolean equals(Object o) {
return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
@@ -2,6 +2,8 @@ package jadx.api;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
@@ -11,7 +13,7 @@ public final class JavaField implements JavaNode {
private final FieldNode field;
private final JavaClass parent;
JavaField(FieldNode f, JavaClass cls) {
JavaField(JavaClass cls, FieldNode f) {
this.field = f;
this.parent = cls;
}
@@ -26,6 +28,10 @@ public final class JavaField implements JavaNode {
return parent.getFullName() + '.' + getName();
}
public String getRawName() {
return field.getName();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
@@ -49,14 +55,25 @@ public final class JavaField implements JavaNode {
return field.getDecompiledLine();
}
@Override
public int getDefPos() {
return field.getDefPosition();
}
@Override
public List<JavaNode> getUseIn() {
return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn());
}
@Override
public void removeAlias() {
this.field.getFieldInfo().removeAlias();
}
/**
* Internal API. Not Stable!
*/
@ApiStatus.Internal
public FieldNode getFieldNode() {
return field;
}
@@ -2,7 +2,12 @@ package jadx.api;
import java.util.Collections;
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.instructions.args.ArgType;
import jadx.core.dex.nodes.MethodNode;
@@ -61,6 +66,17 @@ public final class JavaMethod implements JavaNode {
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() {
return mth.getMethodInfo().isConstructor();
}
@@ -74,9 +90,20 @@ public final class JavaMethod implements JavaNode {
return mth.getDecompiledLine();
}
@Override
public int getDefPos() {
return mth.getDefPosition();
}
@Override
public void removeAlias() {
this.mth.getMethodInfo().removeAlias();
}
/**
* Internal API. Not Stable!
*/
@ApiStatus.Internal
public MethodNode getMethodNode() {
return mth;
}
@@ -14,5 +14,10 @@ public interface JavaNode {
int getDecompiledLine();
int getDefPos();
List<JavaNode> getUseIn();
default void removeAlias() {
}
}
@@ -44,6 +44,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return 0;
}
@Override
public int getDefPos() {
return 0;
}
@Override
public List<JavaNode> getUseIn() {
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 String deobfName;
public static ResourceFile createResourceFile(JadxDecompiler decompiler, File file, ResourceType type) {
return new ResourceFile(decompiler, file.getAbsolutePath(), type);
}
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) {
return null;
@@ -41,6 +41,9 @@ public enum ResourceType {
}
public static ResourceType getFileType(String fileName) {
if (fileName.endsWith("/resources.pb")) {
return ARSC;
}
int dot = fileName.lastIndexOf('.');
if (dot != -1) {
String ext = fileName.substring(dot).toLowerCase(Locale.ROOT);
@@ -17,12 +17,13 @@ import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile.ZipRef;
import jadx.api.impl.SimpleCodeInfo;
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.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResProtoParser;
import jadx.core.xmlgen.ResTableParser;
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));
} catch (JadxException e) {
LOG.error("Decode error", e);
CodeWriter cw = new CodeWriter();
ICodeWriter cw = jadxRef.getRoot().makeCodeWriter();
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
Utils.appendStackTrace(cw, e.getCause());
return ResContainer.textResource(rf.getDeobfName(), cw.finish());
@@ -92,14 +93,25 @@ public final class ResourcesLoader {
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream) throws IOException {
RootNode root = jadxRef.getRoot();
switch (rf.getType()) {
case MANIFEST:
case XML:
ICodeInfo content = jadxRef.getXmlParser().parse(inputStream);
case XML: {
ICodeInfo content;
if (root.isProto()) {
content = jadxRef.getProtoXmlParser().parse(inputStream);
} else {
content = jadxRef.getBinaryXmlParser().parse(inputStream);
}
return ResContainer.textResource(rf.getDeobfName(), content);
}
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:
return decodeImage(rf, inputStream);
@@ -114,8 +126,9 @@ public final class ResourcesLoader {
if (name.endsWith(".9.png")) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
decoder.decode(inputStream, os);
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
if (decoder.decode(inputStream, os)) {
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
}
} catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
}
@@ -124,22 +137,17 @@ public final class ResourcesLoader {
}
private void loadFile(List<ResourceFile> list, File file) {
if (file == null) {
if (file == null || file.isDirectory()) {
return;
}
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 {
addResourceFile(list, file);
}
}
private void addResourceFile(List<ResourceFile> list, File file) {
String name = file.getAbsolutePath();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
list.add(rf);
ResourceType type = ResourceType.getFileType(file.getAbsolutePath());
list.add(ResourceFile.createResourceFile(jadxRef, file, type));
}
}
@@ -0,0 +1,32 @@
package jadx.api.args;
public enum DeobfuscationMapFileMode {
/**
* Load if found, don't save (default)
*/
READ,
/**
* Load if found, save only if new (don't overwrite)
*/
READ_OR_SAVE,
/**
* Don't load, always save
*/
OVERWRITE,
/**
* Don't load and don't save
*/
IGNORE;
public boolean shouldRead() {
return this == READ || this == READ_OR_SAVE;
}
public boolean shouldWrite() {
return this == READ_OR_SAVE || this == OVERWRITE;
}
}
@@ -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
public String toString() {
return "InMemoryCodeCache";
return "InMemoryCodeCache: size=" + storage.size();
}
}
@@ -9,21 +9,9 @@ import jadx.api.ICodeInfo;
public class SimpleCodeInfo implements ICodeInfo {
private final String code;
private final Map<Integer, Integer> lineMapping;
private final Map<CodePosition, Object> annotations;
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.lineMapping = lineMapping;
this.annotations = annotations;
}
@Override
@@ -33,12 +21,12 @@ public class SimpleCodeInfo implements ICodeInfo {
@Override
public Map<Integer, Integer> getLineMapping() {
return lineMapping;
return Collections.emptyMap();
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return annotations;
return Collections.emptyMap();
}
@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_TYPE_INFERENCE = 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_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_STRING_BUILDER = "java.lang.StringBuilder";
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 OVERRIDE_ANNOTATION = "Ljava/lang/Override;";
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
+40 -20
View File
@@ -10,9 +10,13 @@ import java.util.jar.Manifest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
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.AttachTryCatchVisitor;
import jadx.core.dex.visitors.CheckCode;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.ConstInlineVisitor;
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.InitCodeVariables;
import jadx.core.dex.visitors.InlineMethods;
import jadx.core.dex.visitors.MarkFinallyVisitor;
import jadx.core.dex.visitors.MarkMethodsForInline;
import jadx.core.dex.visitors.MethodInvokeVisitor;
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.ProcessAnonymous;
import jadx.core.dex.visitors.ProcessInstructionsVisitor;
import jadx.core.dex.visitors.ProcessMethodsForInline;
import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.RenameVisitor;
import jadx.core.dex.visitors.ShadowFieldVisitor;
import jadx.core.dex.visitors.SignatureProcessor;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.dex.visitors.blocks.BlockProcessor;
import jadx.core.dex.visitors.blocks.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
@@ -53,6 +56,8 @@ import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.regions.ReturnVisitor;
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.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
@@ -71,8 +76,9 @@ public class Jadx {
}
public static List<IDexTreeVisitor> getFallbackPassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>(3);
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new AttachTryCatchVisitor());
passes.add(new AttachCommentsVisitor());
passes.add(new ProcessInstructionsVisitor());
passes.add(new FallbackModeVisitor());
return passes;
@@ -81,8 +87,11 @@ public class Jadx {
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new SignatureProcessor());
passes.add(new OverrideMethodVisitor());
passes.add(new RenameVisitor());
passes.add(new UsageInfoVisitor());
passes.add(new ProcessAnonymous());
passes.add(new ProcessMethodsForInline());
return passes;
}
@@ -90,40 +99,50 @@ public class Jadx {
if (args.isFallbackMode()) {
return getFallbackPassesList();
}
List<IDexTreeVisitor> passes = new ArrayList<>();
// instructions IR
passes.add(new CheckCode());
if (args.isDebugInfo()) {
passes.add(new DebugInfoAttachVisitor());
}
passes.add(new AttachTryCatchVisitor());
if (args.getCommentsLevel() != CommentsLevel.NONE) {
passes.add(new AttachCommentsVisitor());
}
passes.add(new AttachMethodDetails());
passes.add(new ProcessInstructionsVisitor());
// blocks IR
passes.add(new BlockSplitter());
passes.add(new BlockProcessor());
if (args.isRawCFGOutput()) {
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 MoveInlineVisitor());
passes.add(new ConstructorVisitor());
passes.add(new InitCodeVariables());
passes.add(new MarkFinallyVisitor());
if (args.isExtractFinally()) {
passes.add(new MarkFinallyVisitor());
}
passes.add(new ConstInlineVisitor());
passes.add(new TypeInferenceVisitor());
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new InlineMethods());
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
passes.add(new ProcessKotlinInternals());
}
passes.add(new CodeRenameVisitor());
if (args.isInlineMethods()) {
passes.add(new InlineMethods());
}
passes.add(new GenericTypesVisitor());
passes.add(new ShadowFieldVisitor());
passes.add(new DeboxingVisitor());
passes.add(new AnonymousClassVisitor());
passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode());
@@ -131,6 +150,7 @@ public class Jadx {
passes.add(DotGraphVisitor.dump());
}
// regions IR
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
@@ -144,12 +164,12 @@ public class Jadx {
passes.add(new EnumVisitor());
passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers());
passes.add(new ProcessAnonymous());
passes.add(new ClassModifier());
passes.add(new LoopRegionVisitor());
passes.add(new MarkMethodsForInline());
if (args.isInlineMethods()) {
passes.add(new MarkMethodsForInline());
}
passes.add(new ProcessVariables());
passes.add(new PrepareForCodeGen());
if (args.isCfgOutput()) {
@@ -33,15 +33,18 @@ public final class ProcessClass {
try {
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
cls.remove(AFlag.CLASS_DEEP_RELOAD);
cls.unload();
cls.deepUnload();
cls.add(AFlag.CLASS_UNLOADED);
}
if (cls.contains(AFlag.CLASS_UNLOADED)) {
cls.root().runPreDecompileStageForClass(cls);
cls.remove(AFlag.CLASS_UNLOADED);
}
if (cls.getState() == GENERATED_AND_UNLOADED) {
// force loading code again
cls.setState(NOT_LOADED);
}
if (codegen) {
if (cls.getState() == GENERATED_AND_UNLOADED) {
// allow to run code generation again
cls.setState(NOT_LOADED);
}
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
@@ -68,10 +71,14 @@ public final class ProcessClass {
}
return code;
}
return null;
} catch (Throwable e) {
if (codegen) {
throw 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()) {
process(depCls, false);
}
if (!cls.getCodegenDeps().isEmpty()) {
process(cls, false);
for (ClassNode codegenDep : cls.getCodegenDeps()) {
process(codegenDep, false);
}
}
ICodeInfo code = process(cls, true);
if (code == null) {
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.files.FileUtils;
import static jadx.core.utils.Utils.notEmpty;
/**
* 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_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 int VERSION = 3;
@@ -78,9 +76,9 @@ public class ClsSet {
public void loadFromClstFile() throws IOException, DecodeException {
long startTime = System.currentTimeMillis();
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
try (InputStream input = ClsSet.class.getResourceAsStream(CLST_PATH)) {
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);
}
@@ -131,25 +129,13 @@ public class ClsSet {
private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) {
AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isPrivate()) {
if (accessFlags.isPrivate() || accessFlags.isSynthetic() || accessFlags.isBridge()) {
return;
}
ArgType genericRetType = mth.getReturnType();
boolean varArgs = accessFlags.isVarArgs();
List<ArgType> throwList = mth.getThrows();
List<ArgType> typeParameters = mth.getTypeParameters();
// 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);
}
ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(), mth.getArgTypes(),
mth.getReturnType(), mth.getTypeParameters(),
mth.getThrows(), accessFlags.rawValue());
methods.add(clspMethod);
}
public static ArgType[] makeParentsArray(ClassNode cls) {
@@ -197,7 +183,7 @@ public class ClsSet {
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME;
String clst = CLST_PATH;
boolean clstReplaced = false;
ZipEntry entry = in.getNextEntry();
while (entry != null) {
@@ -245,7 +231,7 @@ public class ClsSet {
}
}
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 {
@@ -257,7 +243,7 @@ public class ClsSet {
writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names);
writeArgType(out, method.getReturnType(), names);
writeArgTypesList(out, method.getTypeParameters(), names);
out.writeBoolean(method.isVarArg());
out.writeInt(method.getRawAccessFlags());
writeArgTypesList(out, method.getThrows(), names);
}
@@ -392,12 +378,12 @@ public class ClsSet {
genericRetType = retType;
}
List<ArgType> typeParameters = readArgTypesList(in);
boolean varArgs = in.readBoolean();
int accFlags = in.readInt();
List<ArgType> throwList = readArgTypesList(in);
MethodInfo methodInfo = MethodInfo.fromDetails(root, clsInfo, name, argTypes, retType);
return new ClspMethod(methodInfo,
genericArgTypes, genericRetType,
typeParameters, varArgs, throwList);
typeParameters, throwList, accFlags);
}
private List<ArgType> readArgTypesList(DataInputStream in) throws IOException {
@@ -8,9 +8,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -30,8 +28,9 @@ public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
private final RootNode root;
private final Map<String, Set<String>> superTypesCache = Collections.synchronizedMap(new WeakHashMap<>());
private Map<String, ClspClass> nameMap;
private Map<String, Set<String>> superTypesCache;
private Map<String, List<String>> implementsCache;
private final Set<String> missingClasses = new HashSet<>();
@@ -63,6 +62,11 @@ public class ClspGraph {
}
}
public void initCache() {
fillSuperTypesCache();
fillImplementsCache();
}
public boolean isClsKnown(String 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);
}
@@ -116,13 +120,20 @@ public class ClspGraph {
}
public List<String> getImplementations(String clsName) {
List<String> list = new ArrayList<>();
for (String cls : nameMap.keySet()) {
if (isImplements(cls, clsName)) {
list.add(cls);
List<String> list = implementsCache.get(clsName);
return list == null ? Collections.emptyList() : list;
}
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) {
@@ -159,29 +170,26 @@ public class ClspGraph {
}
public Set<String> getSuperTypes(String clsName) {
Set<String> fromCache = superTypesCache.get(clsName);
if (fromCache != null) {
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);
Set<String> result = superTypesCache.get(clsName);
return result == null ? Collections.emptySet() : result;
}
@NotNull
private Set<String> putInSuperTypesCache(String clsName, Set<String> result) {
if (result.isEmpty()) {
Set<String> empty = Collections.emptySet();
superTypesCache.put(clsName, result);
return empty;
private void fillSuperTypesCache() {
Map<String, Set<String>> map = new HashMap<>(nameMap.size());
Set<String> tmpSet = new HashSet<>();
for (Map.Entry<String, ClspClass> entry : nameMap.entrySet()) {
ClspClass cls = entry.getValue();
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);
return result;
superTypesCache = map;
}
private void addSuperTypes(ClspClass cls, Set<String> result) {
@@ -203,9 +211,7 @@ public class ClspGraph {
private ClspClass getClspClass(ArgType clsType) {
ClspClass clspClass = nameMap.get(clsType.getObject());
if (clspClass == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("External class not found: {}", clsType.getObject());
}
missingClasses.add(clsType.getObject());
}
return clspClass;
}
@@ -5,6 +5,7 @@ import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.IMethodDetails;
@@ -20,18 +21,17 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
private final ArgType returnType;
private final List<ArgType> typeParameters;
private final List<ArgType> throwList;
private final boolean varArg;
private final int accFlags;
public ClspMethod(MethodInfo methodInfo,
List<ArgType> argTypes, ArgType returnType,
List<ArgType> typeParameters,
boolean varArgs, List<ArgType> throwList) {
List<ArgType> typeParameters, List<ArgType> throwList, int accFlags) {
this.methodInfo = methodInfo;
this.argTypes = argTypes;
this.returnType = returnType;
this.typeParameters = typeParameters;
this.throwList = throwList;
this.varArg = varArgs;
this.accFlags = accFlags;
}
@Override
@@ -69,7 +69,12 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
@Override
public boolean isVarArg() {
return varArg;
return (accFlags & AccessFlags.VARARGS) != 0;
}
@Override
public int getRawAccessFlags() {
return accFlags;
}
@Override
@@ -94,6 +99,11 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
return this.methodInfo.compareTo(other.methodInfo);
}
@Override
public String toAttrString() {
return IMethodDetails.super.toAttrString() + " (c)";
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -3,10 +3,15 @@ package jadx.core.clsp;
import java.util.Collections;
import java.util.List;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.IMethodDetails;
/**
* Method details build from MethodInfo.
* Note: some fields have unknown values.
*/
public class SimpleMethodDetails implements IMethodDetails {
private final MethodInfo methodInfo;
@@ -45,6 +50,16 @@ public class SimpleMethodDetails implements IMethodDetails {
return false;
}
@Override
public int getRawAccessFlags() {
return AccessFlags.PUBLIC;
}
@Override
public String toAttrString() {
return IMethodDetails.super.toAttrString() + " (s)";
}
@Override
public String toString() {
return "SimpleMethodDetails{" + methodInfo + '}';
@@ -7,14 +7,16 @@ import java.util.Map.Entry;
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.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.AnnotationsAttr;
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
import jadx.core.Consts;
import jadx.core.dex.attributes.AType;
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.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
@@ -34,24 +36,24 @@ public class AnnotationGen {
this.classGen = classGen;
}
public void addForClass(CodeWriter code) {
public void addForClass(ICodeWriter code) {
add(cls, code);
}
public void addForMethod(CodeWriter code, MethodNode mth) {
public void addForMethod(ICodeWriter code, MethodNode mth) {
add(mth, code);
}
public void addForField(CodeWriter code, FieldNode field) {
public void addForField(ICodeWriter code, FieldNode field) {
add(field, code);
}
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
List<AnnotationsList> paramList = paramsAnnotations.getParamList();
public void addForParameter(ICodeWriter code, MethodParamsAttr paramsAnnotations, int n) {
List<AnnotationsAttr> paramList = paramsAnnotations.getParamList();
if (n >= paramList.size()) {
return;
}
AnnotationsList aList = paramList.get(n);
AnnotationsAttr aList = paramList.get(n);
if (aList == null || aList.isEmpty()) {
return;
}
@@ -61,21 +63,21 @@ public class AnnotationGen {
}
}
private void add(IAttributeNode node, CodeWriter code) {
AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
private void add(IAttributeNode node, ICodeWriter code) {
AnnotationsAttr aList = node.get(JadxAttrType.ANNOTATION_LIST);
if (aList == null || aList.isEmpty()) {
return;
}
for (IAnnotation a : aList.getAll()) {
String aCls = a.getAnnotationClass();
if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
if (!aCls.equals(Consts.OVERRIDE_ANNOTATION)) {
code.startLine();
formatAnnotation(code, a);
}
}
}
private void formatAnnotation(CodeWriter code, IAnnotation a) {
private void formatAnnotation(ICodeWriter code, IAnnotation a) {
code.add('@');
ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass());
if (annCls != null) {
@@ -116,7 +118,7 @@ public class AnnotationGen {
return paramName;
}
public void addThrows(MethodNode mth, CodeWriter code) {
public void addThrows(MethodNode mth, ICodeWriter code) {
List<ArgType> throwList = mth.getThrows();
if (!throwList.isEmpty()) {
code.add(" throws ");
@@ -130,20 +132,16 @@ public class AnnotationGen {
}
}
public EncodedValue getAnnotationDefaultValue(String name) {
IAnnotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
if (an != null) {
EncodedValue defValue = an.getDefaultValue();
if (defValue != null) {
IAnnotation defAnnotation = (IAnnotation) defValue.getValue();
return defAnnotation.getValues().get(name);
}
public EncodedValue getAnnotationDefaultValue(MethodNode mth) {
AnnotationDefaultAttr defaultAttr = mth.get(JadxAttrType.ANNOTATION_DEFAULT);
if (defaultAttr == null) {
return null;
}
return null;
return defaultAttr.getValue();
}
// 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) {
code.add("null");
return;
@@ -187,9 +185,9 @@ public class AnnotationGen {
case ENCODED_ENUM:
case ENCODED_FIELD:
// must be a static field
if (value instanceof IFieldData) {
FieldInfo field = FieldInfo.fromData(root, (IFieldData) value);
InsnGen.makeStaticFieldAccess(code, field, classGen);
if (value instanceof IFieldRef) {
FieldInfo fieldInfo = FieldInfo.fromRef(root, (IFieldRef) value);
InsnGen.makeStaticFieldAccess(code, fieldInfo, classGen);
} else if (value instanceof FieldInfo) {
InsnGen.makeStaticFieldAccess(code, (FieldInfo) value, classGen);
} else {
@@ -9,29 +9,33 @@ import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.CommentsLevel;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.attributes.FieldInitAttr.InitType;
import jadx.core.dex.attributes.FieldInitInsnAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
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.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
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.mods.ConstructorInsn;
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.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.EncodedValueUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -55,10 +60,13 @@ public class ClassGen {
private final boolean showInconsistentCode;
private final Set<ClassInfo> imports = new HashSet<>();
private int clsDeclLine;
private int clsDeclOffset;
private boolean bodyGenStarted;
@Nullable
private NameGen outerNameGen;
public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
}
@@ -82,10 +90,10 @@ public class ClassGen {
}
public ICodeInfo makeClass() throws CodegenException {
CodeWriter clsBody = new CodeWriter();
ICodeWriter clsBody = cls.root().makeCodeWriter();
addClassCode(clsBody);
CodeWriter clsCode = new CodeWriter();
ICodeWriter clsCode = cls.root().makeCodeWriter();
if (!"".equals(cls.getPackage())) {
clsCode.add("package ").add(cls.getPackage()).add(';');
clsCode.newLine();
@@ -110,20 +118,20 @@ public class ClassGen {
return clsCode.finish();
}
public void addClassCode(CodeWriter code) throws CodegenException {
public void addClassCode(ICodeWriter code) throws CodegenException {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
if (Consts.DEBUG_USAGE) {
addClassUsageInfo(code, cls);
}
CodeGenUtils.addComments(code, cls);
insertDecompilationProblems(code, cls);
CodeGenUtils.addErrorsAndComments(code, cls);
CodeGenUtils.addSourceFileInfo(code, cls);
addClassDeclaration(code);
addClassBody(code);
}
public void addClassDeclaration(CodeWriter clsCode) {
public void addClassDeclaration(ICodeWriter clsCode) {
AccessInfo af = cls.getAccessFlags();
if (af.isInterface()) {
af = af.remove(AccessFlags.ABSTRACT)
@@ -141,9 +149,8 @@ public class ClassGen {
annotationGen.addForClass(clsCode);
insertRenameInfo(clsCode, cls);
CodeGenUtils.addSourceFileInfo(clsCode, cls);
clsCode.startLineWithNum(cls.getSourceLine());
clsCode.add(af.makeString());
CodeGenUtils.addInputFileInfo(clsCode, cls);
clsCode.startLineWithNum(cls.getSourceLine()).add(af.makeString(cls.checkCommentsLevel(CommentsLevel.INFO)));
if (af.isInterface()) {
if (af.isAnnotation()) {
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()) {
return false;
}
@@ -229,7 +236,7 @@ public class ClassGen {
return true;
}
public void addClassBody(CodeWriter clsCode) throws CodegenException {
public void addClassBody(ICodeWriter clsCode) throws CodegenException {
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
* classes)
*/
public void addClassBody(CodeWriter clsCode, boolean printClassName) throws CodegenException {
public void addClassBody(ICodeWriter clsCode, boolean printClassName) throws CodegenException {
clsCode.add('{');
setBodyGenStarted(true);
clsDeclLine = clsCode.getLine();
clsCode.incIndent();
if (printClassName) {
clsCode.startLine();
clsCode.add("/* class " + cls.getFullName() + " */");
if (printClassName && cls.checkCommentsLevel(CommentsLevel.INFO)) {
clsCode.add(" // from class: " + cls.getClassInfo().getFullName());
}
setBodyGenStarted(true);
clsDeclOffset = clsCode.getLength();
clsCode.incIndent();
addFields(clsCode);
addInnerClsAndMethods(clsCode);
clsCode.decIndent();
clsCode.startLine('}');
}
private void addInnerClsAndMethods(CodeWriter clsCode) {
private void addInnerClsAndMethods(ICodeWriter clsCode) {
Stream.of(cls.getInnerClasses(), cls.getMethods())
.flatMap(Collection::stream)
.filter(node -> !node.contains(AFlag.DONT_GENERATE))
.filter(node -> !node.contains(AFlag.DONT_GENERATE) || fallback)
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
.forEach(node -> {
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 {
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
code.newLine();
@@ -279,15 +285,18 @@ public class ClassGen {
private boolean isInnerClassesPresents() {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
if (!innerCls.contains(AType.ANONYMOUS_CLASS)) {
return true;
}
}
return false;
}
private void addMethod(CodeWriter code, MethodNode mth) {
if (code.getLine() != clsDeclLine) {
private void addMethod(ICodeWriter code, MethodNode mth) {
if (skipMethod(mth)) {
return;
}
if (code.getLength() != clsDeclOffset) {
code.newLine();
}
int savedIndent = code.getIndent();
@@ -297,15 +306,35 @@ public class ClassGen {
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw new JadxRuntimeException("Method generation error", e);
}
code.newLine().add("/*");
code.newLine().addMultiLine(ErrorsCounter.error(mth, "Method generation error", e));
Utils.appendStackTrace(code, e);
code.newLine().add("*/");
mth.addError("Method generation error", e);
CodeGenUtils.addErrors(code, mth);
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() {
for (MethodNode mth : cls.getMethods()) {
if (!mth.contains(AFlag.DONT_GENERATE)) {
@@ -315,17 +344,15 @@ public class ClassGen {
return false;
}
public void addMethodCode(CodeWriter code, MethodNode mth) throws CodegenException {
CodeGenUtils.addComments(code, mth);
public void addMethodCode(ICodeWriter code, MethodNode mth) throws CodegenException {
CodeGenUtils.addErrorsAndComments(code, mth);
if (mth.isNoCode()) {
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
code.add(';');
} else {
insertDecompilationProblems(code, mth);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode && showInconsistentCode) {
mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false;
}
MethodGen mthGen;
@@ -345,35 +372,14 @@ public class ClassGen {
}
}
public void insertDecompilationProblems(CodeWriter code, AttrNode node) {
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 {
private void addFields(ICodeWriter code) throws CodegenException {
addEnumFields(code);
for (FieldNode f : cls.getFields()) {
addField(code, f);
}
}
public void addField(CodeWriter code, FieldNode f) {
public void addField(ICodeWriter code, FieldNode f) {
if (f.contains(AFlag.DONT_GENERATE)) {
return;
}
@@ -383,28 +389,40 @@ public class ClassGen {
CodeGenUtils.addComments(code, f);
annotationGen.addForField(code, f);
if (f.getFieldInfo().isRenamed()) {
boolean addInfoComments = f.checkCommentsLevel(CommentsLevel.INFO);
if (f.getFieldInfo().isRenamed() && addInfoComments) {
code.newLine();
CodeGenUtils.addRenamedComment(code, f, f.getName());
}
code.startLine(f.getAccessFlags().makeString());
code.startLine(f.getAccessFlags().makeString(addInfoComments));
useType(code, f.getType());
code.add(' ');
code.attachDefinition(f);
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(" = ");
if (fv.getValueType() == InitType.CONST) {
EncodedValue encodedValue = fv.getEncodedValue();
if (encodedValue.getType() == EncodedType.ENCODED_NULL) {
addInsnBody(insnGen, code, initInsnAttr.getInsn());
} else {
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));
} 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(';');
@@ -419,7 +437,7 @@ public class ClassGen {
return false;
}
private void addEnumFields(CodeWriter code) throws CodegenException {
private void addEnumFields(ICodeWriter code) throws CodegenException {
EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS);
if (enumFields == null) {
return;
@@ -427,6 +445,8 @@ public class ClassGen {
InsnGen igen = null;
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext();) {
EnumField f = it.next();
CodeGenUtils.addComments(code, f.getField());
code.startLine(f.getField().getAlias());
ConstructorInsn constrInsn = f.getConstrInsn();
MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth());
@@ -439,7 +459,7 @@ public class ClassGen {
}
if (f.getCls() != null) {
code.add(' ');
new ClassGen(f.getCls(), this).addClassBody(code);
new ClassGen(f.getCls(), this).addClassBody(code, true);
}
if (it.hasNext()) {
code.add(',');
@@ -471,7 +491,7 @@ public class ClassGen {
return new InsnGen(mthGen, false);
}
private void addInsnBody(InsnGen insnGen, CodeWriter code, InsnNode insn) {
private void addInsnBody(InsnGen insnGen, ICodeWriter code, InsnNode insn) {
try {
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
} 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();
if (stype == null) {
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));
}
public void useClass(CodeWriter code, ArgType type) {
public void useClass(ICodeWriter code, ArgType type) {
ArgType outerType = type.getOuterType();
if (outerType != null) {
useClass(code, outerType);
code.add('.');
// import not needed, force use short name
useClassShortName(code, type.getObject());
addInnerType(code, type);
return;
}
useClass(code, ClassInfo.fromType(cls.root(), type));
addGenerics(code, type);
}
private void addInnerType(ICodeWriter code, ArgType baseType) {
ArgType innerType = baseType.getInnerType();
ArgType outerType = innerType.getOuterType();
if (outerType != null) {
useClassWithShortName(code, baseType, outerType);
code.add('.');
addInnerType(code, innerType);
return;
}
useClassWithShortName(code, baseType, innerType);
}
private void useClassWithShortName(ICodeWriter code, ArgType baseType, ArgType type) {
String fullNameObj;
if (type.getObject().contains(".")) {
fullNameObj = type.getObject();
} else {
fullNameObj = baseType.getObject();
}
ClassInfo classInfo = ClassInfo.fromName(cls.root(), fullNameObj);
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
code.add(classInfo.getAliasShortName());
addGenerics(code, type);
}
private void addGenerics(ICodeWriter code, ArgType type) {
List<ArgType> generics = type.getGenericTypes();
if (generics != null) {
code.add('<');
@@ -536,16 +586,7 @@ public class ClassGen {
}
}
private void useClassShortName(CodeWriter code, String object) {
ClassInfo classInfo = ClassInfo.fromName(cls.root(), object);
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
code.add(classInfo.getAliasShortName());
}
public void useClass(CodeWriter code, ClassInfo classInfo) {
public void useClass(ICodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
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);
addClsName(code, classNode.getClassInfo());
}
private void addClsName(CodeWriter code, ClassInfo classInfo) {
public void addClsName(ICodeWriter code, ClassInfo classInfo) {
String clsName = useClassInternal(cls.getClassInfo(), classInfo);
code.add(clsName);
}
@@ -685,14 +726,14 @@ public class ClassGen {
return searchCollision(root, useCls.getParentClass(), searchCls);
}
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
private void insertRenameInfo(ICodeWriter code, ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
if (classInfo.hasAlias()) {
if (classInfo.hasAlias() && cls.checkCommentsLevel(CommentsLevel.INFO)) {
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();
code.startLine("// deps - ").add(Integer.toString(deps.size()));
for (ClassNode depCls : deps) {
@@ -710,7 +751,7 @@ public class ClassGen {
}
}
static void addMthUsageInfo(CodeWriter code, MethodNode mth) {
static void addMthUsageInfo(ICodeWriter code, MethodNode mth) {
List<MethodNode> useInMths = mth.getUseIn();
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
for (MethodNode useMth : useInMths) {
@@ -718,7 +759,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();
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
for (MethodNode useMth : useInMths) {
@@ -745,4 +786,13 @@ public class ClassGen {
public void setBodyGenStarted(boolean 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.Queue;
import jadx.api.ICodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.IfOp;
@@ -41,15 +42,15 @@ public class ConditionGen extends InsnGen {
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);
}
void wrap(CodeWriter code, IfCondition condition) throws CodegenException {
void wrap(ICodeWriter code, IfCondition condition) throws CodegenException {
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);
switch (condition.getMode()) {
case COMPARE:
@@ -75,7 +76,7 @@ public class ConditionGen extends InsnGen {
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);
if (wrap) {
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);
if (wrap) {
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();
InsnArg firstArg = compare.getA();
InsnArg secondArg = compare.getB();
@@ -130,7 +131,7 @@ public class ConditionGen extends InsnGen {
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());
code.add(" ? ");
add(code, stack, condition.second());
@@ -138,12 +139,12 @@ public class ConditionGen extends InsnGen {
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('!');
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 ? " && " : " || ";
Iterator<IfCondition> it = condition.getArgs().iterator();
while (it.hasNext()) {
@@ -9,7 +9,12 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
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.AType;
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.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeCustomNode;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
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.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -62,7 +69,6 @@ public class InsnGen {
protected final MethodNode mth;
protected final RootNode root;
protected final boolean fallback;
protected final boolean attachInsns;
protected enum Flags {
BODY_ONLY,
@@ -75,32 +81,39 @@ public class InsnGen {
this.mth = mgen.getMethodNode();
this.root = mth.root();
this.fallback = fallback;
this.attachInsns = root.getArgs().isJsonOutput();
}
private boolean isFallback() {
return fallback;
}
public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException {
int len = code.bufLength();
public void addArgDot(ICodeWriter code, InsnArg arg) throws CodegenException {
int len = code.getLength();
addArg(code, arg, true);
if (len != code.bufLength()) {
if (len != code.getLength()) {
code.add('.');
}
}
public void addArg(CodeWriter code, InsnArg arg) throws CodegenException {
public void addArg(ICodeWriter code, InsnArg arg) throws CodegenException {
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()) {
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()) {
code.add(lit((LiteralArg) arg));
addLiteralArg(code, (LiteralArg) arg, flags);
} else if (arg.isInsnWrap()) {
addWrappedArg(code, (InsnWrapArg) arg, wrap);
addWrappedArg(code, (InsnWrapArg) arg, flags);
} else if (arg.isNamed()) {
code.add(((Named) arg).getName());
} 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();
if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
code.add('(');
makeInsn(wrapInsn, code, Flags.INLINE);
code.add(')');
} else {
Flags flags = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
makeInsn(wrapInsn, code, flags);
makeInsnBody(code, wrapInsn, flags);
}
}
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
public void assignVar(ICodeWriter code, InsnNode insn) throws CodegenException {
RegisterArg arg = insn.getResult();
if (insn.contains(AFlag.DECLARE_VAR)) {
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());
}
public void declareVar(CodeWriter code, CodeVar codeVar) {
public void declareVar(ICodeWriter code, CodeVar codeVar) {
if (codeVar.isFinal()) {
code.add("final ");
}
useType(code, codeVar.getType());
code.add(' ');
if (code.isMetadataSupported()) {
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
}
code.add(mgen.getNameGen().assignArg(codeVar));
}
@@ -146,9 +170,9 @@ public class InsnGen {
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();
FieldNode fieldNode = pCls.root().deepResolveField(field);
FieldNode fieldNode = pCls.root().resolveField(field);
if (fieldNode != null) {
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
if (replace != null) {
@@ -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();
// TODO
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
@@ -186,7 +210,7 @@ public class InsnGen {
}
code.add('.');
}
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field);
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
@@ -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());
}
public void useClass(CodeWriter code, ArgType type) {
public void useClass(ICodeWriter code, ArgType 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);
}
protected void useType(CodeWriter code, ArgType type) {
protected void useType(ICodeWriter code, ArgType 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);
}
@@ -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_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) {
return;
}
@@ -231,9 +255,7 @@ public class InsnGen {
} else {
if (flag != Flags.INLINE) {
code.startLineWithNum(insn.getSourceLine());
if (attachInsns) {
code.attachLineAnnotation(insn);
}
InsnCodeOffset.attach(code, insn);
if (insn.contains(AFlag.COMMENT_OUT)) {
code.add("// ");
}
@@ -249,6 +271,7 @@ public class InsnGen {
makeInsnBody(code, insn, EMPTY_FLAGS);
if (flag != Flags.INLINE) {
code.add(';');
CodeGenUtils.addCodeComments(code, mth, insn);
}
}
} 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()) {
case CONST_STR:
String str = ((ConstStringNode) insn).getString();
@@ -371,11 +394,15 @@ public class InsnGen {
ArgType arrayType = ((NewArrayNode) insn).getArrayType();
code.add("new ");
useType(code, arrayType.getArrayRootElement());
code.add('[');
addArg(code, insn.getArg(0));
code.add(']');
int k = 0;
int argsCount = insn.getArgsCount();
for (; k < argsCount; k++) {
code.add('[');
addArg(code, insn.getArg(k), false);
code.add(']');
}
int dim = arrayType.getArrayDimension();
for (int i = 0; i < dim - 1; i++) {
for (; k < dim - 1; k++) {
code.add("[]");
}
break;
@@ -478,7 +505,7 @@ public class InsnGen {
break;
case ONE_ARG:
addArg(code, insn.getArg(0));
addArg(code, insn.getArg(0), state);
break;
/* fallback mode instructions */
@@ -545,7 +572,7 @@ public class InsnGen {
case FILL_ARRAY_DATA:
fallbackOnlyInsn(insn);
code.add("fill-array " + insn.toString());
code.add("fill-array " + insn);
break;
case SWITCH_DATA:
@@ -553,6 +580,17 @@ public class InsnGen {
code.add(insn.toString());
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:
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.
* Use one by one array fill (can be replaced with System.arrayCopy)
*/
private void fillArray(CodeWriter code, FillArrayInsn arrayNode) throws CodegenException {
code.add("// fill-array-data instruction");
private void fillArray(ICodeWriter code, FillArrayInsn arrayNode) throws CodegenException {
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
code.add("// fill-array-data instruction");
}
code.startLine();
InsnArg arrArg = arrayNode.getArg(0);
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);
if (wrap) {
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)) {
code.add("new ");
useType(code, insn.getArrayType());
}
code.add('{');
int c = insn.getArgsCount();
int wrap = 0;
for (int i = 0; i < c; i++) {
addArg(code, insn.getArg(i), false);
if (i + 1 < c) {
code.add(", ");
}
wrap++;
if (wrap == 1000) {
code.startLine();
wrap = 0;
}
}
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());
if (cls != null && cls.isAnonymous() && !fallback) {
cls.ensureProcessed();
@@ -635,13 +681,22 @@ public class InsnGen {
if (insn.isSelf()) {
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
}
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
if (insn.isSuper()) {
code.attachAnnotation(callMth);
code.add("super");
} else if (insn.isThis()) {
code.attachAnnotation(callMth);
code.add("this");
} else {
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);
if (genericInfoAttr != null) {
code.add('<');
@@ -659,26 +714,18 @@ public class InsnGen {
code.add('>');
}
}
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
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) {
cls.remove(AFlag.ANONYMOUS_CLASS);
cls.remove(AType.ANONYMOUS_CLASS);
cls.remove(AFlag.DONT_GENERATE);
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
throw new CodegenException("Anonymous inner class unlimited recursion detected."
+ " Convert class to inner: " + cls.getClassInfo().getFullName());
}
cls.add(AFlag.DONT_GENERATE);
ArgType parent;
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
} else {
parent = cls.getSuperClass();
}
ArgType parent = cls.get(AType.ANONYMOUS_CLASS).getBaseType();
// hide empty anonymous constructors
for (MethodNode ctor : cls.getMethods()) {
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
@@ -686,32 +733,46 @@ public class InsnGen {
ctor.add(AFlag.DONT_GENERATE);
}
}
code.add("new ");
if (parent == null) {
code.add("Object");
} else {
useClass(code, parent);
}
useClass(code, parent);
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
if (callMth != null) {
// copy var names
List<RegisterArg> mthArgs = callMth.getArgRegs();
int argsCount = Math.min(insn.getArgsCount(), mthArgs.size());
for (int i = 0; i < argsCount; i++) {
InsnArg arg = insn.getArg(i);
if (arg.isRegister()) {
RegisterArg mthArg = mthArgs.get(i);
RegisterArg insnArg = (RegisterArg) arg;
mthArg.getSVar().setCodeVar(insnArg.getSVar().getCodeVar());
}
}
}
generateMethodArguments(code, insn, 0, callMth);
code.add(' ');
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();
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
MethodNode callMthNode = mth.root().resolveMethod(callMth);
int k = 0;
InvokeType type = insn.getInvokeType();
switch (type) {
case DIRECT:
case VIRTUAL:
case INTERFACE:
InsnArg arg = insn.getArg(0);
// FIXME: add 'this' for equals methods in scope
if (!arg.isThis()) {
if (needInvokeArg(arg)) {
addArgDot(code, arg);
}
k++;
@@ -746,8 +807,137 @@ public class InsnGen {
generateMethodArguments(code, insn, k, callMthNode);
}
// FIXME: add 'this' for equals methods in scope
private boolean needInvokeArg(InsnArg arg) {
if (arg.isAnyThis()) {
if (arg.isThis()) {
return false;
}
ClassNode clsNode = mth.root().resolveClass(arg.getType());
if (clsNode != null && clsNode.contains(AFlag.DONT_GENERATE)) {
return false;
}
}
return true;
}
private void makeInvokeLambda(ICodeWriter code, InvokeCustomNode customNode) throws CodegenException {
if (customNode.isUseRef()) {
makeRefLambda(code, customNode);
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
private ClassInfo getClassForSuperCall(CodeWriter code, MethodInfo callMth) {
private ClassInfo getClassForSuperCall(ICodeWriter code, MethodInfo callMth) {
ClassNode useCls = mth.getParentClass();
ClassInfo insnCls = useCls.getClassInfo();
ClassInfo declClass = callMth.getDeclClass();
@@ -776,7 +966,7 @@ public class InsnGen {
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 {
int k = startArgNum;
if (mthNode != null && mthNode.contains(AFlag.SKIP_FIRST_ARG)) {
@@ -813,7 +1003,7 @@ public class InsnGen {
/**
* 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)) {
return false;
}
@@ -835,7 +1025,7 @@ public class InsnGen {
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);
if (wrap) {
code.add('(');
@@ -848,7 +1038,6 @@ public class InsnGen {
} else {
condGen.wrap(code, insn.getCondition());
code.add(" ? ");
addCastIfNeeded(code, first, second);
addArg(code, first, false);
code.add(" : ");
addArg(code, second, false);
@@ -858,34 +1047,7 @@ public class InsnGen {
}
}
private void addCastIfNeeded(CodeWriter code, InsnArg first, InsnArg second) {
if (first.isLiteral() && second.isLiteral()) {
if (first.getType() == ArgType.BYTE) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (lit1 != Byte.MAX_VALUE && lit1 != Byte.MIN_VALUE
&& lit2 != Byte.MAX_VALUE && lit2 != Byte.MIN_VALUE) {
code.add("(byte) ");
}
} else if (first.getType() == ArgType.SHORT) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (lit1 != Short.MAX_VALUE && lit1 != Short.MIN_VALUE
&& lit2 != Short.MAX_VALUE && lit2 != Short.MIN_VALUE) {
code.add("(short) ");
}
} else if (first.getType() == ArgType.CHAR) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (!NameMapper.isPrintableChar((char) (lit1))
&& !NameMapper.isPrintableChar((char) (lit2))) {
code.add("(char) ");
}
}
}
}
private void makeArith(ArithNode insn, CodeWriter code, Set<Flags> state) throws CodegenException {
private void makeArith(ArithNode insn, ICodeWriter code, Set<Flags> state) throws CodegenException {
if (insn.contains(AFlag.ARITH_ONEARG)) {
makeArithOneArg(insn, code);
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();
InsnArg resArg = insn.getArg(0);
InsnArg arg = insn.getArg(1);
@@ -3,17 +3,25 @@ package jadx.core.codegen;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
import jadx.core.Consts;
import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag;
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.MethodOverrideAttr;
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.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
@@ -34,7 +41,6 @@ import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxOverflowException;
import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP;
@@ -53,7 +59,7 @@ public class MethodGen {
this.mth = mth;
this.classGen = classGen;
this.annotationGen = classGen.getAnnotationGen();
this.nameGen = new NameGen(mth, classGen.isFallbackMode());
this.nameGen = new NameGen(mth, classGen);
}
public ClassGen getClassGen() {
@@ -68,7 +74,7 @@ public class MethodGen {
return mth;
}
public boolean addDefinition(CodeWriter code) {
public boolean addDefinition(ICodeWriter code) {
if (mth.getMethodInfo().isClassInit()) {
code.attachDefinition(mth);
code.startLine("static");
@@ -97,20 +103,31 @@ public class MethodGen {
if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.PUBLIC);
}
if (mth.getMethodInfo().isConstructor() && mth.getParentClass().isEnum()) {
ai = ai.remove(AccessInfo.VISIBILITY_FLAGS);
}
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
}
if (mth.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
if (mth.contains(AFlag.INCONSISTENT_CODE) && mth.checkCommentsLevel(CommentsLevel.ERROR)) {
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.add(ai.makeString());
if (Consts.DEBUG) {
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
}
if (clsAccFlags.isInterface() && !mth.isNoCode()) {
code.add(ai.makeString(mth.checkCommentsLevel(CommentsLevel.INFO)));
if (clsAccFlags.isInterface() && !mth.isNoCode() && !mth.getAccessFlags().isStatic()) {
// add 'default' for method with code in interface
code.add("default ");
}
@@ -137,7 +154,7 @@ public class MethodGen {
} else if (args.size() > 2) {
args = args.subList(2, args.size());
} 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)) {
args = args.subList(1, args.size());
@@ -147,9 +164,9 @@ public class MethodGen {
annotationGen.addThrows(mth, code);
// add default value if in annotation class
// add default value for annotation class
if (mth.getParentClass().getAccessFlags().isAnnotation()) {
EncodedValue def = annotationGen.getAnnotationDefaultValue(mth.getName());
EncodedValue def = annotationGen.getAnnotationDefaultValue(mth);
if (def != null) {
code.add(" default ");
annotationGen.encodeValue(mth.root(), code, def);
@@ -158,25 +175,27 @@ public class MethodGen {
return true;
}
private void addOverrideAnnotation(CodeWriter code, MethodNode mth) {
private void addOverrideAnnotation(ICodeWriter code, MethodNode mth) {
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr == null) {
return;
}
code.startLine("@Override");
code.add(" // ");
Iterator<IMethodDetails> it = overrideAttr.getOverrideList().iterator();
while (it.hasNext()) {
IMethodDetails methodDetails = it.next();
code.add(methodDetails.getMethodInfo().getDeclClass().getAliasFullName());
if (it.hasNext()) {
code.add(", ");
if (!overrideAttr.getBaseMethods().contains(mth)) {
code.startLine("@Override");
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
code.add(" // ");
code.add(Utils.listToString(overrideAttr.getOverrideList(), ", ",
md -> md.getMethodInfo().getDeclClass().getAliasFullName()));
}
}
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) {
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
private void addMethodArguments(ICodeWriter code, List<RegisterArg> args) {
MethodParamsAttr paramsAnnotation = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS);
int i = 0;
Iterator<RegisterArg> it = args.iterator();
while (it.hasNext()) {
@@ -184,7 +203,7 @@ public class MethodGen {
SSAVar ssaVar = mthArg.getSVar();
CodeVar var;
if (ssaVar == null) {
// null for abstract or interface methods
// abstract or interface methods
var = CodeVar.fromMthArg(mthArg, classGen.isFallbackMode());
} else {
var = ssaVar.getCodeVar();
@@ -212,14 +231,18 @@ public class MethodGen {
classGen.useType(code, elType);
code.add("...");
} 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);
}
} else {
classGen.useType(code, argType);
}
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++;
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()) {
addFallbackMethodCode(code, FALLBACK_MODE);
} 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 {
RegionGen regionGen = new RegionGen(this);
regionGen.makeRegion(code, mth.getRegion());
} catch (StackOverflowError | BootstrapMethodError e) {
mth.addError("Method code generation error", new JadxOverflowException("StackOverflow"));
classGen.insertDecompilationProblems(code, mth);
CodeGenUtils.addErrors(code, mth);
dumpInstructions(code);
} catch (Exception e) {
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw e;
}
mth.addError("Method code generation error", e);
classGen.insertDecompilationProblems(code, mth);
CodeGenUtils.addErrors(code, mth);
dumpInstructions(code);
}
}
public void dumpInstructions(CodeWriter code) {
code.startLine("/*");
addFallbackMethodCode(code, COMMENTED_DUMP);
code.startLine("*/");
public void dumpInstructions(ICodeWriter code) {
if (mth.checkCommentsLevel(CommentsLevel.ERROR)) {
code.startLine("/*");
addFallbackMethodCode(code, COMMENTED_DUMP);
code.startLine("*/");
}
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(mth.getParentClass().getClassInfo().getAliasFullName())
.add('.')
@@ -272,27 +296,46 @@ public class MethodGen {
.add("\");");
}
public void addFallbackMethodCode(CodeWriter code, FallbackOption fallbackOption) {
// load original instructions
try {
mth.unload();
mth.load();
for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) {
DepthTraversal.visit(visitor, mth);
public void addFallbackMethodCode(ICodeWriter code, FallbackOption fallbackOption) {
if (fallbackOption != FALLBACK_MODE) {
List<JadxError> errors = mth.getAll(AType.JADX_ERROR); // preserve error before unload
try {
// load original instructions
mth.unload();
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();
if (insnArr == null) {
code.startLine("// Can't load method instructions.");
return;
}
if (insnArr.length > 100) {
code.startLine("// Method dump skipped, instructions count: " + insnArr.length);
return;
if (fallbackOption == COMMENTED_DUMP && mth.getCommentsLevel() != CommentsLevel.DEBUG) {
long insnCountEstimate = Stream.of(insnArr)
.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();
if (mth.getThisArg() != null) {
@@ -308,15 +351,20 @@ public class MethodGen {
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();
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
boolean attachInsns = mth.root().getArgs().isJsonOutput();
InsnNode prevInsn = null;
for (InsnNode insn : insnArr) {
if (insn == null) {
continue;
}
if (insn.contains(AType.JADX_ERROR)) {
for (JadxError error : insn.getAll(AType.JADX_ERROR)) {
code.startLine("// ").add(error.getError());
}
continue;
}
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ':');
@@ -332,11 +380,9 @@ public class MethodGen {
code.startLine("*/");
code.startLine("// ");
} else {
code.startLine();
}
if (attachInsns) {
code.attachLineAnnotation(insn);
code.startLineWithNum(insn.getSourceLine());
}
InsnCodeOffset.attach(code, insn);
RegisterArg resArg = insn.getResult();
if (resArg != null) {
ArgType varType = resArg.getInitType();
@@ -350,10 +396,11 @@ public class MethodGen {
code.incIndent();
}
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
CatchAttr catchAttr = insn.get(AType.EXC_CATCH);
if (catchAttr != null) {
code.add(" // " + catchAttr);
}
CodeGenUtils.addCodeComments(code, mth, insn);
} catch (Exception e) {
LOG.debug("Error generate fallback instruction: ", e.getCause());
code.setIndent(startIndent);
@@ -53,16 +53,26 @@ public class NameGen {
"java.lang.Exception", "exc");
}
public NameGen(MethodNode mth, boolean fallback) {
public NameGen(MethodNode mth, ClassGen classGen) {
this.mth = mth;
this.fallback = fallback;
this.fallback = classGen.isFallbackMode();
NameGen outerNameGen = classGen.getOuterNameGen();
if (outerNameGen != null) {
inheritUsedNames(outerNameGen);
}
addNamesUsedInClass();
}
public void inheritUsedNames(NameGen otherNameGen) {
varNames.addAll(otherNameGen.varNames);
}
private void addNamesUsedInClass() {
ClassNode parentClass = mth.getParentClass();
for (FieldNode field : parentClass.getFields()) {
varNames.add(field.getAlias());
if (field.isStatic()) {
varNames.add(field.getAlias());
}
}
for (ClassNode innerClass : parentClass.getInnerClasses()) {
varNames.add(innerClass.getClassInfo().getAliasShortName());
@@ -127,9 +137,6 @@ public class NameGen {
if (!NameMapper.isValidAndPrintable(name)) {
name = getFallbackName(var);
}
if (Consts.DEBUG) {
name += '_' + getFallbackName(var);
}
return name;
}
@@ -155,8 +162,7 @@ public class NameGen {
InsnNode assignInsn = assignArg.getParentInsn();
if (assignInsn != null) {
String name = makeNameFromInsn(assignInsn);
if (name != null && !NameMapper.isReserved(name)) {
assignArg.setName(name);
if (name != null && NameMapper.isValidAndPrintable(name)) {
return name;
}
}
@@ -195,7 +201,11 @@ public class NameGen {
return vName;
}
if (shortName != null) {
return StringUtils.escape(shortName.toLowerCase());
String lower = StringUtils.escape(shortName.toLowerCase());
if (shortName.equals(lower)) {
return lower + "Var";
}
return lower;
}
}
return StringUtils.escape(type.toString());
@@ -8,9 +8,14 @@ import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.dex.attributes.AFlag;
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.ForceReturnAttr;
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.IBlock;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.Region;
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.trycatch.ExceptionHandler;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -51,56 +57,30 @@ public class RegionGen extends InsnGen {
super(mgen, false);
}
public void makeRegion(CodeWriter code, IContainer cont) throws CodegenException {
if (cont instanceof IBlock) {
makeSimpleBlock((IBlock) cont, 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);
}
public void makeRegion(ICodeWriter code, IContainer cont) throws CodegenException {
declareVars(code, cont);
cont.generate(this, code);
}
private void declareVars(CodeWriter code, IContainer cont) {
private void declareVars(ICodeWriter code, IContainer cont) {
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
if (declVars != null) {
for (CodeVar v : declVars.getVars()) {
code.startLine();
declareVar(code, v);
code.add(';');
CodeGenUtils.addCodeComments(code, mth, v.getAnySsaVar().getAssign());
}
}
}
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException {
declareVars(code, region);
for (IContainer c : region.getSubBlocks()) {
makeRegion(code, c);
}
}
public void makeRegionIndent(CodeWriter code, IContainer region) throws CodegenException {
private void makeRegionIndent(ICodeWriter code, IContainer region) throws CodegenException {
code.incIndent();
makeRegion(code, region);
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)) {
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) {
code.startLineWithNum(region.getSourceLine());
} else {
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);
if (comment) {
code.add("// ");
@@ -140,6 +110,15 @@ public class RegionGen extends InsnGen {
code.add("if (");
new ConditionGen(this).add(code, region.getCondition());
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());
if (comment) {
code.startLine("// }");
@@ -166,7 +145,7 @@ public class RegionGen extends InsnGen {
/**
* 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) {
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
if (subBlocks.size() == 1) {
@@ -181,82 +160,95 @@ public class RegionGen extends InsnGen {
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);
if (labelAttr != null) {
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
code.add(mgen.getNameGen().getLoopLabel(labelAttr)).add(": ");
}
IfCondition condition = region.getCondition();
if (condition == null) {
// infinite loop
code.startLine("while (true) {");
code.add("while (true) {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
return code;
return;
}
InsnNode condInsn = condition.getFirstInsn();
InsnCodeOffset.attach(code, condInsn);
ConditionGen conditionGen = new ConditionGen(this);
LoopType type = region.getType();
if (type != null) {
if (type instanceof ForLoop) {
ForLoop forLoop = (ForLoop) type;
code.startLine("for (");
code.add("for (");
makeInsn(forLoop.getInitInsn(), code, Flags.INLINE);
code.add("; ");
conditionGen.add(code, condition);
code.add("; ");
makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE);
code.add(") {");
CodeGenUtils.addCodeComments(code, mth, condInsn);
makeRegionIndent(code, region.getBody());
code.startLine('}');
return code;
return;
}
if (type instanceof ForEachLoop) {
ForEachLoop forEachLoop = (ForEachLoop) type;
code.startLine("for (");
code.add("for (");
declareVar(code, forEachLoop.getVarArg());
code.add(" : ");
addArg(code, forEachLoop.getIterableArg(), false);
code.add(") {");
CodeGenUtils.addCodeComments(code, mth, condInsn);
makeRegionIndent(code, region.getBody());
code.startLine('}');
return code;
return;
}
throw new JadxRuntimeException("Unknown loop type: " + type.getClass());
}
if (region.isConditionAtEnd()) {
code.startLine("do {");
code.add("do {");
CodeGenUtils.addCodeComments(code, mth, condInsn);
makeRegionIndent(code, region.getBody());
code.startLineWithNum(region.getConditionSourceLine());
code.startLineWithNum(region.getSourceLine());
code.add("} while (");
conditionGen.add(code, condition);
code.add(");");
} else {
code.startLineWithNum(region.getConditionSourceLine());
code.add("while (");
conditionGen.add(code, condition);
code.add(") {");
CodeGenUtils.addCodeComments(code, mth, condInsn);
makeRegionIndent(code, region.getBody());
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 (");
addArg(code, cont.getEnterInsn().getArg(0));
InsnNode monitorEnterInsn = cont.getEnterInsn();
addArg(code, monitorEnterInsn.getArg(0));
code.add(") {");
InsnCodeOffset.attach(code, monitorEnterInsn);
CodeGenUtils.addCodeComments(code, mth, monitorEnterInsn);
makeRegionIndent(code, cont.getRegion());
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());
Objects.requireNonNull(insn, "Switch insn not found in header");
InsnArg arg = insn.getArg(0);
code.startLine("switch (");
addArg(code, arg, false);
code.add(") {");
InsnCodeOffset.attach(code, insn);
CodeGenUtils.addCodeComments(code, mth, insn);
code.incIndent();
for (CaseInfo caseInfo : sw.getCases()) {
@@ -275,22 +267,20 @@ public class RegionGen extends InsnGen {
}
code.decIndent();
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) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
code.add(fn.getAlias());
} else {
staticField(code, fn.getFieldInfo());
// print original value, sometimes replaced with incorrect field
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
if (valueAttr != null && valueAttr.getValueType() == FieldInitAttr.InitType.CONST) {
Object value = valueAttr.getEncodedValue();
if (value != null) {
code.add(" /*").add(value.toString()).add("*/");
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
// print original value, sometimes replaced with incorrect field
EncodedValue constVal = fn.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null && constVal.getValue() != null) {
code.add(" /* ").add(constVal.getValue().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 {");
InsnNode insn = BlockUtils.getFirstInsn(Utils.first(region.getTryCatchBlock().getBlocks()));
InsnCodeOffset.attach(code, insn);
CodeGenUtils.addCodeComments(code, mth, insn);
makeRegionIndent(code, region.getTryRegion());
// TODO: move search of 'allHandler' to 'TryCatchRegion'
ExceptionHandler allHandler = null;
@@ -328,7 +323,7 @@ public class RegionGen extends InsnGen {
code.startLine('}');
}
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler) throws CodegenException {
private void makeCatchBlock(ICodeWriter code, ExceptionHandler handler) throws CodegenException {
IContainer region = handler.getHandlerRegion();
if (region == null) {
return;
@@ -351,14 +346,21 @@ public class RegionGen extends InsnGen {
if (arg == null) {
code.add("unknown"); // throwing exception is too late at this point
} else if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg;
code.add(mgen.getNameGen().assignArg(reg.getSVar().getCodeVar()));
CodeVar codeVar = ((RegisterArg) arg).getSVar().getCodeVar();
if (code.isMetadataSupported()) {
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
}
code.add(mgen.getNameGen().assignArg(codeVar));
} else if (arg instanceof NamedArg) {
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
} else {
throw new JadxRuntimeException("Unexpected arg type in catch block: " + arg + ", class: " + arg.getClass().getSimpleName());
}
code.add(") {");
InsnCodeOffset.attach(code, handler.getHandlerOffset());
CodeGenUtils.addCodeComments(code, mth, handler.getHandlerBlock());
makeRegionIndent(code, region);
}
}
@@ -1,5 +1,6 @@
package jadx.core.codegen;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
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) {
if (l == Short.MAX_VALUE) {
return "Short.MAX_VALUE";
@@ -14,9 +14,12 @@ import com.google.gson.GsonBuilder;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.SimpleCodeWriter;
import jadx.core.codegen.ClassGen;
import jadx.core.codegen.CodeWriter;
import jadx.core.codegen.MethodGen;
import jadx.core.codegen.json.cls.JsonClass;
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.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
@@ -82,9 +84,8 @@ public class JsonCodeGen {
jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), this::getTypeAlias));
}
CodeWriter cw = new CodeWriter();
CodeGenUtils.addComments(cw, cls);
classGen.insertDecompilationProblems(cw, cls);
ICodeWriter cw = new SimpleCodeWriter();
CodeGenUtils.addErrorsAndComments(cw, cls);
classGen.addClassDeclaration(cw);
jsonCls.setDeclaration(cw.getCodeStr());
@@ -127,11 +128,10 @@ public class JsonCodeGen {
jsonField.setAlias(field.getAlias());
}
CodeWriter cw = new CodeWriter();
ICodeWriter cw = new SimpleCodeWriter();
classGen.addField(cw, field);
jsonField.setDeclaration(cw.getCodeStr());
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
jsonCls.getFields().add(jsonField);
}
}
@@ -152,7 +152,7 @@ public class JsonCodeGen {
jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), this::getTypeAlias));
MethodGen mthGen = new MethodGen(classGen, mth);
CodeWriter cw = new CodeWriter();
ICodeWriter cw = new AnnotatedCodeWriter();
mthGen.addDefinition(cw);
jsonMth.setDeclaration(cw.getCodeStr());
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
@@ -167,7 +167,7 @@ public class JsonCodeGen {
return Collections.emptyList();
}
CodeWriter cw = new CodeWriter();
ICodeWriter cw = mth.root().makeCodeWriter();
try {
mthGen.addInstructions(cw);
} catch (Exception e) {
@@ -179,7 +179,7 @@ public class JsonCodeGen {
return Collections.emptyList();
}
String[] lines = codeStr.split(CodeWriter.NL);
String[] lines = codeStr.split(ICodeWriter.NL);
Map<Integer, Integer> lineMapping = code.getLineMapping();
Map<CodePosition, Object> annotations = code.getAnnotations();
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
@@ -192,9 +192,9 @@ public class JsonCodeGen {
JsonCodeLine jsonCodeLine = new JsonCodeLine();
jsonCodeLine.setCode(codeLine);
jsonCodeLine.setSourceLine(lineMapping.get(line));
Object obj = annotations.get(new CodePosition(line, 0));
if (obj instanceof InsnNode) {
long offset = ((InsnNode) obj).getOffset();
Object obj = annotations.get(new CodePosition(line));
if (obj instanceof InsnCodeOffset) {
long offset = ((InsnCodeOffset) obj).getOffset();
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
}
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,47 +1,82 @@
package jadx.core.deobf;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.files.FileUtils;
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 Charset MAP_FILE_CHARSET = UTF_8;
private final Deobfuscator deobfuscator;
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> fldPresetMap = new HashMap<>();
private final Map<String, String> mthPresetMap = new HashMap<>();
public DeobfPresets(Deobfuscator deobfuscator, Path deobfMapFile) {
this.deobfuscator = deobfuscator;
@Nullable
public static DeobfPresets build(RootNode root) {
Path deobfMapPath = getPathDeobfMapPath(root);
if (deobfMapPath == null) {
return null;
}
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
return new DeobfPresets(deobfMapPath);
}
@Nullable
private static Path getPathDeobfMapPath(RootNode root) {
JadxArgs jadxArgs = root.getArgs();
if (jadxArgs.getDeobfuscationMapFileMode() == DeobfuscationMapFileMode.IGNORE) {
return null;
}
File deobfMapFile = jadxArgs.getDeobfuscationMapFile();
if (deobfMapFile != null) {
return deobfMapFile.toPath();
}
List<File> inputFiles = jadxArgs.getInputFiles();
if (inputFiles.isEmpty()) {
return null;
}
Path inputFilePath = inputFiles.get(0).toPath().toAbsolutePath();
String baseName = FileUtils.getPathBaseName(inputFilePath);
return inputFilePath.getParent().resolve(baseName + ".jobf");
}
private DeobfPresets(Path deobfMapFile) {
this.deobfMapFile = deobfMapFile;
}
/**
* Loads deobfuscator presets
*/
public void load() {
public boolean load() {
if (!Files.exists(deobfMapFile)) {
return;
return false;
}
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
try {
@@ -57,18 +92,28 @@ class DeobfPresets {
}
String origName = va[0];
String alias = va[1];
if (l.startsWith("p ")) {
deobfuscator.addPackagePreset(origName, alias);
} else if (l.startsWith("c ")) {
clsPresetMap.put(origName, alias);
} else if (l.startsWith("f ")) {
fldPresetMap.put(origName, alias);
} else if (l.startsWith("m ")) {
mthPresetMap.put(origName, alias);
switch (l.charAt(0)) {
case 'p':
pkgPresetMap.put(origName, alias);
break;
case 'c':
clsPresetMap.put(origName, alias);
break;
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);
return false;
}
}
@@ -80,75 +125,50 @@ class DeobfPresets {
return v;
}
public void save(boolean forceSave) {
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 {
public void save() throws IOException {
List<String> list = new ArrayList<>();
// packages
for (PackageNode p : deobfuscator.getRootPackage().getInnerPackages()) {
for (PackageNode pp : p.getInnerPackages()) {
dfsPackageName(list, p.getName(), pp);
}
if (p.hasAlias()) {
list.add(String.format("p %s = %s", p.getName(), p.getAlias()));
}
for (Map.Entry<String, String> pkgEntry : pkgPresetMap.entrySet()) {
list.add(String.format("p %s = %s", pkgEntry.getKey(), pkgEntry.getValue()));
}
// classes
for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) {
if (deobfClsInfo.getAlias() != null) {
list.add(String.format("c %s = %s",
deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias()));
}
for (Map.Entry<String, String> clsEntry : clsPresetMap.entrySet()) {
list.add(String.format("c %s = %s", clsEntry.getKey(), clsEntry.getValue()));
}
for (FieldInfo fld : deobfuscator.getFldMap().keySet()) {
list.add(String.format("f %s = %s", fld.getRawFullId(), fld.getAlias()));
for (Map.Entry<String, String> fldEntry : fldPresetMap.entrySet()) {
list.add(String.format("f %s = %s", fldEntry.getKey(), fldEntry.getValue()));
}
for (MethodInfo mth : deobfuscator.getMthMap().keySet()) {
list.add(String.format("m %s = %s", mth.getRawFullId(), mth.getAlias()));
for (Map.Entry<String, String> mthEntry : mthPresetMap.entrySet()) {
list.add(String.format("m %s = %s", mthEntry.getKey(), mthEntry.getValue()));
}
Collections.sort(list);
Files.write(deobfMapFile, list, MAP_FILE_CHARSET);
if (LOG.isDebugEnabled()) {
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
}
}
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()));
if (list.isEmpty()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Deobfuscation map is empty, not saving it");
}
return;
}
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) {
if (clsPresetMap.isEmpty()) {
return null;
}
return clsPresetMap.get(cls.makeRawFullName());
}
public String getForFld(FieldInfo fld) {
if (fldPresetMap.isEmpty()) {
return null;
}
return fldPresetMap.get(fld.getRawFullId());
}
public String getForMth(MethodInfo mth) {
if (mthPresetMap.isEmpty()) {
return null;
}
return mthPresetMap.get(mth.getRawFullId());
}
@@ -158,6 +178,14 @@ class DeobfPresets {
mthPresetMap.clear();
}
public Path getDeobfMapFile() {
return deobfMapFile;
}
public Map<String, String> getPkgPresetMap() {
return pkgPresetMap;
}
public Map<String, String> getClsPresetMap() {
return clsPresetMap;
}
@@ -1,14 +1,13 @@
package jadx.core.deobf;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
@@ -17,9 +16,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.core.dex.attributes.AFlag;
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.FieldInfo;
import jadx.core.dex.info.MethodInfo;
@@ -46,13 +48,12 @@ public class Deobfuscator {
private final Map<FieldInfo, String> fldMap = 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 Set<String> pkgSet = new TreeSet<>();
private final Set<String> reservedClsNames = new HashSet<>();
private final NavigableSet<MethodNode> mthProcessQueue = new TreeSet<>();
private final int maxLength;
private final int minLength;
private final boolean useSourceNameAsAlias;
@@ -63,28 +64,87 @@ public class Deobfuscator {
private int fldIndex = 0;
private int mthIndex = 0;
public Deobfuscator(JadxArgs args, RootNode root, Path deobfMapFile) {
this.args = args;
public Deobfuscator(RootNode root) {
this.root = root;
this.args = root.getArgs();
this.minLength = args.getDeobfuscationMinLength();
this.maxLength = args.getDeobfuscationMaxLength();
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
this.parseKotlinMetadata = args.isParseKotlinMetadata();
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
this.deobfPresets = DeobfPresets.build(root);
}
public void execute() {
if (!args.isDeobfuscationForceSave()) {
deobfPresets.load();
initIndexes();
if (args.getDeobfuscationMapFileMode().shouldRead()) {
if (deobfPresets.load()) {
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
}
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
initIndexes();
}
}
process();
}
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() {
@@ -92,9 +152,6 @@ public class Deobfuscator {
clsMap.clear();
fldMap.clear();
mthMap.clear();
ovrd.clear();
ovrdMap.clear();
}
private void initIndexes() {
@@ -121,99 +178,12 @@ public class Deobfuscator {
for (ClassNode cls : root.getClasses()) {
processClass(cls);
}
postProcess();
}
private void postProcess() {
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);
}
while (true) {
MethodNode next = mthProcessQueue.pollLast();
if (next == null) {
break;
}
renameMethod(next);
}
}
@@ -242,9 +212,8 @@ public class Deobfuscator {
}
renameField(field);
}
for (MethodNode mth : cls.getMethods()) {
renameMethod(mth);
}
mthProcessQueue.addAll(cls.getMethods());
for (ClassNode innerCls : cls.getInnerClasses()) {
processClass(innerCls);
}
@@ -265,20 +234,35 @@ public class Deobfuscator {
private void renameMethod(MethodNode mth) {
String alias = getMethodAlias(mth);
if (alias != null) {
mth.getMethodInfo().setAlias(alias);
}
if (mth.isVirtual()) {
resolveOverriding(mth);
applyMethodAlias(mth, alias);
}
}
public void forceRenameMethod(MethodNode mth) {
mth.getMethodInfo().setAlias(makeMethodAlias(mth));
if (mth.isVirtual()) {
resolveOverriding(mth);
String alias = makeMethodAlias(mth);
applyMethodAlias(mth, alias);
}
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) {
PackageNode pkg = getPackageNode(origPkgName, true);
pkg.setAlias(pkgAlias);
@@ -287,8 +271,10 @@ public class Deobfuscator {
/**
* Gets package node for full package name
*
* @param fullPkgName full package name
* @param create if {@code true} then will create all absent objects
* @param fullPkgName
* 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
* {@code false}
*/
@@ -349,7 +335,8 @@ public class Deobfuscator {
} else {
if (!clsMap.containsKey(classInfo)) {
String clsShortName = classInfo.getShortName();
boolean badName = shouldRename(clsShortName) || reservedClsNames.contains(clsShortName);
boolean badName = shouldRename(clsShortName)
|| (args.isRenameValid() && reservedClsNames.contains(clsShortName));
makeClsAlias(cls, badName);
}
}
@@ -368,6 +355,21 @@ public class Deobfuscator {
public String getPkgAlias(ClassNode cls) {
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;
DeobfClsInfo deobfClsInfo = clsMap.get(classInfo);
if (deobfClsInfo != null) {
@@ -388,10 +390,10 @@ public class Deobfuscator {
String alias = null;
String pkgName = null;
if (this.parseKotlinMetadata) {
ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls);
ClsAliasPair kotlinCls = KotlinMetadataUtils.getClassAlias(cls);
if (kotlinCls != null) {
alias = prepareNameFull(kotlinCls.getShortName(), "C");
pkgName = kotlinCls.getPackage();
alias = kotlinCls.getName();
pkgName = kotlinCls.getPkg();
}
}
if (alias == null && this.useSourceNameAsAlias) {
@@ -474,7 +476,7 @@ public class Deobfuscator {
@Nullable
private String getAliasFromSourceFile(ClassNode cls) {
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
SourceFileAttr sourceFileAttr = cls.get(JadxAttrType.SOURCE_FILE);
if (sourceFileAttr == null) {
return null;
}
@@ -499,7 +501,7 @@ public class Deobfuscator {
if (otherCls != null) {
return null;
}
cls.remove(AType.SOURCE_FILE);
cls.remove(JadxAttrType.SOURCE_FILE);
return name;
}
@@ -523,26 +525,32 @@ public class Deobfuscator {
@Nullable
private String getMethodAlias(MethodNode mth) {
if (mth.contains(AFlag.DONT_RENAME)) {
return null;
}
MethodInfo methodInfo = mth.getMethodInfo();
if (methodInfo.isClassInit() || methodInfo.isConstructor()) {
return null;
}
String alias = mthMap.get(methodInfo);
String alias = getAssignedAlias(methodInfo);
if (alias != null) {
return alias;
}
alias = deobfPresets.getForMth(methodInfo);
if (alias != null) {
mthMap.put(methodInfo, alias);
methodInfo.setAliasFromPreset(true);
return alias;
}
if (shouldRename(mth.getName())) {
return makeMethodAlias(mth);
}
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) {
String alias = String.format("f%d%s", fldIndex++, prepareNamePart(field.getName()));
fldMap.put(field.getFieldInfo(), alias);
@@ -550,9 +558,13 @@ public class Deobfuscator {
}
public String makeMethodAlias(MethodNode mth) {
String alias = String.format("m%d%s", mthIndex++, prepareNamePart(mth.getName()));
mthMap.put(mth.getMethodInfo(), alias);
return alias;
String prefix;
if (mth.contains(AType.METHOD_OVERRIDE)) {
prefix = "mo";
} else {
prefix = "m";
}
return String.format("%s%d%s", prefix, mthIndex++, prepareNamePart(mth.getName()));
}
private void processPackageFull(PackageNode pkg, String fullName) {
@@ -573,6 +585,7 @@ public class Deobfuscator {
if (!pkg.hasAlias()) {
String pkgName = pkg.getName();
if ((args.isDeobfuscationOn() && shouldRename(pkgName))
&& (pkg.getParentPackage() != rootPackage || !TldHelper.contains(pkgName)) // check if first level is a valid tld
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName))
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
@@ -593,20 +606,6 @@ public class Deobfuscator {
return NameMapper.removeInvalidCharsMiddle(name);
}
private String prepareNameFull(String name, String prefix) {
if (name.length() > maxLength) {
return makeHashName(name, prefix);
}
String result = NameMapper.removeInvalidChars(name, prefix);
if (result.isEmpty()) {
return makeHashName(name, prefix);
}
if (NameMapper.isReserved(result)) {
return prefix + result;
}
return result;
}
private static String makeHashName(String name, String invalidPrefix) {
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
}
@@ -5,6 +5,8 @@ import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import jadx.core.utils.StringUtils;
import static jadx.core.utils.StringUtils.notEmpty;
public class NameMapper {
@@ -99,35 +101,57 @@ public class NameMapper {
return Character.isJavaIdentifierPart(codePoint);
}
public static boolean isPrintableChar(int c) {
public static boolean isPrintableChar(char c) {
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) {
int len = str.length();
for (int i = 0; i < len; i++) {
if (!isPrintableChar(str.charAt(i))) {
int offset = 0;
while (offset < len) {
int codePoint = str.codePointAt(offset);
if (!isPrintableAsciiCodePoint(codePoint)) {
return false;
}
offset += Character.charCount(codePoint);
}
return true;
}
/**
* Return modified string with removed:
* <p>
* <ul>
* <li>not printable chars (including unicode)
* <li>chars not valid for java identifier part
* </ul>
* <p>
* Note: this 'middle' method must be used with prefixed string:
* <p>
* <ul>
* <li>can leave invalid chars for java identifier start (i.e numbers)
* <li>result not checked for reserved words
* </ul>
* <p>
*/
public static String removeInvalidCharsMiddle(String name) {
if (isValidIdentifier(name) && isAllCharsPrintable(name)) {
@@ -135,12 +159,11 @@ public class NameMapper {
}
int len = name.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
int codePoint = name.codePointAt(i);
if (isPrintableChar(codePoint) && isValidIdentifierPart(codePoint)) {
sb.append((char) codePoint);
StringUtils.visitCodePoints(name, codePoint -> {
if (isPrintableAsciiCodePoint(codePoint) && isValidIdentifierPart(codePoint)) {
sb.appendCodePoint(codePoint);
}
}
});
return sb.toString();
}
@@ -160,6 +183,16 @@ public class NameMapper {
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() {
}
}
@@ -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) {
packageAlias = alias;
cachedPackageFullAlias = null;
}
public boolean hasAlias() {
@@ -109,7 +110,8 @@ public class PackageNode {
/**
* Gets inner package node by name
*
* @param name inner package name
* @param name
* inner package name
* @return package node or {@code null}
*/
public PackageNode getInnerPackageByName(String name) {
@@ -143,6 +145,9 @@ public class PackageNode {
@Override
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 {
MTH_ENTER_BLOCK,
MTH_EXIT_BLOCK,
TRY_ENTER,
TRY_LEAVE,
@@ -25,12 +27,14 @@ public enum AFlag {
DONT_RENAME, // do not rename during deobfuscation
ADDED_TO_REGION,
EXC_TOP_SPLITTER,
EXC_BOTTOM_SPLITTER,
FINALLY_INSNS,
SKIP_FIRST_ARG,
SKIP_ARG, // skip argument in invoke call
NO_SKIP_ARGS,
ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS,
THIS,
SUPER,
@@ -72,11 +76,16 @@ public enum AFlag {
INCONSISTENT_CODE, // warning about incorrect decompilation
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
REQUEST_CODE_SHRINK,
RERUN_SSA_TRANSFORM,
METHOD_CANDIDATE_FOR_INLINE,
// Class processing flags
RESTART_CODEGEN, // codegen must be executed again
RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage
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!)
}
@@ -1,11 +1,9 @@
package jadx.core.dex.attributes;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
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.ForceReturnAttr;
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.JumpInfo;
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.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.RenameReasonAttr;
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.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.SplitterBlockAttr;
import jadx.core.dex.trycatch.TryCatchBlockAttr;
/**
* Attribute types enumeration,
@@ -38,49 +38,52 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
*
* @param <T> attribute class implementation
*/
@SuppressWarnings("InstantiationOfUtilityClass")
public class AType<T extends IAttribute> {
public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
// class, method, field, insn
public static final AType<AttrList<String>> CODE_COMMENTS = new AType<>();
// class, method, field
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
// class, method
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile completely
public static final AType<AttrList<String>> JADX_WARN = new AType<>(); // mark code as inconsistent (code can be viewed)
public static final AType<AttrList<String>> COMMENTS = new AType<>(); // any additional info about decompilation
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile
public static final AType<JadxCommentsAttr> JADX_COMMENTS = new AType<>(); // additional info about decompilation
// class
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
public static final AType<AnonymousClassAttr> ANONYMOUS_CLASS = new AType<>();
// 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<>();
// method
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>();
public static final AType<MethodBridgeAttr> BRIDGED_BY = new AType<>();
// region
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
// block
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<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<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
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
public static final AType<CatchAttr> EXC_CATCH = new AType<>();
// instruction
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
@@ -90,9 +93,4 @@ public class AType<T extends IAttribute> {
// register
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.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;
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<>();
public AttrList(AType<AttrList<T>> type) {
public AttrList(IJadxAttrType<AttrList<T>> type) {
this.type = type;
}
@@ -20,12 +21,12 @@ public class AttrList<T> implements IAttribute {
}
@Override
public AType<AttrList<T>> getType() {
public IJadxAttrType<AttrList<T>> getAttrType() {
return type;
}
@Override
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