Compare commits

...

524 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
Skylot 381afa2741 chore: fix release config 2020-11-14 16:14:13 +00:00
Skylot 82d4099541 fix(res): skip unused bytes in xml attributes parsing (#1013) 2020-11-11 16:46:26 +00:00
Skylot 5f659c8de7 fix: don't apply again already resolved types (#1012) 2020-11-10 13:31:49 +00:00
Skylot e054ea6683 fix: adjust limits to skip processing of large methods (#1012) 2020-11-10 13:23:41 +00:00
Skylot 0deafb768b fix: correct merge code variables across PHI instructions (#930) 2020-11-09 20:46:44 +00:00
Skylot cd612b452c chore: resolve some warnings reported by IntelliJ Idea 2020-11-07 15:44:57 +00:00
Skylot 009939f866 fix: prevent endless loop in method signature parsing (#1007)
Signed-off-by: Skylot <skylot@gmail.com>
2020-11-05 17:23:37 +00:00
Skylot cd006ce78e fix: improve resource type detection and remove deprecated method 2020-11-04 21:09:57 +00:00
Skylot 71bf2aa59f fix: don't apply const split if not needed 2020-11-04 20:21:24 +00:00
Skylot 714b935474 fix: improve checks for boolean to int conversion (#921) 2020-11-04 19:23:57 +00:00
Skylot 2a2b83a695 fix: do not copy input files as resources to output folder 2020-11-03 18:08:57 +00:00
Skylot acdaa95854 test(cli): add multiple input test (#936) 2020-11-03 17:27:15 +00:00
Skylot 278c5f6142 feat(gui): allow to load multiple files, button for add files (#936) 2020-11-02 18:40:55 +00:00
Skylot 8ca3cd3155 fix: don't use static vars of mutable LiteralArg class (#1005) 2020-11-01 15:56:29 +00:00
Skylot 2b7d7ce2cf fix: additional casts at use place to help type inference (#1002) 2020-10-31 16:00:57 +00:00
Skylot a22efc2eb6 fix: don't add cast for PHI insn (#1002) 2020-10-30 23:04:57 +03:00
bagipro 804c8eff91 fix(res): fixes deobfuscated resource text files saving (PR #1004)
Co-authored-by: sergey-wowwow <bugi@MacBook-Pro.local>
2020-10-30 20:28:46 +03:00
Skylot aec8ebe237 fix: reload request for correct class in method inline visitor (#999) 2020-10-29 21:41:35 +03:00
Alisson Lauffer 7353790ed1 fix(gui): only show renaming option for JClass, JField and JMethod (PR #1001) 2020-10-29 20:23:36 +03:00
Skylot e09e8e5823 chore: resolve some issues reported by sonar and lgtm 2020-10-28 14:16:50 +00:00
Skylot 92773417b3 perf(gui): reduce updates count for memory usage bar (#1000)
- disabled by default
- update only if label changed
- skip updates if app window becomes inactive
2020-10-27 12:15:04 +00:00
Skylot 12dc4fde8a fix(gui): clear jumps history on file close 2020-10-27 11:54:11 +03:00
Skylot d1e5186d4a perf(res): speed up rename of deobfuscated resources 2020-10-26 12:28:41 +00:00
Skylot d06ba95374 chore: remove unused static strings array 2020-10-26 11:15:25 +00:00
sergey-wowwow f0e6c8ea8e fix(res): put deobfuscated files to res/ folder (PR #995) 2020-10-25 20:28:06 +00:00
Skylot c94c204da2 chore: fix travis badge link 2020-10-25 22:37:46 +03:00
sergey-wowwow 71617a1c70 feat(res): fix duplicate entries and deobfuscate file names in XML resources (PR #995)
* Fixes dublicates entries in XML resources
* can't use binary search on this list
* add entry config to name comparator, preserve renames by id, improve performance
* Deobf resource files
* Add break
* Changes ResourceFile

Co-authored-by: sergey-wowwow <bugi@MacBook-Pro.local>
Co-authored-by: Skylot <skylot@gmail.com>
2020-10-25 22:18:41 +03:00
Alisson Lauffer 9f684937c6 feat(gui): rename classes and fields by popup menu in tree panel (PR #993) 2020-10-20 12:26:15 +03:00
Coin ff6665c716 fix(gui): use correct charset when writing mapping file (PR #992) 2020-10-17 18:51:08 +03:00
Skylot aa8fd3c861 fix: added another enum restore pattern (#926) 2020-10-02 18:38:08 +01:00
Skylot e2b42804d5 fix: resolve several issues with package rename and class reload (#987) 2020-10-02 16:34:38 +01:00
Skylot 0f6e942c5b build: remove check stage in gitlab build 2020-10-01 21:55:51 +01:00
Skylot c0a81978bf fix(gui): allow to rename packages (#987) 2020-10-01 21:39:23 +01:00
Skylot b76c882210 fix(gui): fix rename dialog pack (#972) 2020-09-28 19:27:48 +01:00
Skylot 14cbfbc5a4 fix: rerun signature parser on class reload (#981) 2020-09-28 16:19:52 +01:00
skylot 9b1761f71f fix: prevent zipbomb forged headers attacks (#980, PR #982) 2020-09-27 21:10:30 +03:00
Skylot 73ca2e0fa4 fix: move class unload to synchronized block (#977)
Signed-off-by: Skylot <skylot@gmail.com>
2020-09-27 18:23:23 +01:00
Skylot 4e4c7f7d7b fix: more visibility checks for @Override (#984)
Signed-off-by: Skylot <skylot@gmail.com>
2020-09-24 17:44:29 +01:00
Skylot 33f2c3f220 fix: transform loop to for with branching at end
Signed-off-by: Skylot <skylot@gmail.com>
2020-09-23 19:20:37 +01:00
Skylot dcca0133fb chore: update gradle and dependencies 2020-09-17 21:03:17 +03:00
Skylot 408201b69b fix: restore enum for java 15 2020-09-17 20:13:36 +03:00
Skylot e024628d46 chore: fix directory search for smali tests 2020-09-17 19:53:18 +03:00
Skylot 6428f29373 fix: don't add @Override for static methods (#976) 2020-09-17 16:47:44 +03:00
Skylot cfaa6ab6df fix: don't add @Override if super method is private (#976) 2020-09-17 16:39:55 +03:00
Skylot 91ee7565ac fix: resolved regression in Kotlin metadata parser 2020-09-14 19:09:54 +01:00
Skylot 1bbcac2ab3 fix: handle xor on boolean (#921) 2020-09-13 22:06:04 +01:00
Skylot 60b2353afe fix: adjust types for arithmetic instructions (#921) 2020-09-11 21:31:10 +03:00
Skylot 50cfa4c971 chore: improve error reporting in tests 2020-09-11 21:31:10 +03:00
Skylot 691bf8faca fix: checks for casts in field access, move method inline to visitor (#962)
Signed-off-by: Skylot <skylot@gmail.com>
2020-09-07 17:29:39 +01:00
Skylot 89b4ae6a6f fix: correct type and data merge for filled-array instruction 2020-08-23 15:31:46 +01:00
Skylot 605a67932f chore: remove unused array allocation 2020-08-22 15:33:43 +01:00
Jan S 1774dc74e3 feat(deobf): improve deobfuscated class names by including class properties and info about super class respectively implemented interfaces (PR #969) 2020-08-21 19:18:41 +03:00
Skylot 2d641bf049 fix: don't trust type info in signature, check before apply (#858) 2020-08-17 22:05:13 +01:00
Skylot 94a06d9b6f feat(gui): on mouse hover highlight identifiers with enabled actions (like 'find usage' or 'rename') 2020-08-17 17:38:48 +01:00
Skylot a485942731 docs: add jadx logo to readme 2020-08-16 13:24:09 +01:00
Skylot 2c1b3b2480 Merge branch 'jpstotz-copyprefs' into master 2020-08-16 15:14:10 +03:00
Skylot f1f7c70aee chore(gui): fix locales test 2020-08-16 13:13:50 +01:00
Jan Peter Stotz 718caf8cb1 chore (gui): Add button in preferences dialog to copy the preference values in text form (json) to clipboard 2020-08-16 13:05:49 +02:00
Skylot 545cd4ec12 fix: don't inline 'null' object to make code compilable (#964) 2020-08-10 20:42:11 +01:00
Skylot 444ea9ec7e fix: load .class files 2020-08-10 12:20:42 +01:00
Skylot 13609a5c44 fix: allow to inline variables around 'monitor-exit' in synchronized block 2020-08-09 15:19:54 +01:00
Skylot d6ad21f6f9 fix: correct detection of exits in synchronized block (#942) 2020-08-09 17:04:54 +03:00
Skylot 593a32a689 refactor: use same instance for empty type vars annotation 2020-08-09 17:04:54 +03:00
Skylot 7fed5534eb refactor: add method info caching to speed up initial loading 2020-08-07 19:22:01 +01:00
Skylot 1560284831 refactor: fix zip security in dex plugin, remove smali deps from jadx-core 2020-08-06 13:39:40 +01:00
Skylot 558a86739f fix: bring back smali files support (#961) 2020-08-05 19:58:43 +01:00
Skylot bfd60b733a fix: handle method arguments in primitive types conversion (#956) 2020-08-04 12:26:31 +01:00
Skylot ae26512601 fix: use internal usage info for rename, fix index refresh (#791) 2020-08-03 11:55:19 +01:00
Skylot 498c2f5256 Merge branch 'rename' into master 2020-08-02 13:25:15 +03:00
Skylot 459f12d61f fix: several improvements for generics and type inference
- support 'extends' for generic type variables
- insert cast instructions to help type inference (#956)
- correct move instructions insertion (to resolve types in PHI)

Signed-off-by: Skylot <skylot@gmail.com>
2020-07-31 18:47:32 +01:00
Skylot bcd6e537e0 fix: correct parsing for array-data-payload 2020-07-30 19:25:15 +01:00
Skylot 867c3413e9 test: replace const values 2020-07-20 13:56:02 +01:00
Skylot 0f808d5c60 fix: resolve char literal incorrect print as string (#856) 2020-07-20 10:33:10 +01:00
Skylot f5767dd865 fix: use recursive objects for nested inner generic classes (#869) 2020-07-16 14:14:06 +01:00
Skylot 631a855bac chore: resolve deprecations in tests 2020-07-13 09:26:55 +01:00
Skylot c616b5b03b build: resolve gradle deprecation warnings 2020-07-11 12:51:38 +01:00
Skylot 3e9f4a5060 fix: improve limit calculation for type updates in type inference (#854) 2020-07-10 18:36:17 +01:00
Skylot 31434186ab fix: improve boolean type handling in type inference 2020-07-08 12:34:21 +01:00
Skylot e81ba1c431 build: fix latest java version for gitlab build 2020-07-06 14:16:57 +01:00
Skylot b219ab607f fix: exclude directories from dex convertion results 2020-07-05 19:02:14 +03:00
Skylot cd8307f432 chore: remove unused d8 from convert plugin 2020-07-05 19:02:14 +03:00
Skylot a720105208 chore: update dependencies 2020-07-05 19:02:14 +03:00
Skylot 34692c41f2 Merge branch 'master' into rename
# Conflicts:
#	jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
#	jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java
2020-07-03 16:39:02 +01:00
Bet4 8a8b945eb8 fix(gui): run indexJob before rename (PR #910) 2020-07-03 17:26:24 +03:00
M.Yasoob Ullah Khalid ☺ 99569c52ac docs: fixed grammar slightly (PR #957) 2020-07-03 17:22:00 +03:00
Snowhite f696dc715b fix(gui): update Messages_zh_CN.properties (PR #959)
modifying error of positional argument in Chinese search result text
2020-07-03 17:19:18 +03:00
Jan S 21b8552386 fix (gui): launch4j exe startup wrapper does not apply maximum heap configuration correctly (#951) 2020-06-11 17:06:43 +03:00
Skylot 4b1886700d fix(gui): merge plugins service files (#949)
Signed-off-by: Skylot <skylot@gmail.com>
2020-06-11 14:26:23 +01:00
Skylot a83ca1f85b fix: don't use FileChannel on ZipFs to avoid creation of temp files (#950) 2020-06-10 22:39:56 +01:00
Skylot 65553c156c fix: restore android R class (#947) (regression) 2020-06-06 19:19:55 +01:00
Skylot 440357d2e8 fix: allow cross-block move inline (#946)
Signed-off-by: Skylot <skylot@gmail.com>
2020-06-01 22:24:42 +01:00
Skylot 5e62b9077a fix: resolve ClassCastException on encoded value access (#946) 2020-06-01 23:39:18 +03:00
Skylot 6192ced214 fix: improve type inference of type variables in method invoke (#913) 2020-06-01 23:39:12 +03:00
Skylot ae31fee8dd fix: add cast to exact type on field access (#729) 2020-05-29 17:53:39 +01:00
Skylot e7b00cc76e fix: add methods and fields types into usage info (#729) 2020-05-27 20:55:32 +01:00
Skylot 7d29c5d766 fix: correct skip size for sparse switch payload 2020-05-27 20:54:28 +01:00
Skylot 15776c4ce3 fix: make class public if some method going to be inlined (#729) 2020-05-26 21:23:27 +01:00
Skylot d720179deb fix: collect class usage and fix class access modifiers (#729) 2020-05-22 12:29:34 +01:00
Skylot 0d69e0ac97 refactor: use own dex parser instead deprecated dx lib 2020-05-21 22:02:20 +01:00
Skylot 09e267f8bc fix: resolve generic types in method arguments (#913) 2020-05-17 14:58:34 +01:00
Soul Trace 705ceca42a fix(gui): RenameDialog: Warn user if deobfuscation settings are invalid (PR #935)
The rename functionality relies on deobfuscation for now - so let the user know
this and ask the user to enable deobfuscation to get rename working.

The "Force rewrite deobfuscation map file" option effectively disables
renaming, because renaming relies on deobfuscation map modification for now,
but Force rewrite rewrites the map on each file reload, destroying changes.
So. let the user know this issue instead of silent failure.
2020-05-14 22:56:35 +03:00
Skylot 58722d372e fix: don't generate string concatenation without assign to variable 2020-05-14 12:25:42 +03:00
Skylot f482b8b95c fix: restart comment to escape strings in insn fallback dump 2020-05-14 12:25:42 +03:00
Jan S 21e4dee0e2 chore: exclude project build directories from code style checking (PR #934) 2020-05-13 20:46:58 +03:00
Jan S c7a12ad75b fix(res): resource XML generation for parent attribute added (PR #933, #931) 2020-05-13 20:25:45 +03:00
Skylot 7cd77ae379 fix: try raw types to help type inference (#913) 2020-05-12 19:23:49 +01:00
Skylot d59c99ddfe fix: attach method details before OverrideMethodVisitor 2020-05-12 19:23:27 +01:00
Skylot 85760cc844 fix: replace type variables in arrays (#913) 2020-05-12 19:16:52 +01:00
Skylot 0692464b85 fix: mark override methods and fix return type (#913) 2020-05-11 21:35:00 +01:00
Skylot 3968222744 style: fix incorrect import 2020-05-10 15:46:24 +01:00
Skylot c05368d92e style: prefer use Stream.of method 2020-05-10 15:46:06 +01:00
Skylot 404136cd72 fix: improve type inference for generics in invoke insn (#927) 2020-05-10 14:20:27 +01:00
Skylot b1d5ed0066 fix: don't modify method argument types in MethodInvokeVisitor (#927 #836) 2020-05-09 21:10:37 +01:00
Skylot e22474e0a7 fix: inline move instructions to help process constructors (#927) 2020-05-09 15:38:18 +01:00
Skylot 45b7304630 fix: workaround for link clicks in RSyntaxTextArea (#929) 2020-05-08 20:01:20 +01:00
Skylot 692536c584 chore: ignore tests in tmp package 2020-05-04 15:47:30 +01:00
Skylot 4e3d85887c fix: correctly process extended enums if class is not inner (#924) 2020-05-04 15:19:17 +01:00
Skylot 87852328da fix: resolve error in SkipMethodArgsAttr if method not yet loaded (#924) 2020-05-04 11:50:31 +01:00
Skylot 2207cd7b52 fix: inline class constants to fix enum transform (#916) 2020-05-03 22:32:32 +03:00
Skylot f3cd4e38d7 fix: check enum constructor content before removing (#922) 2020-05-03 22:32:32 +03:00
Skylot 2dce1c0ad9 build: update gradle and dependencies 2020-05-03 22:32:27 +03:00
Jan S 258ecad277 fix(res): XML parsing: handling of TYPE_DYNAMIC_REFERENCE entries (#919, PR #923) 2020-04-29 21:47:20 +03:00
Skylot 7f5092c0d5 fix: redone shadowed fields handling (#897) 2020-04-27 22:29:34 +01:00
Skylot a7f315f596 fix: split CONST used in PHI to help type inference (#900) 2020-04-26 20:37:34 +01:00
Skylot 4dc4aa122b fix: don't duplicate result arg with instruction copy (breaks SSA variable assign) 2020-04-26 18:44:43 +01:00
Skylot e3f388af11 fix(deobf): resolve NPE when package is empty (if rename is disabled) 2020-04-26 20:40:20 +03:00
Skylot 83196628c9 docs: fix issue and PR templates 2020-04-26 20:40:20 +03:00
skylot 315c07d4f6 feat(res): rename resources keys if contains unprintable chars or duplicates (#844) (PR #909) 2020-04-21 22:33:35 +03:00
Skylot 47dadf0a43 fix: use correct class for Throwable, insert catch arg name if not defined (#896) 2020-04-18 21:28:29 +03:00
yunlong17568 c62039f327 fix(gui): use env %JAVA_HOME% as the JRE path (PR #895)
Co-authored-by: yunlong.yang <yunlong.yang@inveno.com>
2020-04-11 20:41:27 +03:00
Skylot a5ea560edc fix: preserve code semantics on array-for-each transform (#893) 2020-03-31 21:41:36 +01:00
Skylot e09e933f9c fix: additional checks for class signature 2020-03-22 12:12:47 +00:00
Skylot dbd00d5a8b refactor: use instance methods for error and warning notifications 2020-03-22 12:01:42 +00:00
Skylot 2da772df8e fix: resolve some cases of switch in loop (#876) 2020-03-21 18:42:27 +00:00
Skylot 4cdad0e83e fix: correct method exit blocks collection (#876) 2020-03-17 19:38:45 +00:00
Skylot 2f780da305 fix: remove enum methods after instructions check (#884) 2020-03-16 20:31:47 +00:00
Skylot 9d8066f4b8 fix: don't remove synthetic methods from enum (#884) 2020-03-16 19:06:52 +00:00
Jan S 2cc49256a9 chore(gui): optimized the text search for classes, methods and fields (PR #887) 2020-03-15 18:55:32 +03:00
Jan S 79ab2e11f8 chore(gui): preferences dialog changed to two-column mode (PR #888) 2020-03-15 18:21:08 +03:00
Jan S c1f4302e62 feat(gui): allow to search for full method/field signature, not only the name (PR #880) 2020-03-10 12:11:44 +03:00
Skylot f66ec9168c test: update test TestAnonymousInline 2020-03-09 22:44:30 +03:00
Skylot 37aecf72cb Merge branch 'master' into rename 2020-03-09 19:43:40 +00:00
Bendegúz Ács 3c7be5e9be fix: use super instead this when super member is shadowed (PR #878)
* Added failing test for super member shadowing.
* Fixed new test containing incorrect variable names.
* Implemented marking super fields used in a subclass with super keyword.
* Renamed member variable in the example to reflect smali and test.
* Fixed formatting and imports.
2020-03-07 19:52:21 +03:00
Skylot 89dbae8f8e fix: resolve NPE while compare outer generic types 2020-02-29 19:55:16 +00:00
Skylot 5eec8f754d fix: class resolving issues (#867) 2020-02-29 19:24:01 +00:00
Skylot 49a82c8388 fix: method info cache error (#868) 2020-02-29 19:22:18 +00:00
Skylot 26bad4a1cd fix: replace constants for arrays in annotations (#831) 2020-02-29 18:55:30 +00:00
Skylot fa0a38d3aa fix: don't use OS specific new line chars (#861) 2020-02-23 15:37:07 +00:00
Jan S b56fd4d29a chore: add inlined class name as comment (PR #865)
* chore: add inlined class name as comment
* chore: adapt unit test for inlined class name as comment
2020-02-23 17:06:10 +03:00
Jan S 4520747167 fix: concurrent access may cause an java.lang.ClassCastException in JNodeCache (PR #864) 2020-02-23 16:22:49 +03:00
Skylot e444ecb2c7 fix: improve wildcard types compare (#857) 2020-02-17 19:31:13 +00:00
Skylot 1336c47d18 fix: speed up switch processing by skip not relevant exits (#846) 2020-02-16 15:50:02 +00:00
Skylot 519a74e8d2 fix: improve type inference for arrays (#837) 2020-02-16 17:12:31 +03:00
Skylot dea7714ef3 feat: add methods information from standard library, improve generics and varargs restore (#836) 2020-02-16 17:12:31 +03:00
Skylot 74b88b407e chore: add config for lgtm.com analysis 2020-02-14 19:29:24 +00:00
Skylot 57c28c61e0 fix: restore enum for several blocks in class init method 2020-02-14 18:08:37 +00:00
Skylot 87320348dd chore: update all dependencies and gradle 2020-02-12 20:43:27 +00:00
Skylot fcb70e69c1 fix: don't print commented case value if it is an instruction (#850) 2020-02-11 22:08:14 +03:00
Jan S 4859629850 fix: record inlined classes and generate Smali code for them (PR #851, #848)
* fix: additionally show smali code of all inlined classes (recursively)
* variable name corrected
2020-02-11 22:06:07 +03:00
Jan S bd0d248fd0 fix: additionally show smali code of all inner classes (recursively) (PR #849, #848) 2020-02-10 22:00:32 +03:00
Skylot c24a3edb44 fix: inline assignment instruction from same block (#820) 2020-02-09 14:40:27 +00:00
Skylot d0f197ea3d fix: shrink code if region maker add FORCE_ASSIGN_INLINE to insn (#845) 2020-02-09 14:36:59 +00:00
Skylot 5502d93cd5 fix: additional checks before insert move to help type inference (#843) 2020-02-04 18:45:27 +00:00
Skylot 073fd76aa2 chore: update actions/checkout in gradle wrapper validation (#832) 2020-02-04 17:19:16 +00:00
Yaroslav Yadrov 492a3f6928 feat(deobf): add classname parsing for Kotlin metadata (PR #842, #758) 2020-02-02 17:08:29 +03:00
Skylot 1ce8fa8bdd fix: don't apply types if search failed (#840) 2020-01-28 20:07:58 +00:00
Skylot 1bb90233b9 test: NYI tests for #836 and #837 2020-01-27 19:23:35 +00:00
Skylot 49ce92f540 fix: remove move instructions with unused result (#835) 2020-01-24 17:37:53 +00:00
Skylot 2107da2e1a fix: improve 'out' block detection in switch (#826) 2020-01-23 18:58:17 +00:00
S-trace d98321026d gui: RenameDialog: Unload classes in refreshTabs() before refreshing
This should fix possible problems with incorrect refresh for open classes.
2020-01-20 10:13:20 +03:00
S-trace 0b6fabbc71 gui: Perform classes unload in the background UnloadJob
This should improve interface responsibility if there are many classes
to refresh after rename.
2020-01-20 10:13:20 +03:00
Skylot bb0fad2834 fix: resolve multi-threaded unloading 2020-01-20 10:13:20 +03:00
Skylot 08f9722e33 Merge branch 'master' into rename 2020-01-20 10:12:49 +03:00
Skylot 62ca30bbc6 fix: additional patterns to restore enum classes (#830) 2020-01-19 11:12:23 +00:00
S-trace 467403362d core: ConstStorage: Use ConcurrentHashMap for values map in ValueStorage
Exception in thread "pool-9-thread-7" java.util.ConcurrentModificationException
        at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
        at java.util.HashMap$EntryIterator.next(HashMap.java:1479)
        at java.util.HashMap$EntryIterator.next(HashMap.java:1477)
        at jadx.core.dex.info.ConstStorage$ValueStorage.removeForCls(ConstStorage.java:61)
        at jadx.core.dex.info.ConstStorage.removeForClass(ConstStorage.java:100)
        at jadx.core.dex.nodes.ClassNode.deepUnload(ClassNode.java:290)
        at jadx.core.dex.nodes.ClassNode.deepUnload(ClassNode.java:295)
        at jadx.core.dex.nodes.ClassNode.reloadCode(ClassNode.java:284)
        at jadx.api.JavaClass.refresh(JavaClass.java:62)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
2020-01-17 10:38:15 +03:00
S-trace 265a78cd23 core: ConstStorage: Do not remove values from duplicates set in removeForCls()
If the constant already got duplicates - it will have duplicates even after
class reload, won't it?
But removing this constant from duplicates may break constants replacing
(just imagine a class TestClass with public static final int TEST_CONSTANT = 1;
 - after reloading TestClass each "(int) 1" litheral will be replaced to
"TEST_CONSTANT" reference in each reloaded class and trivial increments will
become werid expressions (int y = x + 1; will become
int y = x + TestClass.TEST_CONSTANT;)).
2020-01-17 10:38:15 +03:00
S-trace a0e13d0481 gui: RenameDialog: Rename tmp deobf map file too
Fixes /tmp/deobf_tmp_*.txt temporary files accumulation on renames.
2020-01-17 10:38:15 +03:00
S-trace 77fc6435a0 core: ConstStorage: Don't put known duplicate value to ValueStorage.values map
This is a microoptimization, which remove unnecessary values.put() and
values.remove() pair of operations if ValueStorage.put() is called for a
known duplicated value.
2020-01-17 10:38:15 +03:00
Skylot cd7e5bf020 Merge branch 'master' into rename 2020-01-17 10:37:43 +03:00
Skylot 5e7388f686 refactor: fix several issues reported by sonar 2020-01-16 12:16:32 +03:00
Skylot 1047e751e6 chore: fix github action for gradle wrapper validation 2020-01-16 10:47:31 +03:00
Jonathan Leitschuh c598871764 chore: official Gradle Wrapper Validation GitHub Action (PR #832)
See: https://github.com/gradle/wrapper-validation-action
2020-01-16 10:19:39 +03:00
Skylot 2921c66834 fix: replace constants inside annotations (#831) 2020-01-14 19:52:03 +00:00
Skylot 7bbb083c36 refactor: small changes to switch region and region debug print 2020-01-13 19:27:52 +00:00
Skylot 531650c9f2 refactor: allow to change temp dir using system property JADX_TMP_DIR 2020-01-13 15:32:32 +03:00
Skylot f3098741c3 test: switch with fallthrough cases (#826) 2020-01-08 14:26:40 +00:00
Skylot 9dbffef140 fix: deep reload for inner classes, const values and anonymous classes 2020-01-05 22:12:13 +03:00
Jan S c97e504686 fix: additionally show smali code of inner classes (PR #824) 2020-01-05 12:46:07 +03:00
Skylot 0c4b807caa fix: improve ClassNode reloading and revert some changes 2020-01-03 20:46:47 +03:00
S-trace 1eca2b6cb0 core: ClassInfo: Do not ignore setting alias to original class name
Fixes trouble with renaming class back to its original name.
2020-01-03 06:09:26 +03:00
S-trace 17cbb3eab0 core: Fix possible NPE in DebugInfoParser.addrChange()
This may happen because MethodNode.unloadInsnArr() call from BlockSplitter.visit() - after it instructions[] become null.
So, try to reload method before processing its instructions array and bail if insnArr still null even after reloading method.
2020-01-03 06:09:25 +03:00
S-trace c72f2a2c96 core: RenameReasonAttr: Do not append new reason if it is already there
Fixes possible "reason: invalid class name and invalid class name" comments after class refresh.
2020-01-03 06:09:23 +03:00
S-trace 610f531653 core: EnumVisitor: Do not remove ACC_ENUM access flag
This flag is necessary for class refresh, and should be left there.
Fixes disappearing of enum fields after class refresh.
2020-01-03 06:09:22 +03:00
S-trace 1e9b28b369 core: AType: Add FIELD_INIT and SOURCE_FILE to SKIP_ON_UNLOAD set
Fixes disappearing litheral values for replaced constants in switches and source file names..
2020-01-03 06:09:21 +03:00
S-trace 6d4caca6cc core: ClassModifier: Don't skip methods with SKIP_FIRST_ARG attr
Skipping those methods on class refresh leads to "M.this = r1;" like
assigments appears in the inner class constructors.

Unsure is this saint or not.
2020-01-03 06:09:20 +03:00
S-trace 15953f832f core: Do not call addConstField() on class refresh - fix constants replacing
There is a duplicate control in the ConstStorage.ValueStorage.add() method,
so each constant should be added only once, and not be added on class refresh.

Fixes "Replace constants" failure after renaming any node.
2020-01-03 06:09:19 +03:00
S-trace d346ed0570 core: MethodNode: Fix possible decompilation failure on refresh inner class
checkInstructions() may fail with NPE:
ERROR - NullPointerException in pass: BlockSplitter in method: com.google.common.primitives.Ints.IntArrayAsList.<init>(int[], int, int):void, dex: out.dex
java.lang.NullPointerException: null
        at jadx.core.dex.nodes.MethodNode.checkInstructions(MethodNode.java:159)
        at jadx.core.dex.visitors.blocksmaker.BlockSplitter.visit(BlockSplitter.java:49)
        at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:31)
        at jadx.core.dex.visitors.DepthTraversal.lambda$visit$1(DepthTraversal.java:16)
        at java.util.ArrayList.forEach(ArrayList.java:1257)
        at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:16)
        at jadx.core.dex.visitors.DepthTraversal.lambda$visit$0(DepthTraversal.java:15)
        at java.util.ArrayList.forEach(ArrayList.java:1257)
        at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:15)
        at jadx.core.ProcessClass.process(ProcessClass.java:41)
        at jadx.core.ProcessClass.generateCode(ProcessClass.java:58)
        at jadx.core.dex.nodes.ClassNode.decompile(ClassNode.java:292)
        at jadx.core.dex.nodes.ClassNode.decompile(ClassNode.java:271)
        at jadx.core.dex.nodes.ClassNode.refresh(ClassNode.java:303)
        at jadx.api.JavaClass.refresh(JavaClass.java:61)
        at jadx.gui.treemodel.JClass.refresh(JClass.java:63)
...

This happens because MethodNode.unloadInsnArr() call from BlockSplitter.visit() - after it instructions[] become null.
So, try to reload method before processing its instructions array.
2020-01-03 06:09:17 +03:00
S-trace df520a1134 core: ClassNode: Check is field really static or not in loadStaticValues()
Fixes appearing of the 0/null/false initializers for instance final fields.
2020-01-03 06:09:16 +03:00
S-trace f90fc1d5ec core: ClassNode: Load recursively missing information on refresh()
Fixes loss of static identifiers, comments, annotations and source file name after rename.
2020-01-03 06:09:14 +03:00
S-trace 797904afb5 gui: Perform refresh of non-displayed classes in background thread
After renaming some classes needs to be redecompiled to reflect new state.
Move recompilation of non-displayed classes to background thread.
This should improve performance on weak machines.
2020-01-03 06:09:13 +03:00
S-trace 489fbb5e42 gui: Improve performance of renaming
Fixes multiple decompilation of classes - now each class decompiled just once.
2020-01-03 06:09:12 +03:00
S-trace 9dd5a9ef89 gui: More advanced implementation of renaming
This implementation does not reload file after renaming, and so works faster.
2020-01-03 06:09:08 +03:00
Skylot 02213802c5 fix: make correct replacement for synthetic constructor (#808) 2019-12-28 08:42:39 +00:00
Skylot 8365855475 fix(gui): search only in short names of methods and fields (#818) 2019-12-27 18:55:11 +00:00
Skylot 55eb86d2d5 fix(gui): search class with --select-class also by not deobfuscated names 2019-12-27 18:52:28 +00:00
Skylot 0a9b944431 refactor(gui): add on finish runnable to open action 2019-12-27 18:42:07 +00:00
Skylot f1e229193c fix: resolve exception on assign inline 2019-12-27 21:20:14 +03:00
Skylot 04e309aeff fix: additional checks for 'if' blocks inside loops (#809) 2019-12-27 21:20:14 +03:00
Skylot 99eb31b312 fix(gui): add dots for progress titles 2019-12-27 21:19:37 +03:00
Soul Trace 287275d886 fix(gui): resolve --select-class option regression (PR #816) 2019-12-26 20:29:47 +03:00
Skylot af6f8b5391 fix(gui): resolve exceptions during index (#812) 2019-12-23 22:39:06 +03:00
Hen Ry 3b9b103c3f fix(gui): update Deutsch translation (PR #811) 2019-12-23 20:48:17 +03:00
Skylot 0c55ab9001 fix: resolve class cast exception introduced in CodeWriter refactoring (#810) 2019-12-22 16:19:47 +00:00
Skylot 9c88f70740 fix(gui): load file in background thread and show progress indicator 2019-12-22 15:56:50 +00:00
Skylot 9ab003df4c feat(gui): map back and forward mouse keys for navigation (#807) 2019-12-21 16:15:05 +00:00
Skylot 7f8d03d192 style: fix some sonar and compiler warnings 2019-12-21 15:37:25 +03:00
Skylot c64ffde11f refactor: use ICodeInfo interface instead CodeWriter 2019-12-21 12:36:54 +00:00
Hen Ry 1568008c67 fix(gui): improve Deutsch translation (PR #806) 2019-12-20 21:34:32 +03:00
Skylot 84211576e4 fix(gui): add Deutsch to locales list (#804) 2019-12-19 17:20:50 +00:00
Hen Ry 553f5b063f feat(gui): add Messages_de_DE.properties (PR #804) 2019-12-19 20:14:46 +03:00
Skylot f5d1f288d0 fix: don't inline constants in synchronized statement (#799) 2019-12-15 12:04:24 +00:00
Skylot a2df92dd68 fix(gui): correct app close on menu exit action 2019-12-14 15:14:20 +00:00
Skylot 1c6e51f8b2 fix: allow to regenerate class code (#791) 2019-12-13 18:37:10 +00:00
Soul Trace ef5da49bc0 fix(xml): reset nsMap in parse method (PR #798 #796)
Fixes injection of xmlns: attributes from other files (#796)
2019-12-13 21:16:08 +03:00
Skylot 7545625af4 test: add NYI test for empty finally block (#789) 2019-12-10 22:10:27 +03:00
Soul Trace e3055b95f6 feat(gui): support for renaming methods, classes and fields (PR #794 #791)
* Add getRealFullName() to ClassNode and JavaClass and searchJavaClassByRealName() to JadxWrapper

Those methods is like getFullName() and searchJavaClassByClassName(), but for class names without aliases.
It is necessary for renaming classes/methods/fields.

* core: Make getFieldNode(), getMethodNode() and getRoot() public

This is necessary for renaming functionality

* jadx-gui: Add Rename popup menu entry (renames classes, methods and fields)

It allows user to rename classes, methods and fields.
It updates deobfuscation map and reload file.
This may be suboptimal, and maybe some RenameVisitor should be added.
Deobfuscation should be enabled in order to allow this.
2019-12-10 22:08:27 +03:00
Soul Trace 78eed8629c feat(gui): reopened tabs on file reload (PR #793 #792)
* Add getRealFullName() to ClassNode and JavaClass and searchJavaClassByRealName() to JadxWrapper

Those methods is like getFullName() and searchJavaClassByClassName(), but for class names without aliases.
It is necessary for renaming classes/methods/fields.

* MainWindow: Try to restore open tabs on deobfuscation toggle

Restore open tabs if possible when user toggles deobfuscation mode.
Try to scroll to the position before toggling deobfuscation mode (may be not exact cause of the comments).
2019-12-10 21:41:57 +03:00
1231 changed files with 92695 additions and 16035 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,15 +8,12 @@ assignees: ''
--- ---
**Checks before report** **Checks before report**
- check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki - check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
- search existing issues by exception message - search existing issues by exception message
**Describe error** **Describe error**
- provide full name of method or class with error - full name of method or class with error
- provide full java stacktrace - full java stacktrace (no need to copy method fallback code (commented pseudocode))
- **IMPORTANT!** attach or provide link to apk file (double check apk version)
**Note**: no need to copy method fallback code (commented pseudocode)
- attach or provide link to apk file (double check apk version)
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :) **Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
+2 -2
View File
@@ -1,5 +1,5 @@
:exclamation: Please review the [guidelines for contributing](../CONTRIBUTING.md#Pull-Request-Process) :exclamation: Please review the [guidelines for contributing](https://github.com/skylot/jadx/blob/master/CONTRIBUTING.md#Pull-Request-Process)
### Description ### Description
Please describe your pull request. Please describe your pull request.
Reference issue it fix. Reference issue it fixes.
+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
@@ -0,0 +1,10 @@
name: "Validate Gradle Wrapper"
on: [push]
jobs:
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1
+6 -1
View File
@@ -5,6 +5,7 @@
# IntelliJ Idea files # IntelliJ Idea files
.idea/ .idea/
.run/
out/ out/
*.iml *.iml
*.ipr *.ipr
@@ -24,10 +25,14 @@ node_modules/
jadx-output/ jadx-output/
*-tmp/ *-tmp/
**/tmp/
*.jobf
*.dex
*.class *.class
*.dump *.dump
*.log *.log
*.cfg *.cfg
*.orig *.orig
quark.json
cliff.toml
+5 -18
View File
@@ -7,31 +7,18 @@ before_script:
stages: stages:
- test - test
- check
java-8: java-8:
stage: test stage: test
image: openjdk:8 image: openjdk:8
script: ./gradlew clean build script: ./gradlew clean build dist copyExe --warning-mode=all
java-11: java-11:
stage: test stage: test
image: openjdk:11 image: openjdk:11
script: ./gradlew clean build script: ./gradlew clean build dist copyExe --warning-mode=all
java-12: java-latest:
stage: test stage: test
image: gradle:jdk12 # latest gradle and jdk12 image: openjdk:latest
script: gradle clean build script: java -version && ./gradlew clean build dist --warning-mode=all
check:
stage: check
image: openjdk:8
script:
- export JADX_LAST_TAG="$(git describe --abbrev=0 --tags)"
- export JADX_VERSION="${JADX_LAST_TAG:1}-dev-$(git rev-parse --short HEAD)"
- ./gradlew clean sonarqube -Dsonar.host.url="${SONAR_HOST}" -Dsonar.projectKey=jadx -Dsonar.organization="${SONAR_ORG}" -Dsonar.login="${SONAR_TOKEN}" -Dsonar.branch.name=dev || echo "Skip sonar build and upload"
- ./gradlew clean dist
artifacts:
paths:
- build/jadx*.zip
-35
View File
@@ -1,35 +0,0 @@
branch: 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
-41
View File
@@ -1,41 +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: 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
+5 -7
View File
@@ -5,16 +5,14 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
## Open Issue ## Open Issue
1. Before proceed please do: 1. Before proceed please do:
- check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki - check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
- search existing issues by exception message - search existing issues by exception message
2. Describe error 2. Describe error
- provide full name of method or class with error **Describe error**
- provide full java stacktrace - full name of method or class with error
- full java stacktrace (no need to copy method fallback code (commented pseudocode))
**Note**: no need to copy method fallback code (commented pseudocode) - **IMPORTANT!:** attach or provide link to apk file (double check apk version)
- attach or provide link to apk file (double check apk version)
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :) **Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
@@ -23,7 +21,7 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
1. Please don't submit any code style fixes, dependencies updates or other changes which are not fixing any issues. 1. Please don't submit any code style fixes, dependencies updates or other changes which are not fixing any issues.
1. Before open a PR please discuss the change you wish to make via issue. PR without corresponding issue will be rejected. 1. Before start working on PR please discuss the change you wish to make via issue. PR without corresponding issue will be rejected.
1. Use only features and API from Java 8 or below. 1. Use only features and API from Java 8 or below.
+47 -28
View File
@@ -1,18 +1,21 @@
<img src="https://raw.githubusercontent.com/skylot/jadx/master/jadx-gui/src/main/resources/logos/jadx-logo.png" width="64" align="left" />
## JADX ## JADX
[![Build Status](https://travis-ci.org/skylot/jadx.png?branch=master)](https://travis-ci.org/skylot/jadx) [![Build status](https://github.com/skylot/jadx/workflows/Build/badge.svg)](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
[![Code Coverage](https://codecov.io/gh/skylot/jadx/branch/master/graph/badge.svg)](https://codecov.io/gh/skylot/jadx)
[![Alerts from lgtm.com](https://img.shields.io/lgtm/alerts/g/skylot/jadx.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/skylot/jadx/alerts/) [![Alerts from lgtm.com](https://img.shields.io/lgtm/alerts/g/skylot/jadx.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/skylot/jadx/alerts/)
[![SonarQube Bugs](https://sonarcloud.io/api/project_badges/measure?project=jadx&metric=bugs)](https://sonarcloud.io/dashboard?id=jadx)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
[![Maven Central](https://img.shields.io/maven-central/v/io.github.skylot/jadx-core)](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
**jadx** - Dex to Java decompiler **jadx** - Dex to Java decompiler
Command line and GUI tools for produce Java source code from Android Dex and Apk files Command line and GUI tools for producing Java source code from Android Dex and Apk files
:exclamation::exclamation::exclamation: Please note that in most cases **jadx** can't decompile all 100% of the code, so errors will occur. Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds
**Main features:** **Main features:**
- decompile Dalvik bytecode to java classes from APK, dex, aar and zip files - decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
- decode `AndroidManifest.xml` and other resources from `resources.arsc` - decode `AndroidManifest.xml` and other resources from `resources.arsc`
- deobfuscator included - deobfuscator included
@@ -21,25 +24,25 @@ Command line and GUI tools for produce Java source code from Android Dex and Apk
- jump to declaration - jump to declaration
- find usage - find usage
- full text search - full text search
- smali debugger, check [wiki page](https://github.com/skylot/jadx/wiki/Smali-debugger) for setup and usage
Jadx-gui key bindings can be found [here](https://github.com/skylot/jadx/wiki/JADX-GUI-Key-bindings)
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview) See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
![jadx-gui screenshot](https://i.imgur.com/h917IBZ.png)
### Download ### Download
- latest [unstable build: ![Download](https://api.bintray.com/packages/skylot/jadx/unstable/images/download.svg) ](https://bintray.com/skylot/jadx/unstable/_latestVersion#files)
- release from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest) - release from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
- release from [bintray: ![Download](https://api.bintray.com/packages/skylot/jadx/releases/images/download.svg) ](https://bintray.com/skylot/jadx/releases/_latestVersion#files) - latest [unstable build](https://nightly.link/skylot/jadx/workflows/build/master)
After download unpack zip file go to `bin` directory and run: After download unpack zip file go to `bin` directory and run:
- `jadx` - command line version - `jadx` - command line version
- `jadx-gui` - UI version - `jadx-gui` - UI version
On Windows run `.bat` files with double-click\ On Windows run `.bat` files with double-click\
**Note:** ensure you have installed Java 8 or later 64-bit version. **Note:** ensure you have installed Java 11 or later 64-bit version.
For windows you can download it from [adoptopenjdk.net](https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot#x64_win) (select "Install JRE"). For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
### Install ### Install
1. Arch linux 1. Arch linux
@@ -51,6 +54,9 @@ For windows you can download it from [adoptopenjdk.net](https://adoptopenjdk.net
brew install jadx brew install jadx
``` ```
### Use jadx as a library
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
### Build from source ### Build from source
JDK 8 or higher must be installed: JDK 8 or higher must be installed:
``` ```
@@ -66,44 +72,62 @@ and also packed to `build/jadx-<version>.zip`
### Usage ### Usage
``` ```
jadx[-gui] [options] <input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc) jadx[-gui] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab)
options: options:
-d, --output-dir - output directory -d, --output-dir - output directory
-ds, --output-dir-src - output directory for sources -ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources -dr, --output-dir-res - output directory for resources
-r, --no-res - do not decode resources -r, --no-res - do not decode resources
-s, --no-src - do not decompile source code -s, --no-src - do not decompile source code
--single-class - decompile a single class --single-class - decompile a single class, full name, raw or alias
--single-class-output - file or dir for write if decompile a single class
--output-format - can be 'java' or 'json', default: java --output-format - can be 'java' or 'json', default: java
-e, --export-gradle - save as android gradle project -e, --export-gradle - save as android gradle project
-j, --threads-count - processing threads count, default: 4 -j, --threads-count - processing threads count, default: 4
--show-bad-code - show inconsistent code (incorrectly decompiled) --show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name --no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info --no-debug-info - disable debug info
--add-debug-lines - add comments with debug line numbers if available
--no-inline-anonymous - disable anonymous classes inline --no-inline-anonymous - disable anonymous classes inline
--no-inline-methods - disable methods inline
--no-replace-consts - don't replace constant value with matching constant field --no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u) --escape-unicode - escape non latin characters in strings (with \u)
--respect-bytecode-access-modifiers - don't change original access modifiers --respect-bytecode-access-modifiers - don't change original access modifiers
--deobf - activate deobfuscation --deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter, default: 3 --deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64 --deobf-max - max length of name, renamed if longer, default: 64
--deobf-rewrite-cfg - force to save deobfuscation map --deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension
--deobf-cfg-file-mode - set mode for handle deobfuscation map file:
'read' - read if found, don't save (default)
'read-or-save' - read if found, save otherwise (don't overwrite)
'overwrite' - don't read, always save
'ignore' - don't read and don't save
--deobf-rewrite-cfg - set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)
--deobf-use-sourcename - use source file name as class name alias --deobf-use-sourcename - use source file name as class name alias
--rename-flags - what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all' (default) --deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
--rename-flags - fix options (comma-separated list of):
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
'valid' - rename java identifiers to make them valid,
'printable' - remove non-printable chars from identifiers,
or single 'none' - to disable all renames
or single 'all' - to enable all (default)
--fs-case-sensitive - treat filesystem as case sensitive, false by default --fs-case-sensitive - treat filesystem as case sensitive, false by default
--cfg - save methods control flow graph to dot file --cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions) --raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc) -f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
--use-dx - use dx/d8 to convert java bytecode
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG) -v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET) -q, --quiet - turn off output (set --log-level to QUIET)
--log-level - set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG, default: PROGRESS
--version - print jadx version --version - print jadx version
-h, --help - print this help -h, --help - print this help
Example: Examples:
jadx -d out classes.dex jadx -d out classes.dex
jadx --rename-flags "none" classes.dex jadx --rename-flags "none" classes.dex
jadx --rename-flags "valid,printable" classes.dex jadx --rename-flags "valid, printable" classes.dex
jadx --log-level error app.apk jadx --log-level ERROR app.apk
``` ```
These options also worked on jadx-gui running from command line and override options from preferences dialog These options also worked on jadx-gui running from command line and override options from preferences dialog
@@ -116,10 +140,5 @@ To support this project you can:
- Submit decompilation issues, please read before proceed: [Open issue](CONTRIBUTING.md#Open-Issue) - Submit decompilation issues, please read before proceed: [Open issue](CONTRIBUTING.md#Open-Issue)
- Open pull request, please follow these rules: [Pull Request Process](CONTRIBUTING.md#Pull-Request-Process) - Open pull request, please follow these rules: [Pull Request Process](CONTRIBUTING.md#Pull-Request-Process)
### Related projects:
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
--------------------------------------- ---------------------------------------
*Licensed under the Apache 2.0 License* *Licensed under the Apache 2.0 License*
*Copyright 2019 by Skylot*
+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.
+70 -69
View File
@@ -1,7 +1,6 @@
plugins { plugins {
id 'org.sonarqube' version '2.8' id 'com.github.ben-manes.versions' version '0.42.0'
id 'com.github.ben-manes.versions' version '0.27.0' id 'com.diffplug.spotless' version '6.2.2'
id "com.diffplug.gradle.spotless" version "3.26.0"
} }
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev" ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -10,40 +9,37 @@ println("jadx version: ${jadxVersion}")
allprojects { allprojects {
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'jacoco'
apply plugin: 'checkstyle' apply plugin: 'checkstyle'
version = jadxVersion version = jadxVersion
tasks.withType(JavaCompile) { sourceCompatibility = JavaVersion.VERSION_1_8
if (!"$it".contains(':jadx-samples:')) { targetCompatibility = JavaVersion.VERSION_1_8
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
}
compileJava { compileJava {
options.encoding = "UTF-8" options.encoding = "UTF-8"
} }
jar { jar {
version = jadxVersion
manifest { manifest {
mainAttributes('jadx-version': jadxVersion) mainAttributes('jadx-version': jadxVersion)
} }
} }
dependencies { dependencies {
compile 'org.slf4j:slf4j-api:1.7.29' implementation 'org.slf4j:slf4j-api:1.7.36'
compileOnly 'org.jetbrains:annotations:23.0.0'
testCompile 'ch.qos.logback:logback-classic:1.2.3' testImplementation 'ch.qos.logback:logback-classic:1.2.10'
testCompile 'org.hamcrest:hamcrest-library:2.2' testImplementation 'org.hamcrest:hamcrest-library:2.2'
testCompile 'org.mockito:mockito-core:3.1.0' testImplementation 'org.mockito:mockito-core:4.3.1'
testCompile 'org.assertj:assertj-core:3.14.0' testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
testCompile 'org.eclipse.jdt.core.compiler:ecj:4.6.1' testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
testCompileOnly 'org.jetbrains:annotations:23.0.0'
} }
test { test {
@@ -53,31 +49,8 @@ allprojects {
repositories { repositories {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
jcenter()
google() google()
} }
jacoco {
toolVersion = "0.8.2"
}
jacocoTestReport {
reports {
xml.enabled = true
html.enabled = true
}
}
checkstyleMain {
// exclude all sources in samples module
exclude '**/samples/**'
}
}
sonarqube {
properties {
property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*'
property 'sonar.coverage.exclusions', '**/jadx/gui/**/*'
}
} }
spotless { spotless {
@@ -86,11 +59,18 @@ spotless {
include 'jadx-cli/src/**/java/**/*.java' include 'jadx-cli/src/**/java/**/*.java'
include 'jadx-core/src/**/java/**/*.java' include 'jadx-core/src/**/java/**/*.java'
include 'jadx-gui/src/**/java/**/*.java' include 'jadx-gui/src/**/java/**/*.java'
include 'jadx-plugins/**/java/**/*.java'
} }
importOrderFile 'config/code-formatter/eclipse.importorder' importOrderFile 'config/code-formatter/eclipse.importorder'
eclipse().configFile 'config/code-formatter/eclipse.xml' eclipse().configFile 'config/code-formatter/eclipse.xml'
removeUnusedImports() if (JavaVersion.current() < JavaVersion.VERSION_16) {
removeUnusedImports()
} else {
// google-format on Java 16+ issue: https://github.com/diffplug/spotless/issues/834
println('Warning! Unused imports remove is disabled for Java 16+'
+ ' (use workaround from https://github.com/diffplug/spotless/tree/main/plugin-gradle#google-java-format)')
}
lineEndings(com.diffplug.spotless.LineEnding.UNIX) lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8") encoding("UTF-8")
@@ -99,7 +79,7 @@ spotless {
} }
format 'misc', { format 'misc', {
target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties' target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties'
targetExclude ".gradle/**", ".idea/**" targetExclude ".gradle/**", ".idea/**", "*/build/**"
lineEndings(com.diffplug.spotless.LineEnding.UNIX) lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8") encoding("UTF-8")
@@ -108,47 +88,70 @@ spotless {
} }
} }
dependencyUpdates.resolutionStrategy = { dependencyUpdates {
componentSelection { rules -> resolutionStrategy {
rules.all { ComponentSelection selection -> componentSelection { rules ->
boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier -> rules.all { ComponentSelection selection ->
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/ boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
} selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
if (rejected) { }
selection.reject('Release candidate') if (rejected) {
selection.reject('Release candidate')
}
} }
} }
} }
} }
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) { task copyArtifacts(type: Copy) {
destinationDir file("$buildDir/jadx") from tasks.getByPath(":jadx-cli:installDist")
['jadx-cli', 'jadx-gui'].each { from tasks.getByPath(":jadx-gui:installDist")
from tasks.getByPath(":${it}:installDist").destinationDir into layout.buildDirectory.dir("jadx")
} duplicatesStrategy = DuplicatesStrategy.EXCLUDE
} }
task pack(type: Zip, dependsOn: copyArtifacts) { task pack(type: Zip) {
destinationDir buildDir from copyArtifacts
archiveName "jadx-${jadxVersion}.zip" archiveFileName = "jadx-${jadxVersion}.zip"
from copyArtifacts.destinationDir destinationDirectory = layout.buildDirectory
} }
task copyExe(type: Copy, dependsOn: 'jadx-gui:createExe') { task copyExe(type: Copy) {
group 'jadx' group 'jadx'
description = 'Copy exe to build dir' description = 'Copy exe to build dir'
destinationDir buildDir mustRunAfter pack // not needed, but gradle throws warning because of same output dir
from tasks.getByPath('jadx-gui:createExe').outputs
from tasks.getByPath('jadx-gui:createExe')
include '*.exe' include '*.exe'
into layout.buildDirectory
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
} }
task dist(dependsOn: [pack, copyExe]) { 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 dist {
group 'jadx' group 'jadx'
description = 'Build jadx distribution zip' description = 'Build jadx distribution zip'
}
task samples(dependsOn: 'jadx-samples:samples') { dependsOn(pack)
group 'jadx'
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
if (os.isWindows()) {
if (project.hasProperty("bundleJRE")) {
println("Build win bundle with JRE")
dependsOn('distWinBundle')
} else {
dependsOn('copyExe')
}
}
} }
task cleanBuildDir(type: Delete) { task cleanBuildDir(type: Delete) {
@@ -156,6 +159,4 @@ task cleanBuildDir(type: Delete) {
delete buildDir delete buildDir
} }
test.dependsOn(samples)
clean.dependsOn(cleanBuildDir) clean.dependsOn(cleanBuildDir)
+3
View File
@@ -0,0 +1,3 @@
plugins {
id 'groovy-gradle-plugin'
}
@@ -0,0 +1,78 @@
plugins {
id 'java-library'
id 'maven-publish'
id 'signing'
}
group = 'io.github.skylot'
version = jadxVersion
java {
withJavadocJar()
withSourcesJar()
}
publishing {
publications {
mavenJava(MavenPublication) {
artifactId = project.name
from components.java
versionMapping {
usage('java-api') {
fromResolutionOf('runtimeClasspath')
}
usage('java-runtime') {
fromResolutionResult()
}
}
pom {
name = project.name
description = 'Dex to Java decompiler'
url = 'https://github.com/skylot/jadx'
licenses {
license {
name = 'The Apache License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id = 'skylot'
name = 'Skylot'
email = 'skylot@gmail.com'
url = 'https://github.com/skylot'
}
}
scm {
connection = 'scm:git:git://github.com/skylot/jadx.git'
developerConnection = 'scm:git:ssh://github.com:skylot/jadx.git'
url = 'https://github.com/skylot/jadx'
}
}
}
}
repositories {
maven {
def releasesRepoUrl = uri('https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/')
def snapshotsRepoUrl = uri('https://s01.oss.sonatype.org/content/repositories/snapshots/')
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials {
username = project.properties['ossrhUser'].toString()
password = project.properties['ossrhPassword'].toString()
}
}
}
}
signing {
sign publishing.publications.mavenJava
}
javadoc {
if (JavaVersion.current().isJava9Compatible()) {
options.addBooleanOption('html5', true)
}
// disable 'missing' warnings
options.addStringOption('Xdoclint:all,-missing', '-quiet')
}
+11
View File
@@ -119,6 +119,17 @@
<module name="OuterTypeNumber"/> <module name="OuterTypeNumber"/>
<module name="SuppressWarningsHolder"/> <module name="SuppressWarningsHolder"/>
<module name="IllegalType"/>
<module name="IllegalImport">
<property name="illegalClasses" value="jadx.core.utils.DebugUtils"/>
</module>
<module name="RegexpSinglelineJava">
<property name="id" value="printstacktrace"/>
<property name="format" value="\.printStackTrace\(\)"/>
<property name="ignoreComments" value="true"/>
<property name="message" value="Using Throwable.printStackTrace() is forbidden. Use logger to print exception"/>
</module>
</module> </module>
<module name="NewlineAtEndOfFile"/> <module name="NewlineAtEndOfFile"/>
+1
View File
@@ -0,0 +1 @@
SmaliTokenMaker.java
+5
View File
@@ -0,0 +1,5 @@
Refer to the following instructions to modify and use to generate SmaliTokenMarker
```shell
jflex SmaliTokenMaker.flex --skel skeleton.default
```
+681
View File
@@ -0,0 +1,681 @@
/*
* Generated on 11/22/21, 8:58 PM
*/
package jadx.gui.ui.codeearea;
import java.io.*;
import javax.swing.text.Segment;
import org.fife.ui.rsyntaxtextarea.*;
/**
* 用于Smali代码高亮
* MartinKay@qq.com
*/
%%
%public
%class SmaliTokenMaker
%extends AbstractJFlexCTokenMaker
%unicode
/* Case sensitive */
%type org.fife.ui.rsyntaxtextarea.Token
%{
/**
* Constructor. This must be here because JFlex does not generate a
* no-parameter constructor.
*/
public SmaliTokenMaker() {
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
* @see #addToken(int, int, int)
*/
private void addHyperlinkToken(int start, int end, int tokenType) {
int so = start + offsetShift;
addToken(zzBuffer, start,end, tokenType, so, true);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
*/
private void addToken(int tokenType) {
addToken(zzStartRead, zzMarkedPos-1, tokenType);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
* @see #addHyperlinkToken(int, int, int)
*/
private void addToken(int start, int end, int tokenType) {
int so = start + offsetShift;
addToken(zzBuffer, start,end, tokenType, so, false);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param array The character array.
* @param start The starting offset in the array.
* @param end The ending offset in the array.
* @param tokenType The token's type.
* @param startOffset The offset in the document at which this token
* occurs.
* @param hyperlink Whether this token is a hyperlink.
*/
public void addToken(char[] array, int start, int end, int tokenType,
int startOffset, boolean hyperlink) {
super.addToken(array, start,end, tokenType, startOffset, hyperlink);
zzStartRead = zzMarkedPos;
}
/**
* {@inheritDoc}
*/
public String[] getLineCommentStartAndEnd(int languageIndex) {
return new String[] { "#", null };
}
/**
* Returns the first token in the linked list of tokens generated
* from <code>text</code>. This method must be implemented by
* subclasses so they can correctly implement syntax highlighting.
*
* @param text The text from which to get tokens.
* @param initialTokenType The token type we should start with.
* @param startOffset The offset into the document at which
* <code>text</code> starts.
* @return The first <code>Token</code> in a linked list representing
* the syntax highlighted text.
*/
public Token getTokenList(Segment text, int initialTokenType, int startOffset) {
resetTokenList();
this.offsetShift = -text.offset + startOffset;
// Start off in the proper state.
int state = Token.NULL;
switch (initialTokenType) {
/* No multi-line comments */
/* No documentation comments */
default:
state = Token.NULL;
}
s = text;
try {
yyreset(zzReader);
yybegin(state);
return yylex();
} catch (IOException ioe) {
ioe.printStackTrace();
return new TokenImpl();
}
}
/**
* Refills the input buffer.
*
* @return <code>true</code> if EOF was reached, otherwise
* <code>false</code>.
*/
private boolean zzRefill() {
return zzCurrentPos>=s.offset+s.count;
}
/**
* Resets the scanner to read from a new input stream.
* Does not close the old reader.
*
* All internal variables are reset, the old input stream
* <b>cannot</b> be reused (internal buffer is discarded and lost).
* Lexical state is set to <tt>YY_INITIAL</tt>.
*
* @param reader the new input stream
*/
public final void yyreset(Reader reader) {
// 's' has been updated.
zzBuffer = s.array;
/*
* We replaced the line below with the two below it because zzRefill
* no longer "refills" the buffer (since the way we do it, it's always
* "full" the first time through, since it points to the segment's
* array). So, we assign zzEndRead here.
*/
//zzStartRead = zzEndRead = s.offset;
zzStartRead = s.offset;
zzEndRead = zzStartRead + s.count - 1;
zzCurrentPos = zzMarkedPos = zzPushbackPos = s.offset;
zzLexicalState = YYINITIAL;
zzReader = reader;
zzAtBOL = true;
zzAtEOF = false;
}
%}
Letter = [A-Za-z]
LetterOrUnderscore = ({Letter}|"_")
NonzeroDigit = [1-9]
Digit = ("0"|{NonzeroDigit})
HexDigit = ({Digit}|[A-Fa-f])
OctalDigit = ([0-7])
AnyCharacterButApostropheOrBackSlash = ([^\\'])
AnyCharacterButDoubleQuoteOrBackSlash = ([^\\\"\n])
EscapedSourceCharacter = ("u"{HexDigit}{HexDigit}{HexDigit}{HexDigit})
Escape = ("\\"(([btnfr\"'\\])|([0123]{OctalDigit}?{OctalDigit}?)|({OctalDigit}{OctalDigit}?)|{EscapedSourceCharacter}))
NonSeparator = ([^\t\f\r\n\ \(\)\{\}\[\]\;\,\.\=\>\<\!\~\?\:\+\-\*\/\&\|\^\%\"\']|"#"|"\\")
IdentifierStart = ({LetterOrUnderscore}|"$")
IdentifierPart = ({IdentifierStart}|{Digit}|("\\"{EscapedSourceCharacter}))
LineTerminator = (\n)
WhiteSpace = ([ \t\f]+)
CharLiteral = ([\']({AnyCharacterButApostropheOrBackSlash}|{Escape})[\'])
UnclosedCharLiteral = ([\'][^\'\n]*)
ErrorCharLiteral = ({UnclosedCharLiteral}[\'])
StringLiteral = ([\"]({AnyCharacterButDoubleQuoteOrBackSlash}|{Escape})*[\"])
UnclosedStringLiteral = ([\"]([\\].|[^\\\"])*[^\"]?)
ErrorStringLiteral = ({UnclosedStringLiteral}[\"])
/* No multi-line comments */
/* No documentation comments */
LineCommentBegin = "#"
IntegerLiteral = ({Digit}+)
HexLiteral = (0x{HexDigit}+)
FloatLiteral = (({Digit}+)("."{Digit}+)?(e[+-]?{Digit}+)? | ({Digit}+)?("."{Digit}+)(e[+-]?{Digit}+)?)
ErrorNumberFormat = (({IntegerLiteral}|{HexLiteral}|{FloatLiteral}){NonSeparator}+)
BooleanLiteral = ("true"|"false")
Separator = ([\(\)\{\}\[\]])
Separator2 = ([\;,.])
Identifier = ({IdentifierStart}{IdentifierPart}*)
URLGenDelim = ([:\/\?#\[\]@])
URLSubDelim = ([\!\$&'\(\)\*\+,;=])
URLUnreserved = ({LetterOrUnderscore}|{Digit}|[\-\.\~])
URLCharacter = ({URLGenDelim}|{URLSubDelim}|{URLUnreserved}|[%])
URLCharacters = ({URLCharacter}*)
URLEndCharacter = ([\/\$]|{Letter}|{Digit})
URL = (((https?|f(tp|ile))"://"|"www.")({URLCharacters}{URLEndCharacter})?)
/* Custom Regex */
/* fully-qualified name Rules */
SimpleName = ([a-zA-Z0-9_$]*)
QUALIFIED_TYPE_NAME = ("L"({SimpleName}{SLASH})*{SimpleName}*";")
/* Types */
VOID_TYPE = ("V")
BOOLEAN_TYPE = ("Z")
BYTE_TYPE = ("B")
SHORT_TYPE = ("S")
CHAR_TYPE = ("C")
INT_TYPE = ("I")
LONG_TYPE = ("J")
FLOAT_TYPE = ("F")
DOUBLE_TYPE = ("D")
/* Multi Args Types Highlight */
MULTI_ARGS_TYPES = (({BOOLEAN_TYPE}|{BYTE_TYPE}|{SHORT_TYPE}|{CHAR_TYPE}|{INT_TYPE}|{LONG_TYPE}|{FLOAT_TYPE}|{DOUBLE_TYPE})+);
/* Types fully-qualified name */
COMPOUND_METHOD_ARG_LITERAL = (({BOOLEAN_TYPE}|{BYTE_TYPE}|{SHORT_TYPE}|{CHAR_TYPE}|{INT_TYPE}|{LONG_TYPE}|{FLOAT_TYPE}|{DOUBLE_TYPE})+{QUALIFIED_TYPE_NAME})
LBRACK = ("[")
RBRACK = ("]")
LPAREN = ("(")
RPAREN = (")")
LBRACE = ("{")
RBRACE = ("}")
COLON = (":")
ASSIGN = ("=")
DOT = (".")
SUB = ("-")
COMMA = (",")
SLASH = ("/")
LT = ("<")
GT = (">")
ARROW = ("->")
SEMI = (";")
ARROW_FUNCTION = ({ARROW}[a-zA-Z_$<>]*)
CustomSeparator = ({Separator}|{ARROW_FUNCTION})
/* Register */
VREGISTER = ("v"("0"|[1-9])*)
PREGISTER = ("p"("0"|[1-9])*)
/* Flags */
FLAG_PSWITCH = (":pswitch_"{SimpleName})
FLAG_PSWITCH_DATA = (":pswitch_data_"{SimpleName})
FLAG_GOTO = (":goto_"{SimpleName})
FLAG_COND = (":cond_"{SimpleName})
FLAG_TRY_START = (":try_start_"{SimpleName})
FLAG_TRY_END = (":try_end_"{SimpleName})
FLAG_CATCH = (":catch_"{SimpleName})
FLAG_CATCHALL = (":catchall_"{SimpleName})
FLAG_ARRAY = (":array_"{SimpleName})
/* No string state */
/* No char state */
/* No MLC state */
/* No documentation comment state */
%state EOL_COMMENT
%%
<YYINITIAL> {
/* Keywords Instructions Highlight */
"nop" |
"move" |
"move/from16" |
"move/16" |
"move-wide" |
"move-wide/from16" |
"move-wide/16" |
"move-object" |
"move-object/from16" |
"move-object/16" |
"move-result" |
"move-result-wide" |
"move-result-object" |
"move-exception" |
"return-void" |
"return" |
"return-wide" |
"return-object" |
"const/4" |
"const/16" |
"const" |
"const/high16" |
"const-wide/16" |
"const-wide/32" |
"const-wide" |
"const-wide/high16" |
"const-string" |
"const-string/jumbo" |
"const-class" |
"monitor-enter" |
"monitor-exit" |
"check-cast" |
"instance-of" |
"array-length" |
"new-instance" |
"new-array" |
"filled-new-array" |
"filled-new-array/range" |
"fill-array-data" |
"throw" |
"goto" |
"goto/16" |
"goto/32" |
"cmpl-float" |
"cmpg-float" |
"cmpl-double" |
"cmpg-double" |
"cmp-long" |
"if-eq" |
"if-ne" |
"if-lt" |
"if-ge" |
"if-gt" |
"if-le" |
"if-eqz" |
"if-nez" |
"if-ltz" |
"if-gez" |
"if-gtz" |
"if-lez" |
"aget" |
"aget-wide" |
"aget-object" |
"aget-boolean" |
"aget-byte" |
"aget-char" |
"aget-short" |
"aput" |
"aput-wide" |
"aput-object" |
"aput-boolean" |
"aput-byte" |
"aput-char" |
"aput-short" |
"iget" |
"iget-wide" |
"iget-object" |
"iget-boolean" |
"iget-byte" |
"iget-char" |
"iget-short" |
"iput" |
"iput-wide" |
"iput-object" |
"iput-boolean" |
"iput-byte" |
"iput-char" |
"iput-short" |
"sget" |
"sget-wide" |
"sget-object" |
"sget-boolean" |
"sget-byte" |
"sget-char" |
"sget-short" |
"sput" |
"sput-wide" |
"sput-object" |
"sput-boolean" |
"sput-byte" |
"sput-char" |
"sput-short" |
"invoke-virtual" |
"invoke-super" |
"invoke-direct" |
"invoke-static" |
"invoke-interface" |
"invoke-virtual/range" |
"invoke-super/range" |
"invoke-direct/range" |
"invoke-static/range" |
"invoke-interface/range" |
"neg-int" |
"not-int" |
"neg-long" |
"not-long" |
"neg-float" |
"neg-double" |
"int-to-long" |
"int-to-float" |
"int-to-double" |
"long-to-int" |
"long-to-float" |
"long-to-double" |
"float-to-int" |
"float-to-long" |
"float-to-double" |
"double-to-int" |
"double-to-long" |
"double-to-float" |
"int-to-byte" |
"int-to-char" |
"int-to-short" |
"add-int" |
"sub-int" |
"mul-int" |
"div-int" |
"rem-int" |
"and-int" |
"or-int" |
"xor-int" |
"shl-int" |
"shr-int" |
"ushr-int" |
"add-long" |
"sub-long" |
"mul-long" |
"div-long" |
"rem-long" |
"and-long" |
"or-long" |
"xor-long" |
"shl-long" |
"shr-long" |
"ushr-long" |
"add-float" |
"sub-float" |
"mul-float" |
"div-float" |
"rem-float" |
"add-double" |
"sub-double" |
"mul-double" |
"div-double" |
"rem-double" |
"add-int/2addr" |
"sub-int/2addr" |
"mul-int/2addr" |
"div-int/2addr" |
"rem-int/2addr" |
"and-int/2addr" |
"or-int/2addr" |
"xor-int/2addr" |
"shl-int/2addr" |
"shr-int/2addr" |
"ushr-int/2addr" |
"add-long/2addr" |
"sub-long/2addr" |
"mul-long/2addr" |
"div-long/2addr" |
"rem-long/2addr" |
"and-long/2addr" |
"or-long/2addr" |
"xor-long/2addr" |
"shl-long/2addr" |
"shr-long/2addr" |
"ushr-long/2addr" |
"add-float/2addr" |
"sub-float/2addr" |
"mul-float/2addr" |
"div-float/2addr" |
"rem-float/2addr" |
"add-double/2addr" |
"sub-double/2addr" |
"mul-double/2addr" |
"div-double/2addr" |
"rem-double/2addr" |
"add-int/lit16" |
"rsub-int" |
"mul-int/lit16" |
"div-int/lit16" |
"rem-int/lit16" |
"and-int/lit16" |
"or-int/lit16" |
"xor-int/lit16" |
"add-int/lit8" |
"rsub-int/lit8" |
"mul-int/lit8" |
"div-int/lit8" |
"rem-int/lit8" |
"and-int/lit8" |
"or-int/lit8" |
"xor-int/lit8" |
"shl-int/lit8" |
"shr-int/lit8" |
"ushr-int/lit8" |
"invoke-polymorphic" |
"invoke-polymorphic/range" |
"invoke-custom" |
"invoke-custom/range" |
"const-method-handle" |
"const-method-type" |
"packed-switch" |
"sparse-switch" { addToken(Token.FUNCTION); }
/* Keywords Modifiers(IDENTIFIER标识符、修饰符) Highlight */
"public" |
"private" |
"protected" |
"final" |
"annotation" |
"static" |
"synthetic" |
"constructor" |
"abstract" |
"enum" |
"interface" |
"transient" |
"bridge" |
"declared-synchronized" |
"volatile" |
"strictfp" |
"varargs" |
"native" { addToken(Token.RESERVED_WORD); }
/* Keywords Directives Highlight */
".method" |
".end method" |
".implements" |
".class" |
".prologue" |
".source" |
".super" |
".field" |
".end field" |
".registers" |
".locals" |
".param" |
".line" |
".catch" |
".catchall" |
".annotation" |
".end annotation" |
".local" |
".end local" |
".restart local" |
".packed-switch" |
".end packed-switch" |
".array-data" |
".end array-data" |
".sparse-switch" |
".end sparse-switch" |
".end param" { addToken(Token.RESERVED_WORD_2); }
/* VARIABLE Register Highlight */
{VREGISTER} |
{PREGISTER} { addToken(Token.VARIABLE); }
/* Data types Highlight */
{QUALIFIED_TYPE_NAME} |
{COMPOUND_METHOD_ARG_LITERAL} |
{MULTI_ARGS_TYPES} |
{QUALIFIED_TYPE_NAME} |
{VOID_TYPE} |
{BOOLEAN_TYPE} |
{BYTE_TYPE} |
{SHORT_TYPE} |
{CHAR_TYPE} |
{INT_TYPE} |
{LONG_TYPE} |
{FLOAT_TYPE} |
{DOUBLE_TYPE} { addToken(Token.DATA_TYPE); }
/* FLAGS */
{FLAG_PSWITCH} |
{FLAG_PSWITCH_DATA} |
{FLAG_GOTO} |
{FLAG_COND} |
{FLAG_TRY_START} |
{FLAG_TRY_END} |
{FLAG_CATCHALL} |
{FLAG_ARRAY} |
{FLAG_CATCH} { addToken(Token.MARKUP_TAG_NAME); }
/* Functions */
/* No functions */
{BooleanLiteral} { addToken(Token.LITERAL_BOOLEAN); }
{LineTerminator} { addNullToken(); return firstToken; }
{Identifier} { addToken(Token.IDENTIFIER); }
{WhiteSpace} { addToken(Token.WHITESPACE); }
/* String/Character literals. */
{CharLiteral} { addToken(Token.LITERAL_CHAR); }
{UnclosedCharLiteral} { addToken(Token.ERROR_CHAR); addNullToken(); return firstToken; }
{ErrorCharLiteral} { addToken(Token.ERROR_CHAR); }
{StringLiteral} { addToken(Token.LITERAL_STRING_DOUBLE_QUOTE); }
{UnclosedStringLiteral} { addToken(Token.ERROR_STRING_DOUBLE); addNullToken(); return firstToken; }
{ErrorStringLiteral} { addToken(Token.ERROR_STRING_DOUBLE); }
/* Comment literals. */
/* No multi-line comments */
/* No documentation comments */
{LineCommentBegin} { start = zzMarkedPos-1; yybegin(EOL_COMMENT); }
/* Separators. */
{CustomSeparator} { addToken(Token.SEPARATOR); }
{Separator2} { addToken(Token.IDENTIFIER); }
/* Operators. */
"!" |
";" |
"." |
"=" |
"/" |
"'" |
"(" |
")" |
"," |
"->" |
";->" |
"<" |
">" |
"@" |
"[" |
"]" |
"{" |
"}" { addToken(Token.OPERATOR); }
/* Numbers */
{IntegerLiteral} { addToken(Token.LITERAL_NUMBER_DECIMAL_INT); }
{HexLiteral} { addToken(Token.LITERAL_NUMBER_HEXADECIMAL); }
{FloatLiteral} { addToken(Token.LITERAL_NUMBER_FLOAT); }
{ErrorNumberFormat} { addToken(Token.ERROR_NUMBER_FORMAT); }
/* Ended with a line not in a string or comment. */
<<EOF>> { addNullToken(); return firstToken; }
/* Catch any other (unhandled) characters. */
. { addToken(Token.IDENTIFIER); }
}
/* No char state */
/* No string state */
/* No multi-line comment state */
/* No documentation comment state */
<EOL_COMMENT> {
[^hwf\n]+ {}
{URL} { int temp=zzStartRead; addToken(start,zzStartRead-1, Token.COMMENT_EOL); addHyperlinkToken(temp,zzMarkedPos-1, Token.COMMENT_EOL); start = zzMarkedPos; }
[hwf] {}
\n { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
<<EOF>> { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
}
+243
View File
@@ -0,0 +1,243 @@
/** This character denotes the end of file */
public static final int YYEOF = -1;
/** initial size of the lookahead buffer */
--- private static final int ZZ_BUFFERSIZE = ...;
/** lexical states */
--- lexical states, charmap
/* error codes */
private static final int ZZ_UNKNOWN_ERROR = 0;
private static final int ZZ_NO_MATCH = 1;
private static final int ZZ_PUSHBACK_2BIG = 2;
/* error messages for the codes above */
private static final String ZZ_ERROR_MSG[] = {
"Unkown internal scanner error",
"Error: could not match input",
"Error: pushback value was too large"
};
--- isFinal list
/** the input device */
private java.io.Reader zzReader;
/** the current state of the DFA */
private int zzState;
/** the current lexical state */
private int zzLexicalState = YYINITIAL;
/** this buffer contains the current text to be matched and is
the source of the yytext() string */
private char zzBuffer[];
/** the textposition at the last accepting state */
private int zzMarkedPos;
/** the textposition at the last state to be included in yytext */
private int zzPushbackPos;
/** the current text position in the buffer */
private int zzCurrentPos;
/** startRead marks the beginning of the yytext() string in the buffer */
private int zzStartRead;
/** endRead marks the last character in the buffer, that has been read
from input */
private int zzEndRead;
/** number of newlines encountered up to the start of the matched text */
private int yyline;
/** the number of characters up to the start of the matched text */
private int yychar;
/**
* the number of characters from the last newline up to the start of the
* matched text
*/
private int yycolumn;
/**
* zzAtBOL == true <=> the scanner is currently at the beginning of a line
*/
private boolean zzAtBOL = true;
/** zzAtEOF == true <=> the scanner is at the EOF */
private boolean zzAtEOF;
--- user class code
/**
* Creates a new scanner
* There is also a java.io.InputStream version of this constructor.
*
* @param in the java.io.Reader to read input from.
*/
--- constructor declaration
/**
* Closes the input stream.
*/
public final void yyclose() throws java.io.IOException {
zzAtEOF = true; /* indicate end of file */
zzEndRead = zzStartRead; /* invalidate buffer */
if (zzReader != null)
zzReader.close();
}
/**
* Enters a new lexical state
*
* @param newState the new lexical state
*/
public final void yybegin(int newState) {
zzLexicalState = newState;
}
/**
* Returns the text matched by the current regular expression.
*/
public final String yytext() {
return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
}
/**
* Returns the character at position <tt>pos</tt> from the
* matched text.
*
* It is equivalent to yytext().charAt(pos), but faster
*
* @param pos the position of the character to fetch.
* A value from 0 to yylength()-1.
*
* @return the character at position pos
*/
public final char yycharat(int pos) {
return zzBuffer[zzStartRead+pos];
}
/**
* Returns the length of the matched text region.
*/
public final int yylength() {
return zzMarkedPos-zzStartRead;
}
/**
* Reports an error that occured while scanning.
*
* In a wellformed scanner (no or only correct usage of
* yypushback(int) and a match-all fallback rule) this method
* will only be called with things that "Can't Possibly Happen".
* If this method is called, something is seriously wrong
* (e.g. a JFlex bug producing a faulty scanner etc.).
*
* Usual syntax/scanner level error handling should be done
* in error fallback rules.
*
* @param errorCode the code of the errormessage to display
*/
--- zzScanError declaration
String message;
try {
message = ZZ_ERROR_MSG[errorCode];
}
catch (ArrayIndexOutOfBoundsException e) {
message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
}
--- throws clause
}
/**
* Pushes the specified amount of characters back into the input stream.
*
* They will be read again by then next call of the scanning method
*
* @param number the number of characters to be read again.
* This number must not be greater than yylength()!
*/
--- yypushback decl (contains zzScanError exception)
if ( number > yylength() )
zzScanError(ZZ_PUSHBACK_2BIG);
zzMarkedPos -= number;
}
--- zzDoEOF
/**
* Resumes scanning until the next regular expression is matched,
* the end of input is encountered or an I/O-Error occurs.
*
* @return the next token
* @exception java.io.IOException if any I/O-Error occurs
*/
--- yylex declaration
int zzInput;
int zzAction;
// cached fields:
int zzCurrentPosL;
int zzMarkedPosL;
int zzEndReadL = zzEndRead;
char [] zzBufferL = zzBuffer;
char [] zzCMapL = ZZ_CMAP;
--- local declarations
while (true) {
zzMarkedPosL = zzMarkedPos;
--- start admin (line, char, col count)
zzAction = -1;
zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
--- start admin (lexstate etc)
zzForAction: {
while (true) {
--- next input, line, col, char count, next transition, isFinal action
zzAction = zzState;
zzMarkedPosL = zzCurrentPosL;
--- line count update
}
}
}
// store back cached position
zzMarkedPos = zzMarkedPosL;
--- char count update
--- actions
default:
if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
zzAtEOF = true;
--- eofvalue
}
else {
--- no match
}
}
}
}
--- main
}
+1 -1
View File
@@ -1 +1 @@
org.gradle.daemon=false org.gradle.warning.mode=all
Binary file not shown.
+2 -1
View File
@@ -1,5 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip distributionSha256Sum=8cc27038d5dbd815759851ba53e70cf62e481b87494cc97cfd97982ada5ba634
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
Vendored
+160 -109
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env sh #!/bin/sh
# #
# Copyright 2015 the original author or authors. # Copyright © 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -17,78 +17,113 @@
# #
############################################################################## ##############################################################################
## #
## Gradle start up script for UN*X # Gradle start up script for POSIX generated by Gradle.
## #
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
@@ -105,79 +140,95 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD=$( ulimit -H -n ) ||
MAX_FD="$MAX_FD_LIMIT" warn "Could not query maximum file descriptor limit"
fi esac
ulimit -n $MAX_FD case $MAX_FD in #(
if [ $? -ne 0 ] ; then '' | soft) :;; #(
warn "Could not set maximum file descriptor limit: $MAX_FD" *)
fi ulimit -n "$MAX_FD" ||
else warn "Could not set maximum file descriptor limit to $MAX_FD"
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
# Escape application args # Collect all arguments for the java command, stacking in reverse order:
save () { # * args from the command line
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # * the main class name
echo " " # * -classpath
} # * -D...appname settings
APP_ARGS=`save "$@"` # * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules # For Cygwin or MSYS, switch paths to Windows format before running java
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"
Vendored
+7 -18
View File
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init if "%ERRORLEVEL%" == "0" goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -51,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init if exist "%JAVA_EXE%" goto execute
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -61,28 +64,14 @@ echo location of your Java installation.
goto fail goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
+10 -4
View File
@@ -3,14 +3,20 @@ plugins {
} }
dependencies { dependencies {
compile(project(':jadx-core')) implementation(project(':jadx-core'))
compile 'com.beust:jcommander:1.78'
compile 'ch.qos.logback:logback-classic:1.2.3' runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
runtimeOnly(project(':jadx-plugins:jadx-java-input'))
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
implementation 'com.beust:jcommander:1.82'
implementation 'ch.qos.logback:logback-classic:1.2.10'
} }
application { application {
applicationName = 'jadx' applicationName = 'jadx'
mainClassName = 'jadx.cli.JadxCLI' mainClass.set('jadx.cli.JadxCLI')
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC'] applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
} }
@@ -5,8 +5,8 @@ import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -80,26 +80,37 @@ public class JCommanderWrapper<T> {
} }
StringBuilder opt = new StringBuilder(); StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames()); opt.append(" ").append(p.getNames());
String description = p.getDescription();
addSpaces(opt, maxNamesLen - opt.length() + 3); 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); String defaultValue = getDefaultValue(args, f, opt);
if (defaultValue != null) { if (defaultValue != null && !description.contains("(default)")) {
opt.append(", default: ").append(defaultValue); opt.append(", default: ").append(defaultValue);
} }
out.println(opt); out.println(opt);
} }
out.println("Example:"); out.println("Examples:");
out.println(" jadx -d out classes.dex"); out.println(" jadx -d out classes.dex");
out.println(" jadx --rename-flags \"none\" classes.dex");
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
out.println(" jadx --log-level ERROR app.apk");
} }
/** /**
* Get all declared fields of the specified class and all super classes * Get all declared fields of the specified class and all super classes
*
* @param clazz
* @return
*/ */
private List<Field> getFields(Class<?> clazz) { private List<Field> getFields(Class<?> clazz) {
List<Field> fieldList = new LinkedList<>(); List<Field> fieldList = new ArrayList<>();
while (clazz != null) { while (clazz != null) {
fieldList.addAll(Arrays.asList(clazz.getDeclaredFields())); fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
@@ -120,7 +131,7 @@ public class JCommanderWrapper<T> {
if (Enum.class.isAssignableFrom(fieldType)) { if (Enum.class.isAssignableFrom(fieldType)) {
Enum<?> val = (Enum<?>) f.get(args); Enum<?> val = (Enum<?>) f.get(args);
if (val != null) { if (val != null) {
return val.name(); return val.name().toLowerCase(Locale.ROOT);
} }
} }
} catch (Exception e) { } catch (Exception e) {
+42 -21
View File
@@ -6,7 +6,9 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.impl.NoOpCodeCache; import jadx.api.impl.NoOpCodeCache;
import jadx.api.impl.SimpleCodeWriter;
import jadx.core.utils.exceptions.JadxArgsValidateException; import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.files.FileUtils;
public class JadxCLI { public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class); private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
@@ -14,36 +16,55 @@ public class JadxCLI {
public static void main(String[] args) { public static void main(String[] args) {
int result = 0; int result = 0;
try { try {
JadxCLIArgs jadxArgs = new JadxCLIArgs(); result = execute(args);
if (jadxArgs.processArgs(args)) { } catch (JadxArgsValidateException e) {
result = processAndSave(jadxArgs); LOG.error("Incorrect arguments: {}", e.getMessage());
} result = 1;
} catch (Exception e) { } catch (Exception e) {
LOG.error("jadx error: {}", e.getMessage(), e); LOG.error("jadx error: {}", e.getMessage(), e);
result = 1; result = 1;
} finally { } finally {
FileUtils.deleteTempRootDir();
System.exit(result); System.exit(result);
} }
} }
static int processAndSave(JadxCLIArgs inputArgs) { public static int execute(String[] args) {
JadxArgs args = inputArgs.toJadxArgs(); JadxCLIArgs jadxArgs = new JadxCLIArgs();
args.setCodeCache(new NoOpCodeCache()); if (jadxArgs.processArgs(args)) {
JadxDecompiler jadx = new JadxDecompiler(args); return processAndSave(jadxArgs);
try {
jadx.load();
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
return 1;
}
jadx.save();
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors, count: {}", errorsCount);
} else {
LOG.info("done");
} }
return 0; return 0;
} }
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();
if (!SingleClassMode.process(jadx, cliArgs)) {
save(jadx);
}
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors, count: {}", errorsCount);
} else {
LOG.info("done");
}
}
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);
});
}
}
} }
+161 -26
View File
@@ -1,25 +1,28 @@
package jadx.cli; package jadx.cli;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.beust.jcommander.IStringConverter; import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RenameEnum; import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
public class JadxCLIArgs { public class JadxCLIArgs {
@Parameter(description = "<input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)") @Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab)")
protected List<String> files = new ArrayList<>(1); protected List<String> files = new ArrayList<>(1);
@Parameter(names = { "-d", "--output-dir" }, description = "output directory") @Parameter(names = { "-d", "--output-dir" }, description = "output directory")
@@ -37,9 +40,12 @@ public class JadxCLIArgs {
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code") @Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
protected boolean skipSources = false; protected boolean skipSources = false;
@Parameter(names = { "--single-class" }, description = "decompile a single class") @Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
protected String singleClass = null; protected String singleClass = null;
@Parameter(names = { "--single-class-output" }, description = "file or dir for write if decompile a single class")
protected String singleClassOutput = null;
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'") @Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
protected String outputFormat = "java"; protected String outputFormat = "java";
@@ -58,9 +64,15 @@ public class JadxCLIArgs {
@Parameter(names = { "--no-debug-info" }, description = "disable debug info") @Parameter(names = { "--no-debug-info" }, description = "disable debug info")
protected boolean debugInfo = true; protected boolean debugInfo = true;
@Parameter(names = { "--add-debug-lines" }, description = "add comments with debug line numbers if available")
protected boolean addDebugLines = false;
@Parameter(names = { "--no-inline-anonymous" }, description = "disable anonymous classes inline") @Parameter(names = { "--no-inline-anonymous" }, description = "disable anonymous classes inline")
protected boolean inlineAnonymousClasses = true; protected boolean inlineAnonymousClasses = true;
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
protected boolean inlineMethods = true;
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field") @Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
protected boolean replaceConsts = true; protected boolean replaceConsts = true;
@@ -79,19 +91,47 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer") @Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer")
protected int deobfuscationMaxLength = 64; protected int deobfuscationMaxLength = 64;
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "force to save deobfuscation map") @Parameter(
names = { "--deobf-cfg-file" },
description = "deobfuscation map file, default: same dir and name as input file with '.jobf' extension"
)
protected String deobfuscationMapFile;
@Parameter(
names = { "--deobf-cfg-file-mode" },
description = "set mode for handle deobfuscation map file:"
+ "\n 'read' - read if found, don't save (default)"
+ "\n 'read-or-save' - read if found, save otherwise (don't overwrite)"
+ "\n 'overwrite' - don't read, always save"
+ "\n 'ignore' - don't read and don't save",
converter = DeobfuscationMapFileModeConverter.class
)
protected DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)")
protected boolean deobfuscationForceSave = false; protected boolean deobfuscationForceSave = false;
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias") @Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = false; protected boolean deobfuscationUseSourceNameAsAlias = false;
@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( @Parameter(
names = { "--rename-flags" }, names = { "--rename-flags" },
description = "what to rename, comma-separated," description = "fix options (comma-separated list of):"
+ " 'case' for system case sensitivity," + "\n 'case' - fix case sensitivity issues (according to --fs-case-sensitive option),"
+ " 'valid' for java identifiers," + "\n 'valid' - rename java identifiers to make them valid,"
+ " 'printable' characters," + "\n 'printable' - remove non-printable chars from identifiers,"
+ " 'none' or 'all' (default)", + "\nor single 'none' - to disable all renames"
+ "\nor single 'all' - to enable all (default)",
converter = RenameConverter.class converter = RenameConverter.class
) )
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class); protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
@@ -108,19 +148,29 @@ public class JadxCLIArgs {
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)") @Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false; protected boolean fallbackMode = false;
@Parameter(names = { "--use-dx" }, description = "use dx/d8 to convert java bytecode")
protected boolean useDx = false;
@Parameter(
names = { "--comments-level" },
description = "set code comments level, values: error, warn, info, debug, user-only, none",
converter = CommentsLevelConverter.class
)
protected CommentsLevel commentsLevel = CommentsLevel.INFO;
@Parameter(
names = { "--log-level" },
description = "set log level, values: quiet, progress, error, warn, info, debug",
converter = LogHelper.LogLevelConverter.class
)
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
@Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)") @Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)")
protected boolean verbose = false; protected boolean verbose = false;
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)") @Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
protected boolean quiet = false; protected boolean quiet = false;
@Parameter(
names = { "--log-level" },
description = "set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG",
converter = LogHelper.LogLevelConverter.class
)
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
@Parameter(names = { "--version" }, description = "print jadx version") @Parameter(names = { "--version" }, description = "print jadx version")
protected boolean printVersion = false; protected boolean printVersion = false;
@@ -180,9 +230,6 @@ public class JadxCLIArgs {
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase())); args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
args.setThreadsCount(threadsCount); args.setThreadsCount(threadsCount);
args.setSkipSources(skipSources); args.setSkipSources(skipSources);
if (singleClass != null) {
args.setClassFilter(className -> singleClass.equals(className));
}
args.setSkipResources(skipResources); args.setSkipResources(skipResources);
args.setFallbackMode(fallbackMode); args.setFallbackMode(fallbackMode);
args.setShowInconsistentCode(showInconsistentCode); args.setShowInconsistentCode(showInconsistentCode);
@@ -190,20 +237,29 @@ public class JadxCLIArgs {
args.setRawCFGOutput(rawCfgOutput); args.setRawCFGOutput(rawCfgOutput);
args.setReplaceConsts(replaceConsts); args.setReplaceConsts(replaceConsts);
args.setDeobfuscationOn(deobfuscationOn); args.setDeobfuscationOn(deobfuscationOn);
args.setDeobfuscationForceSave(deobfuscationForceSave); args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
if (deobfuscationForceSave) {
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
} else {
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
}
args.setDeobfuscationMinLength(deobfuscationMinLength); args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength); args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias); args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
args.setEscapeUnicode(escapeUnicode); args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers); args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject); args.setExportAsGradleProject(exportAsGradleProject);
args.setUseImports(useImports); args.setUseImports(useImports);
args.setDebugInfo(debugInfo); args.setDebugInfo(debugInfo);
args.setInsertDebugLines(addDebugLines);
args.setInlineAnonymousClasses(inlineAnonymousClasses); args.setInlineAnonymousClasses(inlineAnonymousClasses);
args.setRenameCaseSensitive(isRenameCaseSensitive()); args.setInlineMethods(inlineMethods);
args.setRenameValid(isRenameValid()); args.setRenameFlags(renameFlags);
args.setRenamePrintable(isRenamePrintable());
args.setFsCaseSensitive(fsCaseSensitive); args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel);
args.setUseDxInput(useDx);
return args; return args;
} }
@@ -223,6 +279,14 @@ public class JadxCLIArgs {
return outDirRes; return outDirRes;
} }
public String getSingleClass() {
return singleClass;
}
public String getSingleClassOutput() {
return singleClassOutput;
}
public boolean isSkipResources() { public boolean isSkipResources() {
return skipResources; return skipResources;
} }
@@ -239,6 +303,10 @@ public class JadxCLIArgs {
return fallbackMode; return fallbackMode;
} }
public boolean isUseDx() {
return useDx;
}
public boolean isShowInconsistentCode() { public boolean isShowInconsistentCode() {
return showInconsistentCode; return showInconsistentCode;
} }
@@ -251,10 +319,18 @@ public class JadxCLIArgs {
return debugInfo; return debugInfo;
} }
public boolean isAddDebugLines() {
return addDebugLines;
}
public boolean isInlineAnonymousClasses() { public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses; return inlineAnonymousClasses;
} }
public boolean isInlineMethods() {
return inlineMethods;
}
public boolean isDeobfuscationOn() { public boolean isDeobfuscationOn() {
return deobfuscationOn; return deobfuscationOn;
} }
@@ -267,6 +343,14 @@ public class JadxCLIArgs {
return deobfuscationMaxLength; return deobfuscationMaxLength;
} }
public String getDeobfuscationMapFile() {
return deobfuscationMapFile;
}
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
return deobfuscationMapFileMode;
}
public boolean isDeobfuscationForceSave() { public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave; return deobfuscationForceSave;
} }
@@ -275,6 +359,14 @@ public class JadxCLIArgs {
return deobfuscationUseSourceNameAsAlias; return deobfuscationUseSourceNameAsAlias;
} }
public boolean isDeobfuscationParseKotlinMetadata() {
return deobfuscationParseKotlinMetadata;
}
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public boolean isEscapeUnicode() { public boolean isEscapeUnicode() {
return escapeUnicode; return escapeUnicode;
} }
@@ -315,6 +407,10 @@ public class JadxCLIArgs {
return fsCaseSensitive; return fsCaseSensitive;
} }
public CommentsLevel getCommentsLevel() {
return commentsLevel;
}
static class RenameConverter implements IStringConverter<Set<RenameEnum>> { static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
private final String paramName; private final String paramName;
@@ -333,7 +429,7 @@ public class JadxCLIArgs {
Set<RenameEnum> set = EnumSet.noneOf(RenameEnum.class); Set<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
for (String s : value.split(",")) { for (String s : value.split(",")) {
try { try {
set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT))); set.add(RenameEnum.valueOf(s.trim().toUpperCase(Locale.ROOT)));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
'\'' + s + "' is unknown for parameter " + paramName '\'' + s + "' is unknown for parameter " + paramName
@@ -344,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) { public static String enumValuesString(Enum<?>[] values) {
return Arrays.stream(values) return Stream.of(values)
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'') .map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
.collect(Collectors.joining(", ")); .collect(Collectors.joining(", "));
} }
} }
+17 -2
View File
@@ -1,5 +1,6 @@
package jadx.cli; package jadx.cli;
import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter; import com.beust.jcommander.IStringConverter;
@@ -31,6 +32,8 @@ public class LogHelper {
} }
} }
private static LogLevelEnum logLevelValue;
public static void setLogLevelFromArgs(JadxCLIArgs args) { public static void setLogLevelFromArgs(JadxCLIArgs args) {
if (isCustomLogConfig()) { if (isCustomLogConfig()) {
return; return;
@@ -46,20 +49,32 @@ public class LogHelper {
} }
public static void applyLogLevel(LogLevelEnum logLevel) { public static void applyLogLevel(LogLevelEnum logLevel) {
logLevelValue = logLevel;
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(logLevel.getLevel()); rootLogger.setLevel(logLevel.getLevel());
if (logLevel != LogLevelEnum.QUIET) { if (logLevel == LogLevelEnum.PROGRESS) {
// show progress for all levels except quiet // show progress for all levels except quiet
setLevelForClass(JadxCLI.class, Level.INFO); setLevelForClass(JadxCLI.class, Level.INFO);
setLevelForClass(JadxDecompiler.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); ((Logger) LoggerFactory.getLogger(cls)).setLevel(level);
} }
public static void setLevelForPackage(String pkgName, Level level) {
((Logger) LoggerFactory.getLogger(pkgName)).setLevel(level);
}
/** /**
* Try to detect if user provide custom logback config via -Dlogback.configurationFile= * Try to detect if user provide custom logback config via -Dlogback.configurationFile=
*/ */
@@ -0,0 +1,87 @@
package jadx.cli;
import java.io.File;
import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.api.JadxDecompiler;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
public class SingleClassMode {
private static final Logger LOG = LoggerFactory.getLogger(SingleClassMode.class);
public static boolean process(JadxDecompiler jadx, JadxCLIArgs cliArgs) {
String singleClass = cliArgs.getSingleClass();
String singleClassOutput = cliArgs.getSingleClassOutput();
if (singleClass == null && singleClassOutput == null) {
return false;
}
ClassNode clsForProcess;
if (singleClass != null) {
clsForProcess = jadx.getRoot().resolveClass(singleClass);
if (clsForProcess == null) {
clsForProcess = jadx.getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(singleClass))
.findFirst().orElse(null);
}
if (clsForProcess == null) {
throw new JadxRuntimeException("Input class not found: " + singleClass);
}
if (clsForProcess.contains(AFlag.DONT_GENERATE)) {
throw new JadxRuntimeException("Input class can't be saved by currect jadx settings (marked as DONT_GENERATE)");
}
if (clsForProcess.isInner()) {
clsForProcess = clsForProcess.getTopParentClass();
LOG.warn("Input class is inner, parent class will be saved: {}", clsForProcess.getFullName());
}
} else {
// singleClassOutput is set
// expect only one class to be loaded
List<ClassNode> classes = jadx.getRoot().getClasses().stream()
.filter(c -> !c.isInner() && !c.contains(AFlag.DONT_GENERATE))
.collect(Collectors.toList());
int size = classes.size();
if (size == 1) {
clsForProcess = classes.get(0);
} else {
throw new JadxRuntimeException("Found " + size + " classes, single class output can't be used");
}
}
ICodeInfo codeInfo;
try {
codeInfo = clsForProcess.decompile();
} catch (Exception e) {
throw new JadxRuntimeException("Class decompilation failed", e);
}
String fileExt = SaveCode.getFileExtension(jadx.getRoot());
File out;
if (singleClassOutput == null) {
out = new File(jadx.getArgs().getOutDirSrc(), clsForProcess.getClassInfo().getAliasFullPath() + fileExt);
} else {
if (singleClassOutput.endsWith(fileExt)) {
// treat as file name
out = new File(singleClassOutput);
} else {
// treat as directory
out = new File(singleClassOutput, clsForProcess.getShortName() + fileExt);
}
}
File resultOut = FileUtils.prepareFile(out);
if (clsForProcess.getClassInfo().hasAlias()) {
LOG.info("Saving class '{}' (alias: '{}') to file '{}'",
clsForProcess.getClassInfo().getFullName(), clsForProcess.getFullName(), resultOut.getAbsolutePath());
} else {
LOG.info("Saving class '{}' to file '{}'", clsForProcess.getFullName(), resultOut.getAbsolutePath());
}
SaveCode.save(codeInfo.getCodeStr(), resultOut);
return true;
}
}
@@ -0,0 +1,67 @@
package jadx.cli.clst;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
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)
*/
public class ConvertToClsSet {
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
public static void usage() {
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
}
public static void main(String[] args) throws Exception {
if (args.length < 2) {
usage();
System.exit(1);
}
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
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));
}
JadxArgs jadxArgs = new JadxArgs();
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
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: {}", 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"), () -> converter.convert("wrong"),
"Expected convert() to throw, but it didn't"); "Expected convert() to throw, but it didn't");
assertEquals("'wrong' is unknown for parameter someParam, " assertEquals("'wrong' is unknown for parameter someParam, possible values are case, valid, printable",
+ "possible values are 'case', 'valid', 'printable'",
thrown.getMessage()); thrown.getMessage());
} }
} }
@@ -0,0 +1,96 @@
package jadx.cli;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
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;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.utils.files.FileUtils;
import static org.assertj.core.api.Assertions.assertThat;
public class TestInput {
private static final Logger LOG = LoggerFactory.getLogger(TestInput.class);
@Test
public void testDexInput() throws Exception {
decompile("dex", "samples/hello.dex");
}
@Test
public void testSmaliInput() throws Exception {
decompile("smali", "samples/HelloWorld.smali");
}
@Test
public void testClassInput() throws Exception {
decompile("class", "samples/HelloWorld.class");
}
@Test
public void testMultipleInput() throws Exception {
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
}
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
List<String> args = new ArrayList<>();
Path tempDir = FileUtils.createTempDir(tmpDirName);
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.add(sampleFile);
}
int result = JadxCLI.execute(args.toArray(new String[0]));
assertThat(result).isEqualTo(0);
List<Path> resultJavaFiles = collectJavaFilesInDir(tempDir);
assertThat(resultJavaFiles).isNotEmpty();
// do not copy input files as resources
PathMatcher logAllFiles = path -> {
LOG.debug("File in result dir: {}", path);
return true;
};
for (Path path : collectFilesInDir(tempDir, logAllFiles)) {
for (String inputSample : inputSamples) {
assertThat(path.toAbsolutePath().toString()).doesNotContain(inputSample);
}
}
}
private static List<Path> collectJavaFilesInDir(Path dir) throws IOException {
PathMatcher javaMatcher = dir.getFileSystem().getPathMatcher("glob:**.java");
return collectFilesInDir(dir, javaMatcher);
}
private static List<Path> collectFilesInDir(Path dir, PathMatcher matcher) throws IOException {
try (Stream<Path> pathStream = Files.walk(dir)) {
return pathStream
.filter(p -> Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS))
.filter(matcher::matches)
.collect(Collectors.toList());
}
}
@AfterAll
public static void cleanup() {
FileUtils.clearTempRootDir();
}
}
Binary file not shown.
@@ -0,0 +1,26 @@
.class Lsmali/HelloWorld;
.super Ljava/lang/Object;
.source "HelloWorld.java"
.method constructor <init>()V
.registers 1
.line 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public static main([Ljava/lang/String;)V
.registers 2
.line 3
sget-object p0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v0, "Hello, World"
invoke-virtual {p0, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.line 4
return-void
.end method
Binary file not shown.
+27 -17
View File
@@ -1,18 +1,28 @@
dependencies { plugins {
runtime files('clsp-data/android-29-clst.jar') id 'jadx-library'
runtime files('clsp-data/android-29-res.jar') }
compile files('lib/dx-1.16.jar') // TODO: dx don't support java version > 9 (53) dependencies {
api(project(':jadx-plugins:jadx-plugins-api'))
compile 'org.ow2.asm:asm:7.2'
compile 'org.jetbrains:annotations:18.0.0' implementation 'com.google.code.gson:gson:2.9.0'
compile 'com.google.code.gson:gson:2.8.6' implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
constraints {
compile 'org.smali:baksmali:2.3.4' // Force protobuf version to prevent Java-7 issue
compile('org.smali:smali:2.3.4') { implementation 'com.google.protobuf:protobuf-java:3.11.4'
exclude group: 'com.google.guava' }
}
compile 'com.google.guava:guava:28.1-jre' testImplementation 'org.apache.commons:commons-lang3:3.12.0'
testCompile 'org.apache.commons:commons-lang3:3.9' 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 {
exclude '**/tmp/*'
} }
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,32 +2,27 @@ package jadx.api;
public final class CodePosition { public final class CodePosition {
private final JavaNode node;
private final int line; private final int line;
private final int offset; private final int offset;
private final int pos;
public CodePosition(JavaNode node, int line, int offset) { public CodePosition(int line, int offset, int pos) {
this.node = node;
this.line = line; this.line = line;
this.offset = offset; this.offset = offset;
this.pos = pos;
} }
public CodePosition(int line) {
this(line, 0, -1);
}
@Deprecated
public CodePosition(int line, int offset) { public CodePosition(int line, int offset) {
this.node = null; this(line, offset, -1);
this.line = line;
this.offset = offset;
} }
public JavaNode getNode() { public int getPos() {
return node; return pos;
}
public JavaClass getJavaClass() {
JavaClass parent = node.getDeclaringClass();
if (parent == null && node instanceof JavaClass) {
return (JavaClass) node;
}
return parent;
} }
public int getLine() { public int getLine() {
@@ -62,8 +57,8 @@ public final class CodePosition {
if (offset != 0) { if (offset != 0) {
sb.append(':').append(offset); sb.append(':').append(offset);
} }
if (node != null) { if (pos > 0) {
sb.append(' ').append(node); sb.append('@').append(pos);
} }
return sb.toString(); return sb.toString();
} }
@@ -0,0 +1,14 @@
package jadx.api;
public enum CommentsLevel {
NONE,
USER_ONLY,
ERROR,
WARN,
INFO,
DEBUG;
public boolean filter(CommentsLevel limit) {
return this.ordinal() <= limit.ordinal();
}
}
@@ -6,6 +6,8 @@ public interface ICodeCache {
void add(String clsFullName, ICodeInfo codeInfo); void add(String clsFullName, ICodeInfo codeInfo);
void remove(String clsFullName);
@Nullable @Nullable
ICodeInfo get(String clsFullName); ICodeInfo get(String clsFullName);
} }
@@ -2,7 +2,12 @@ package jadx.api;
import java.util.Map; import java.util.Map;
import jadx.api.impl.SimpleCodeInfo;
public interface ICodeInfo { public interface ICodeInfo {
ICodeInfo EMPTY = new SimpleCodeInfo("");
String getCodeStr(); String getCodeStr();
Map<Integer, Integer> getLineMapping(); Map<Integer, Integer> getLineMapping();
@@ -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);
}
+147 -5
View File
@@ -6,8 +6,12 @@ import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.data.ICodeData;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache; import jadx.api.impl.InMemoryCodeCache;
public class JadxArgs { public class JadxArgs {
@@ -25,6 +29,7 @@ public class JadxArgs {
private File outDirRes; private File outDirRes;
private ICodeCache codeCache = new InMemoryCodeCache(); private ICodeCache codeCache = new InMemoryCodeCache();
private Function<JadxArgs, ICodeWriter> codeWriterProvider = AnnotatedCodeWriter::new;
private int threadsCount = DEFAULT_THREADS_COUNT; private int threadsCount = DEFAULT_THREADS_COUNT;
@@ -36,7 +41,10 @@ public class JadxArgs {
private boolean useImports = true; private boolean useImports = true;
private boolean debugInfo = true; private boolean debugInfo = true;
private boolean insertDebugLines = false;
private boolean extractFinally = true;
private boolean inlineAnonymousClasses = true; private boolean inlineAnonymousClasses = true;
private boolean inlineMethods = true;
private boolean skipResources = false; private boolean skipResources = false;
private boolean skipSources = false; private boolean skipSources = false;
@@ -47,8 +55,11 @@ public class JadxArgs {
private Predicate<String> classFilter = null; private Predicate<String> classFilter = null;
private boolean deobfuscationOn = false; private boolean deobfuscationOn = false;
private boolean deobfuscationForceSave = false;
private boolean useSourceNameAsClassAlias = false; private boolean useSourceNameAsClassAlias = false;
private boolean parseKotlinMetadata = false;
private File deobfuscationMapFile = null;
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
private int deobfuscationMinLength = 0; private int deobfuscationMinLength = 0;
private int deobfuscationMaxLength = Integer.MAX_VALUE; private int deobfuscationMaxLength = Integer.MAX_VALUE;
@@ -72,6 +83,23 @@ public class JadxArgs {
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA; private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
private ICodeData codeData;
private CommentsLevel commentsLevel = CommentsLevel.INFO;
private boolean useDxInput = false;
public enum UseKotlinMethodsForVarNames {
DISABLE, APPLY, APPLY_AND_HIDE
}
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
/**
* Don't save files (can be using for performance testing)
*/
private boolean skipFilesSave = false;
public JadxArgs() { public JadxArgs() {
// use default options // use default options
} }
@@ -123,7 +151,7 @@ public class JadxArgs {
} }
public void setThreadsCount(int threadsCount) { public void setThreadsCount(int threadsCount) {
this.threadsCount = threadsCount; this.threadsCount = Math.max(1, threadsCount); // make sure threadsCount >= 1
} }
public boolean isCfgOutput() { public boolean isCfgOutput() {
@@ -174,6 +202,14 @@ public class JadxArgs {
this.debugInfo = debugInfo; this.debugInfo = debugInfo;
} }
public boolean isInsertDebugLines() {
return insertDebugLines;
}
public void setInsertDebugLines(boolean insertDebugLines) {
this.insertDebugLines = insertDebugLines;
}
public boolean isInlineAnonymousClasses() { public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses; return inlineAnonymousClasses;
} }
@@ -182,6 +218,22 @@ public class JadxArgs {
this.inlineAnonymousClasses = inlineAnonymousClasses; this.inlineAnonymousClasses = inlineAnonymousClasses;
} }
public boolean isInlineMethods() {
return inlineMethods;
}
public void setInlineMethods(boolean inlineMethods) {
this.inlineMethods = inlineMethods;
}
public boolean isExtractFinally() {
return extractFinally;
}
public void setExtractFinally(boolean extractFinally) {
this.extractFinally = extractFinally;
}
public boolean isSkipResources() { public boolean isSkipResources() {
return skipResources; return skipResources;
} }
@@ -214,12 +266,24 @@ public class JadxArgs {
this.deobfuscationOn = deobfuscationOn; this.deobfuscationOn = deobfuscationOn;
} }
@Deprecated
public boolean isDeobfuscationForceSave() { public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave; return deobfuscationMapFileMode == DeobfuscationMapFileMode.OVERWRITE;
} }
@Deprecated
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) { public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
this.deobfuscationForceSave = deobfuscationForceSave; if (deobfuscationForceSave) {
this.deobfuscationMapFileMode = DeobfuscationMapFileMode.OVERWRITE;
}
}
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
return deobfuscationMapFileMode;
}
public void setDeobfuscationMapFileMode(DeobfuscationMapFileMode deobfuscationMapFileMode) {
this.deobfuscationMapFileMode = deobfuscationMapFileMode;
} }
public boolean isUseSourceNameAsClassAlias() { public boolean isUseSourceNameAsClassAlias() {
@@ -230,6 +294,14 @@ public class JadxArgs {
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias; this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
} }
public boolean isParseKotlinMetadata() {
return parseKotlinMetadata;
}
public void setParseKotlinMetadata(boolean parseKotlinMetadata) {
this.parseKotlinMetadata = parseKotlinMetadata;
}
public int getDeobfuscationMinLength() { public int getDeobfuscationMinLength() {
return deobfuscationMinLength; return deobfuscationMinLength;
} }
@@ -246,6 +318,14 @@ public class JadxArgs {
this.deobfuscationMaxLength = deobfuscationMaxLength; this.deobfuscationMaxLength = deobfuscationMaxLength;
} }
public File getDeobfuscationMapFile() {
return deobfuscationMapFile;
}
public void setDeobfuscationMapFile(File deobfuscationMapFile) {
this.deobfuscationMapFile = deobfuscationMapFile;
}
public boolean isEscapeUnicode() { public boolean isEscapeUnicode() {
return escapeUnicode; return escapeUnicode;
} }
@@ -318,6 +398,14 @@ public class JadxArgs {
} }
} }
public void setRenameFlags(Set<RenameEnum> renameFlags) {
this.renameFlags = renameFlags;
}
public Set<RenameEnum> getRenameFlags() {
return renameFlags;
}
public OutputFormatEnum getOutputFormat() { public OutputFormatEnum getOutputFormat() {
return outputFormat; return outputFormat;
} }
@@ -338,6 +426,54 @@ public class JadxArgs {
this.codeCache = codeCache; this.codeCache = codeCache;
} }
public Function<JadxArgs, ICodeWriter> getCodeWriterProvider() {
return codeWriterProvider;
}
public void setCodeWriterProvider(Function<JadxArgs, ICodeWriter> codeWriterProvider) {
this.codeWriterProvider = codeWriterProvider;
}
public ICodeData getCodeData() {
return codeData;
}
public void setCodeData(ICodeData codeData) {
this.codeData = codeData;
}
public CommentsLevel getCommentsLevel() {
return commentsLevel;
}
public void setCommentsLevel(CommentsLevel commentsLevel) {
this.commentsLevel = commentsLevel;
}
public boolean isUseDxInput() {
return useDxInput;
}
public void setUseDxInput(boolean useDxInput) {
this.useDxInput = useDxInput;
}
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public void setUseKotlinMethodsForVarNames(UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
}
public boolean isSkipFilesSave() {
return skipFilesSave;
}
public void setSkipFilesSave(boolean skipFilesSave) {
this.skipFilesSave = skipFilesSave;
}
@Override @Override
public String toString() { public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -353,8 +489,11 @@ public class JadxArgs {
+ ", skipResources=" + skipResources + ", skipResources=" + skipResources
+ ", skipSources=" + skipSources + ", skipSources=" + skipSources
+ ", deobfuscationOn=" + deobfuscationOn + ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationForceSave=" + deobfuscationForceSave + ", deobfuscationMapFile=" + deobfuscationMapFile
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias + ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
+ ", deobfuscationMinLength=" + deobfuscationMinLength + ", deobfuscationMinLength=" + deobfuscationMinLength
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength + ", deobfuscationMaxLength=" + deobfuscationMaxLength
+ ", escapeUnicode=" + escapeUnicode + ", escapeUnicode=" + escapeUnicode
@@ -364,7 +503,10 @@ public class JadxArgs {
+ ", fsCaseSensitive=" + fsCaseSensitive + ", fsCaseSensitive=" + fsCaseSensitive
+ ", renameFlags=" + renameFlags + ", renameFlags=" + renameFlags
+ ", outputFormat=" + outputFormat + ", outputFormat=" + outputFormat
+ ", commentsLevel=" + commentsLevel
+ ", codeCache=" + codeCache + ", codeCache=" + codeCache
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
+ ", useDxInput=" + useDxInput
+ '}'; + '}';
} }
} }
@@ -27,14 +27,11 @@ public class JadxArgsValidator {
if (inputFiles.isEmpty()) { if (inputFiles.isEmpty()) {
throw new JadxArgsValidateException("Please specify input file"); throw new JadxArgsValidateException("Please specify input file");
} }
if (inputFiles.size() > 1) { for (File inputFile : inputFiles) {
for (File inputFile : inputFiles) { String fileName = inputFile.getName();
String fileName = inputFile.getName(); if (fileName.startsWith("--")) {
if (fileName.startsWith("--")) { throw new JadxArgsValidateException("Unknown argument: " + fileName);
throw new JadxArgsValidateException("Unknown argument: " + fileName);
}
} }
throw new JadxArgsValidateException("Only one input file supported");
} }
for (File file : inputFiles) { for (File file : inputFiles) {
checkFile(file); checkFile(file);
@@ -88,9 +85,6 @@ public class JadxArgsValidator {
if (!file.exists()) { if (!file.exists()) {
throw new JadxArgsValidateException("File not found " + file.getAbsolutePath()); throw new JadxArgsValidateException("File not found " + file.getAbsolutePath());
} }
if (file.isDirectory()) {
throw new JadxArgsValidateException("Expected file but found directory instead: " + file.getAbsolutePath());
}
} }
private static void checkDir(File dir, String desc) { private static void checkDir(File dir, String desc) {
@@ -1,22 +1,35 @@
package jadx.api; package jadx.api;
import java.io.Closeable;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.data.annotations.VarRef;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.core.Jadx; import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.attributes.nodes.LineAttrNode;
@@ -26,9 +39,13 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.SaveCode; import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject; import jadx.core.export.ExportGradleProject;
import jadx.core.utils.DecompilerScheduler;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile; import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.BinaryXMLParser; import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ProtoXMLParser;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResourcesSaver; import jadx.core.xmlgen.ResourcesSaver;
/** /**
@@ -36,13 +53,14 @@ import jadx.core.xmlgen.ResourcesSaver;
* *
* <pre> * <pre>
* <code> * <code>
*
* JadxArgs args = new JadxArgs(); * JadxArgs args = new JadxArgs();
* args.getInputFiles().add(new File("test.apk")); * args.getInputFiles().add(new File("test.apk"));
* args.setOutDir(new File("jadx-test-output")); * args.setOutDir(new File("jadx-test-output"));
* * try (JadxDecompiler jadx = new JadxDecompiler(args)) {
* JadxDecompiler jadx = new JadxDecompiler(args); * jadx.load();
* jadx.load(); * jadx.save();
* jadx.save(); * }
* </code> * </code>
* </pre> * </pre>
* <p> * <p>
@@ -50,27 +68,32 @@ import jadx.core.xmlgen.ResourcesSaver;
* *
* <pre> * <pre>
* <code> * <code>
*
* for(JavaClass cls : jadx.getClasses()) { * for(JavaClass cls : jadx.getClasses()) {
* System.out.println(cls.getCode()); * System.out.println(cls.getCode());
* } * }
* </code> * </code>
* </pre> * </pre>
*/ */
public final class JadxDecompiler { public final class JadxDecompiler implements Closeable {
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class); private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
private JadxArgs args; private final JadxArgs args;
private List<InputFile> inputFiles; private final JadxPluginManager pluginManager = new JadxPluginManager();
private final List<ILoadResult> loadedInputs = new ArrayList<>();
private RootNode root; private RootNode root;
private List<JavaClass> classes; private List<JavaClass> classes;
private List<ResourceFile> resources; private List<ResourceFile> resources;
private BinaryXMLParser xmlParser; private BinaryXMLParser binaryXmlParser;
private ProtoXMLParser protoXmlParser;
private Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>(); private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
private Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>(); private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>(); private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
public JadxDecompiler() { public JadxDecompiler() {
this(new JadxArgs()); this(new JadxArgs());
@@ -84,50 +107,102 @@ public final class JadxDecompiler {
reset(); reset();
JadxArgsValidator.validate(args); JadxArgsValidator.validate(args);
LOG.info("loading ..."); LOG.info("loading ...");
loadPlugins(args);
inputFiles = loadFiles(args.getInputFiles()); loadInputFiles();
root = new RootNode(args); root = new RootNode(args);
root.load(inputFiles); root.loadClasses(loadedInputs);
root.initClassPath(); root.initClassPath();
root.loadResources(getResources()); root.loadResources(getResources());
root.runPreDecompileStage();
root.initPasses(); root.initPasses();
} }
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(inputFiles);
if (loadResult != null && !loadResult.isEmpty()) {
loadedInputs.add(loadResult);
}
}
LOG.debug("Loaded using {} inputs plugin", loadedInputs.size());
}
private void reset() { private void reset() {
root = null; root = null;
classes = null; classes = null;
resources = null; resources = null;
xmlParser = null; binaryXmlParser = null;
protoXmlParser = null;
classesMap.clear(); classesMap.clear();
methodsMap.clear(); methodsMap.clear();
fieldsMap.clear(); fieldsMap.clear();
closeInputs();
}
private void closeInputs() {
loadedInputs.forEach(load -> {
try {
load.close();
} catch (Exception e) {
LOG.error("Failed to close input", e);
}
});
loadedInputs.clear();
}
@Override
public void close() {
reset();
}
private void loadPlugins(JadxArgs args) {
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
pluginManager.load();
if (LOG.isDebugEnabled()) {
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
p -> p.getPluginInfo().getPluginId()));
}
}
public void registerPlugin(JadxPlugin plugin) {
pluginManager.register(plugin);
} }
public static String getVersion() { public static String getVersion() {
return Jadx.getVersion(); return Jadx.getVersion();
} }
private List<InputFile> loadFiles(List<File> files) {
if (files.isEmpty()) {
throw new JadxRuntimeException("Empty file list");
}
List<InputFile> filesList = new ArrayList<>();
for (File file : files) {
try {
InputFile.addFilesFrom(file, filesList, args.isSkipSources());
} catch (Exception e) {
throw new JadxRuntimeException("Error load file: " + file, e);
}
}
return filesList;
}
public void save() { public void save() {
save(!args.isSkipSources(), !args.isSkipResources()); save(!args.isSkipSources(), !args.isSkipResources());
} }
public interface ProgressListener {
void progress(long done, long total);
}
@SuppressWarnings("BusyWait")
public void save(int intervalInMillis, ProgressListener listener) {
ThreadPoolExecutor ex = (ThreadPoolExecutor) getSaveExecutor();
ex.shutdown();
try {
long total = ex.getTaskCount();
while (ex.isTerminating()) {
long done = ex.getCompletedTaskCount();
listener.progress(done, total);
Thread.sleep(intervalInMillis);
}
} catch (InterruptedException e) {
LOG.error("Save interrupted", e);
Thread.currentThread().interrupt();
}
}
public void saveSources() { public void saveSources() {
save(true, false); save(true, false);
} }
@@ -151,20 +226,44 @@ public final class JadxDecompiler {
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources()); return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
} }
public List<Runnable> getSaveTasks() {
return getSaveTasks(!args.isSkipSources(), !args.isSkipResources());
}
private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) { private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) {
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
LOG.info("processing ...");
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
List<Runnable> tasks = getSaveTasks(saveSources, saveResources);
tasks.forEach(executor::execute);
return executor;
}
private List<Runnable> getSaveTasks(boolean saveSources, boolean saveResources) {
if (root == null) { if (root == null) {
throw new JadxRuntimeException("No loaded files"); throw new JadxRuntimeException("No loaded files");
} }
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
LOG.info("processing ...");
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
File sourcesOutDir; File sourcesOutDir;
File resOutDir; File resOutDir;
if (args.isExportAsGradleProject()) { if (args.isExportAsGradleProject()) {
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir()); ResourceFile androidManifest = resources.stream()
.filter(resourceFile -> resourceFile.getType() == ResourceType.MANIFEST)
.findFirst()
.orElseThrow(IllegalStateException::new);
ResContainer strings = resources.stream()
.filter(resourceFile -> resourceFile.getType() == ResourceType.ARSC)
.findFirst()
.orElseThrow(IllegalStateException::new)
.loadContent()
.getSubFiles()
.stream()
.filter(resContainer -> resContainer.getFileName().contains("strings.xml"))
.findFirst()
.orElseThrow(IllegalStateException::new);
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir(), androidManifest, strings);
export.init(); export.init();
sourcesOutDir = export.getSrcOutDir(); sourcesOutDir = export.getSrcOutDir();
resOutDir = export.getResOutDir(); resOutDir = export.getResOutDir();
@@ -172,36 +271,60 @@ public final class JadxDecompiler {
sourcesOutDir = args.getOutDirSrc(); sourcesOutDir = args.getOutDirSrc();
resOutDir = args.getOutDirRes(); resOutDir = args.getOutDirRes();
} }
List<Runnable> tasks = new ArrayList<>();
// save resources first because decompilation can hang or fail
if (saveResources) { if (saveResources) {
appendResourcesSave(executor, resOutDir); appendResourcesSaveTasks(tasks, resOutDir);
} }
if (saveSources) { if (saveSources) {
appendSourcesSave(executor, sourcesOutDir); appendSourcesSave(tasks, sourcesOutDir);
} }
return executor; return tasks;
} }
private void appendResourcesSave(ExecutorService executor, File outDir) { private void appendResourcesSaveTasks(List<Runnable> tasks, File outDir) {
if (args.isSkipFilesSave()) {
return;
}
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
for (ResourceFile resourceFile : getResources()) { for (ResourceFile resourceFile : getResources()) {
executor.execute(new ResourcesSaver(outDir, resourceFile)); if (resourceFile.getType() != ResourceType.ARSC
&& inputFileNames.contains(resourceFile.getOriginalName())) {
// ignore resource made from input file
continue;
}
tasks.add(new ResourcesSaver(outDir, resourceFile));
} }
} }
private void appendSourcesSave(ExecutorService executor, File outDir) { private void appendSourcesSave(List<Runnable> tasks, File outDir) {
final Predicate<String> classFilter = args.getClassFilter(); Predicate<String> classFilter = args.getClassFilter();
for (JavaClass cls : getClasses()) { List<JavaClass> classes = getClasses();
List<JavaClass> processQueue = new ArrayList<>(classes.size());
for (JavaClass cls : classes) {
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) { if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
continue; continue;
} }
if (classFilter != null && !classFilter.test(cls.getFullName())) { if (classFilter != null && !classFilter.test(cls.getFullName())) {
continue; continue;
} }
executor.execute(() -> { processQueue.add(cls);
try { }
ICodeInfo code = cls.getCodeInfo(); List<List<JavaClass>> batches;
SaveCode.save(outDir, cls.getClassNode(), code); try {
} catch (Exception e) { batches = decompileScheduler.buildBatches(processQueue);
LOG.error("Error saving class: {}", cls.getFullName(), e); } catch (Exception e) {
throw new JadxRuntimeException("Decompilation batches build failed", e);
}
for (List<JavaClass> decompileBatch : batches) {
tasks.add(() -> {
for (JavaClass cls : decompileBatch) {
try {
ICodeInfo code = cls.getCodeInfo();
SaveCode.save(outDir, cls.getClassNode(), code);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls, e);
}
} }
}); });
} }
@@ -232,7 +355,7 @@ public final class JadxDecompiler {
if (root == null) { if (root == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
resources = new ResourcesLoader(this).load(inputFiles); resources = new ResourcesLoader(this).load();
} }
return resources; return resources;
} }
@@ -281,15 +404,25 @@ public final class JadxDecompiler {
root.getErrorsCounter().printReport(); root.getErrorsCounter().printReport();
} }
RootNode getRoot() { /**
* Internal API. Not Stable!
*/
public RootNode getRoot() {
return root; return root;
} }
synchronized BinaryXMLParser getXmlParser() { synchronized BinaryXMLParser getBinaryXmlParser() {
if (xmlParser == null) { if (binaryXmlParser == null) {
xmlParser = new BinaryXMLParser(root); binaryXmlParser = new BinaryXMLParser(root);
} }
return xmlParser; return binaryXmlParser;
}
synchronized ProtoXMLParser getProtoXmlParser() {
if (protoXmlParser == null) {
protoXmlParser = new ProtoXMLParser(root);
}
return protoXmlParser;
} }
private void loadJavaClass(JavaClass javaClass) { private void loadJavaClass(JavaClass javaClass) {
@@ -300,12 +433,33 @@ public final class JadxDecompiler {
classesMap.put(innerCls.getClassNode(), innerCls); classesMap.put(innerCls.getClassNode(), innerCls);
loadJavaClass(innerCls); loadJavaClass(innerCls);
} }
for (JavaClass inlinedCls : javaClass.getInlinedClasses()) {
classesMap.put(inlinedCls.getClassNode(), inlinedCls);
loadJavaClass(inlinedCls);
}
}
/**
* Get JavaClass by ClassNode without loading and decompilation
*/
JavaClass convertClassNode(ClassNode cls) {
return classesMap.compute(cls, (node, prevJavaCls) -> {
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) {
// keep previous variable
return prevJavaCls;
}
if (cls.isInner()) {
return new JavaClass(cls, convertClassNode(cls.getParentClass()));
}
return new JavaClass(cls, this);
});
} }
@Nullable("For not generated classes") @Nullable("For not generated classes")
private JavaClass getJavaClassByNode(ClassNode cls) { @ApiStatus.Internal
public JavaClass getJavaClassByNode(ClassNode cls) {
JavaClass javaClass = classesMap.get(cls); JavaClass javaClass = classesMap.get(cls);
if (javaClass != null) { if (javaClass != null && javaClass.getClassNode() == cls) {
return javaClass; return javaClass;
} }
// load parent class if inner // load parent class if inner
@@ -335,9 +489,12 @@ public final class JadxDecompiler {
@Nullable @Nullable
private JavaMethod getJavaMethodByNode(MethodNode mth) { private JavaMethod getJavaMethodByNode(MethodNode mth) {
JavaMethod javaMethod = methodsMap.get(mth); JavaMethod javaMethod = methodsMap.get(mth);
if (javaMethod != null) { if (javaMethod != null && javaMethod.getMethodNode() == mth) {
return javaMethod; return javaMethod;
} }
if (mth.contains(AFlag.DONT_GENERATE)) {
return null;
}
// parent class not loaded yet // parent class not loaded yet
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass()); JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
if (javaClass == null) { if (javaClass == null) {
@@ -357,7 +514,7 @@ public final class JadxDecompiler {
@Nullable @Nullable
private JavaField getJavaFieldByNode(FieldNode fld) { private JavaField getJavaFieldByNode(FieldNode fld) {
JavaField javaField = fieldsMap.get(fld); JavaField javaField = fieldsMap.get(fld);
if (javaField != null) { if (javaField != null && javaField.getFieldNode() == fld) {
return javaField; return javaField;
} }
// parent class not loaded yet // parent class not loaded yet
@@ -376,8 +533,60 @@ public final class JadxDecompiler {
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld); throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
} }
@Nullable
public JavaClass searchJavaClassByOrigFullName(String fullName) {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
.findFirst()
.map(this::getJavaClassByNode)
.orElse(null);
}
@Nullable
public ClassNode searchClassNodeByOrigFullName(String fullName) {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
.findFirst()
.orElse(null);
}
// returns parent if class contains DONT_GENERATE flag.
@Nullable
public JavaClass searchJavaClassOrItsParentByOrigFullName(String fullName) {
ClassNode node = getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
.findFirst()
.orElse(null);
if (node != null) {
if (node.contains(AFlag.DONT_GENERATE)) {
return getJavaClassByNode(node.getTopParentClass());
} else {
return getJavaClassByNode(node);
}
}
return null;
}
@Nullable
public JavaClass searchJavaClassByAliasFullName(String fullName) {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
.findFirst()
.map(this::getJavaClassByNode)
.orElse(null);
}
@Nullable @Nullable
JavaNode convertNode(Object obj) { JavaNode convertNode(Object obj) {
if (obj instanceof VarRef) {
VarRef varRef = (VarRef) obj;
MethodNode mthNode = varRef.getMth();
JavaMethod mth = getJavaMethodByNode(mthNode);
if (mth == null) {
return null;
}
return new JavaVariable(mth, varRef);
}
if (!(obj instanceof LineAttrNode)) { if (!(obj instanceof LineAttrNode)) {
return null; return null;
} }
@@ -386,7 +595,7 @@ public final class JadxDecompiler {
return null; return null;
} }
if (obj instanceof ClassNode) { if (obj instanceof ClassNode) {
return getJavaClassByNode((ClassNode) obj); return convertClassNode((ClassNode) obj);
} }
if (obj instanceof MethodNode) { if (obj instanceof MethodNode) {
return getJavaMethodByNode(((MethodNode) obj)); return getJavaMethodByNode(((MethodNode) obj));
@@ -397,6 +606,30 @@ public final class JadxDecompiler {
throw new JadxRuntimeException("Unexpected node type: " + obj); throw new JadxRuntimeException("Unexpected node type: " + obj);
} }
// TODO: make interface for all nodes in code annotations and add common method instead this
Object getInternalNode(JavaNode javaNode) {
if (javaNode instanceof JavaClass) {
return ((JavaClass) javaNode).getClassNode();
}
if (javaNode instanceof JavaMethod) {
return ((JavaMethod) javaNode).getMethodNode();
}
if (javaNode instanceof JavaField) {
return ((JavaField) javaNode).getFieldNode();
}
if (javaNode instanceof JavaVariable) {
return ((JavaVariable) javaNode).getVarRef();
}
throw new JadxRuntimeException("Unexpected node type: " + javaNode);
}
List<JavaNode> convertNodes(Collection<?> nodesList) {
return nodesList.stream()
.map(this::convertNode)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@Nullable @Nullable
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) { public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
Map<CodePosition, Object> map = codeInfo.getAnnotations(); Map<CodePosition, Object> map = codeInfo.getAnnotations();
@@ -418,13 +651,25 @@ public final class JadxDecompiler {
if (defLine == 0) { if (defLine == 0) {
return null; return null;
} }
return new CodePosition(jCls, defLine, 0); return new CodePosition(defLine, 0, javaNode.getDefPos());
}
public void reloadCodeData() {
root.notifyCodeDataListeners();
} }
public JadxArgs getArgs() { public JadxArgs getArgs() {
return args; return args;
} }
public JadxPluginManager getPluginManager() {
return pluginManager;
}
public IDecompileScheduler getDecompileScheduler() {
return decompileScheduler;
}
@Override @Override
public String toString() { public String toString() {
return "jadx decompiler " + getVersion(); return "jadx decompiler " + getVersion();
+103 -11
View File
@@ -7,9 +7,12 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
@@ -22,6 +25,7 @@ public final class JavaClass implements JavaNode {
private final JavaClass parent; private final JavaClass parent;
private List<JavaClass> innerClasses = Collections.emptyList(); private List<JavaClass> innerClasses = Collections.emptyList();
private List<JavaClass> inlinedClasses = Collections.emptyList();
private List<JavaField> fields = Collections.emptyList(); private List<JavaField> fields = Collections.emptyList();
private List<JavaMethod> methods = Collections.emptyList(); private List<JavaMethod> methods = Collections.emptyList();
private boolean listsLoaded; private boolean listsLoaded;
@@ -57,45 +61,69 @@ public final class JavaClass implements JavaNode {
cls.decompile(); cls.decompile();
} }
public synchronized String getSmali() { public synchronized void reload() {
return cls.getSmali();
}
public synchronized void unload() {
cls.unload();
listsLoaded = false; listsLoaded = false;
cls.reloadCode();
} }
public void unload() {
listsLoaded = false;
cls.unloadCode();
}
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() { public ClassNode getClassNode() {
return cls; return cls;
} }
private void loadLists() { private synchronized void loadLists() {
if (listsLoaded) { if (listsLoaded) {
return; return;
} }
listsLoaded = true; listsLoaded = true;
decompile(); decompile();
JadxDecompiler rootDecompiler = getRootDecompiler();
int inClsCount = cls.getInnerClasses().size(); int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) { if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inClsCount); List<JavaClass> list = new ArrayList<>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) { for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.contains(AFlag.DONT_GENERATE)) { if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(inner, this); JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists(); javaClass.loadLists();
list.add(javaClass); list.add(javaClass);
} }
} }
this.innerClasses = Collections.unmodifiableList(list); this.innerClasses = Collections.unmodifiableList(list);
} }
int inlinedClsCount = cls.getInlinedClasses().size();
if (inlinedClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
for (ClassNode inner : cls.getInlinedClasses()) {
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists();
list.add(javaClass);
}
this.inlinedClasses = Collections.unmodifiableList(list);
}
int fieldsCount = cls.getFields().size(); int fieldsCount = cls.getFields().size();
if (fieldsCount != 0) { if (fieldsCount != 0) {
List<JavaField> flds = new ArrayList<>(fieldsCount); List<JavaField> flds = new ArrayList<>(fieldsCount);
for (FieldNode f : cls.getFields()) { for (FieldNode f : cls.getFields()) {
if (!f.contains(AFlag.DONT_GENERATE)) { if (!f.contains(AFlag.DONT_GENERATE)) {
JavaField javaField = new JavaField(f, this); JavaField javaField = new JavaField(this, f);
flds.add(javaField); flds.add(javaField);
} }
} }
@@ -116,14 +144,14 @@ public final class JavaClass implements JavaNode {
} }
} }
private JadxDecompiler getRootDecompiler() { protected JadxDecompiler getRootDecompiler() {
if (parent != null) { if (parent != null) {
return parent.getRootDecompiler(); return parent.getRootDecompiler();
} }
return decompiler; return decompiler;
} }
private Map<CodePosition, Object> getCodeAnnotations() { public Map<CodePosition, Object> getCodeAnnotations() {
ICodeInfo code = getCodeInfo(); ICodeInfo code = getCodeInfo();
if (code == null) { if (code == null) {
return Collections.emptyMap(); return Collections.emptyMap();
@@ -131,6 +159,10 @@ public final class JavaClass implements JavaNode {
return code.getAnnotations(); return code.getAnnotations();
} }
public Object getAnnotationAt(CodePosition pos) {
return getCodeAnnotations().get(pos);
}
public Map<CodePosition, JavaNode> getUsageMap() { public Map<CodePosition, JavaNode> getUsageMap() {
Map<CodePosition, Object> map = getCodeAnnotations(); Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty() || decompiler == null) { if (map.isEmpty() || decompiler == null) {
@@ -148,6 +180,28 @@ public final class JavaClass implements JavaNode {
return resultMap; return resultMap;
} }
public List<CodePosition> getUsageFor(JavaNode javaNode) {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty() || decompiler == null) {
return Collections.emptyList();
}
Object internalNode = getRootDecompiler().getInternalNode(javaNode);
List<CodePosition> result = new ArrayList<>();
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
if (internalNode.equals(obj)) {
result.add(codePosition);
}
}
return result;
}
@Override
public List<JavaNode> getUseIn() {
return getRootDecompiler().convertNodes(cls.getUseIn());
}
@Nullable @Nullable
@Deprecated @Deprecated
public JavaNode getJavaNodeAtPosition(int line, int offset) { public JavaNode getJavaNodeAtPosition(int line, int offset) {
@@ -174,6 +228,10 @@ public final class JavaClass implements JavaNode {
return cls.getFullName(); return cls.getFullName();
} }
public String getRawName() {
return cls.getRawName();
}
public String getPackage() { public String getPackage() {
return cls.getPackage(); return cls.getPackage();
} }
@@ -185,9 +243,19 @@ public final class JavaClass implements JavaNode {
@Override @Override
public JavaClass getTopParentClass() { public JavaClass getTopParentClass() {
if (cls.contains(AType.ANONYMOUS_CLASS)) {
// moved to usage class
return getParentForAnonymousClass();
}
return parent == null ? this : parent.getTopParentClass(); return parent == null ? this : parent.getTopParentClass();
} }
private JavaClass getParentForAnonymousClass() {
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
ClassNode topParentClass = attr.getOuterCls().getTopParentClass();
return getRootDecompiler().convertClassNode(topParentClass);
}
public AccessInfo getAccessInfo() { public AccessInfo getAccessInfo() {
return cls.getAccessFlags(); return cls.getAccessFlags();
} }
@@ -197,6 +265,11 @@ public final class JavaClass implements JavaNode {
return innerClasses; return innerClasses;
} }
public List<JavaClass> getInlinedClasses() {
loadLists();
return inlinedClasses;
}
public List<JavaField> getFields() { public List<JavaField> getFields() {
loadLists(); loadLists();
return fields; return fields;
@@ -207,11 +280,30 @@ public final class JavaClass implements JavaNode {
return methods; return methods;
} }
@Nullable
public JavaMethod searchMethodByShortId(String shortId) {
MethodNode methodNode = cls.searchMethodByShortId(shortId);
if (methodNode == null) {
return null;
}
return new JavaMethod(this, methodNode);
}
@Override
public void removeAlias() {
this.cls.getClassInfo().removeAlias();
}
@Override @Override
public int getDecompiledLine() { public int getDecompiledLine() {
return cls.getDecompiledLine(); return cls.getDecompiledLine();
} }
@Override
public int getDefPos() {
return cls.getDefPosition();
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls); return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
@@ -1,5 +1,9 @@
package jadx.api; package jadx.api;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
@@ -9,7 +13,7 @@ public final class JavaField implements JavaNode {
private final FieldNode field; private final FieldNode field;
private final JavaClass parent; private final JavaClass parent;
JavaField(FieldNode f, JavaClass cls) { JavaField(JavaClass cls, FieldNode f) {
this.field = f; this.field = f;
this.parent = cls; this.parent = cls;
} }
@@ -24,6 +28,10 @@ public final class JavaField implements JavaNode {
return parent.getFullName() + '.' + getName(); return parent.getFullName() + '.' + getName();
} }
public String getRawName() {
return field.getName();
}
@Override @Override
public JavaClass getDeclaringClass() { public JavaClass getDeclaringClass() {
return parent; return parent;
@@ -39,14 +47,34 @@ public final class JavaField implements JavaNode {
} }
public ArgType getType() { public ArgType getType() {
return ArgType.tryToResolveClassAlias(field.dex(), field.getType()); return ArgType.tryToResolveClassAlias(field.root(), field.getType());
} }
@Override
public int getDecompiledLine() { public int getDecompiledLine() {
return field.getDecompiledLine(); return field.getDecompiledLine();
} }
FieldNode getFieldNode() { @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; return field;
} }
@@ -2,7 +2,12 @@ package jadx.api;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.jetbrains.annotations.ApiStatus;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
@@ -47,19 +52,29 @@ public final class JavaMethod implements JavaNode {
return Collections.emptyList(); return Collections.emptyList();
} }
List<ArgType> arguments = mth.getArgTypes(); List<ArgType> arguments = mth.getArgTypes();
if (arguments == null) {
arguments = infoArgTypes;
}
return Utils.collectionMap(arguments, return Utils.collectionMap(arguments,
type -> ArgType.tryToResolveClassAlias(mth.dex(), type)); type -> ArgType.tryToResolveClassAlias(mth.root(), type));
} }
public ArgType getReturnType() { public ArgType getReturnType() {
ArgType retType = mth.getReturnType(); ArgType retType = mth.getReturnType();
if (retType == null) { return ArgType.tryToResolveClassAlias(mth.root(), retType);
retType = mth.getMethodInfo().getReturnType(); }
@Override
public List<JavaNode> getUseIn() {
return getDeclaringClass().getRootDecompiler().convertNodes(mth.getUseIn());
}
public List<JavaMethod> getOverrideRelatedMethods() {
MethodOverrideAttr ovrdAttr = mth.get(AType.METHOD_OVERRIDE);
if (ovrdAttr == null) {
return Collections.emptyList();
} }
return ArgType.tryToResolveClassAlias(mth.dex(), retType); JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
return ovrdAttr.getRelatedMthNodes().stream()
.map(m -> ((JavaMethod) decompiler.convertNode(m)))
.collect(Collectors.toList());
} }
public boolean isConstructor() { public boolean isConstructor() {
@@ -70,11 +85,26 @@ public final class JavaMethod implements JavaNode {
return mth.getMethodInfo().isClassInit(); return mth.getMethodInfo().isClassInit();
} }
@Override
public int getDecompiledLine() { public int getDecompiledLine() {
return mth.getDecompiledLine(); return mth.getDecompiledLine();
} }
MethodNode getMethodNode() { @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; return mth;
} }
@@ -1,5 +1,7 @@
package jadx.api; package jadx.api;
import java.util.List;
public interface JavaNode { public interface JavaNode {
String getName(); String getName();
@@ -11,4 +13,11 @@ public interface JavaNode {
JavaClass getTopParentClass(); JavaClass getTopParentClass();
int getDecompiledLine(); int getDecompiledLine();
int getDefPos();
List<JavaNode> getUseIn();
default void removeAlias() {
}
} }
@@ -1,5 +1,6 @@
package jadx.api; package jadx.api;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -43,6 +44,16 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return 0; return 0;
} }
@Override
public int getDefPos() {
return 0;
}
@Override
public List<JavaNode> getUseIn() {
return Collections.emptyList();
}
@Override @Override
public int compareTo(@NotNull JavaPackage o) { public int compareTo(@NotNull JavaPackage o) {
return name.compareTo(o.name); return name.compareTo(o.name);
@@ -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);
}
}
@@ -2,8 +2,9 @@ package jadx.api;
import java.io.File; import java.io.File;
import jadx.core.utils.files.ZipSecurity; import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.xmlgen.ResContainer; import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.entry.ResourceEntry;
public class ResourceFile { public class ResourceFile {
@@ -34,6 +35,11 @@ public class ResourceFile {
private final String name; private final String name;
private final ResourceType type; private final ResourceType type;
private ZipRef zipRef; 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) { public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) { if (!ZipSecurity.isValidZipEntryName(name)) {
@@ -48,10 +54,14 @@ public class ResourceFile {
this.type = type; this.type = type;
} }
public String getName() { public String getOriginalName() {
return name; return name;
} }
public String getDeobfName() {
return deobfName != null ? deobfName : name;
}
public ResourceType getType() { public ResourceType getType() {
return type; return type;
} }
@@ -64,6 +74,15 @@ public class ResourceFile {
this.zipRef = zipRef; this.zipRef = zipRef;
} }
public void setAlias(ResourceEntry ri) {
int index = name.lastIndexOf('.');
deobfName = String.format("res/%s%s/%s%s",
ri.getTypeName(),
ri.getConfig(),
ri.getKeyName(),
index == -1 ? "" : name.substring(index));
}
public ZipRef getZipRef() { public ZipRef getZipRef() {
return zipRef; return zipRef;
} }
@@ -1,18 +1,17 @@
package jadx.api; package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.xmlgen.ResContainer; import jadx.core.xmlgen.ResContainer;
public class ResourceFileContent extends ResourceFile { public class ResourceFileContent extends ResourceFile {
private final CodeWriter content; private final ICodeInfo content;
public ResourceFileContent(String name, ResourceType type, CodeWriter content) { public ResourceFileContent(String name, ResourceType type, ICodeInfo content) {
super(null, name, type); super(null, name, type);
this.content = content; this.content = content;
} }
@Override @Override
public ResContainer loadContent() { public ResContainer loadContent() {
return ResContainer.textResource(getName(), content); return ResContainer.textResource(getDeobfName(), content);
} }
} }
@@ -1,14 +1,20 @@
package jadx.api; package jadx.api;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import jadx.core.utils.exceptions.JadxRuntimeException;
public enum ResourceType { public enum ResourceType {
CODE(".dex", ".jar", ".class"), CODE(".dex", ".jar", ".class"),
MANIFEST("AndroidManifest.xml"),
XML(".xml"), XML(".xml"),
ARSC(".arsc"), ARSC(".arsc"),
FONT(".ttf", ".otf"), FONT(".ttf", ".otf"),
IMG(".png", ".gif", ".jpg"), IMG(".png", ".gif", ".jpg"),
MEDIA(".mp3", ".wav"), MEDIA(".mp3", ".wav"),
LIB(".so"), LIB(".so"),
MANIFEST,
UNKNOWN; UNKNOWN;
private final String[] exts; private final String[] exts;
@@ -21,14 +27,34 @@ public enum ResourceType {
return exts; return exts;
} }
public static ResourceType getFileType(String fileName) { private static final Map<String, ResourceType> EXT_MAP = new HashMap<>();
static {
for (ResourceType type : ResourceType.values()) { for (ResourceType type : ResourceType.values()) {
for (String ext : type.getExts()) { for (String ext : type.getExts()) {
if (fileName.toLowerCase().endsWith(ext)) { ResourceType prev = EXT_MAP.put(ext, type);
return type; if (prev != null) {
throw new JadxRuntimeException("Duplicate extension in ResourceType: " + ext);
} }
} }
} }
}
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);
ResourceType resType = EXT_MAP.get(ext);
if (resType != null) {
if (resType == XML && fileName.equals("AndroidManifest.xml")) {
return MANIFEST;
}
return resType;
}
}
return UNKNOWN; return UNKNOWN;
} }
} }
@@ -7,7 +7,6 @@ import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
@@ -16,13 +15,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile.ZipRef; import jadx.api.ResourceFile.ZipRef;
import jadx.core.codegen.CodeWriter; import jadx.api.impl.SimpleCodeInfo;
import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.android.Res9patchStreamDecoder; import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile; import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.xmlgen.ResContainer; import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResProtoParser;
import jadx.core.xmlgen.ResTableParser; import jadx.core.xmlgen.ResTableParser;
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE; import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
@@ -38,10 +39,11 @@ public final class ResourcesLoader {
this.jadxRef = jadxRef; this.jadxRef = jadxRef;
} }
List<ResourceFile> load(List<InputFile> inputFiles) { List<ResourceFile> load() {
List<File> inputFiles = jadxRef.getArgs().getInputFiles();
List<ResourceFile> list = new ArrayList<>(inputFiles.size()); List<ResourceFile> list = new ArrayList<>(inputFiles.size());
for (InputFile file : inputFiles) { for (File file : inputFiles) {
loadFile(list, file.getFile()); loadFile(list, file);
} }
return list; return list;
} }
@@ -54,7 +56,7 @@ public final class ResourcesLoader {
try { try {
ZipRef zipRef = rf.getZipRef(); ZipRef zipRef = rf.getZipRef();
if (zipRef == null) { if (zipRef == null) {
File file = new File(rf.getName()); File file = new File(rf.getOriginalName());
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
return decoder.decode(file.length(), inputStream); return decoder.decode(file.length(), inputStream);
} }
@@ -67,13 +69,13 @@ public final class ResourcesLoader {
if (!ZipSecurity.isValidZipEntry(entry)) { if (!ZipSecurity.isValidZipEntry(entry)) {
return null; return null;
} }
try (InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(entry))) { try (InputStream inputStream = ZipSecurity.getInputStreamForEntry(zipFile, entry)) {
return decoder.decode(entry.getSize(), inputStream); return decoder.decode(entry.getSize(), inputStream);
} }
} }
} }
} catch (Exception e) { } catch (Exception e) {
throw new JadxException("Error decode: " + rf.getName(), e); throw new JadxException("Error decode: " + rf.getDeobfName(), e);
} }
} }
@@ -82,23 +84,34 @@ public final class ResourcesLoader {
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is)); return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
} catch (JadxException e) { } catch (JadxException e) {
LOG.error("Decode error", e); LOG.error("Decode error", e);
CodeWriter cw = new CodeWriter(); ICodeWriter cw = jadxRef.getRoot().makeCodeWriter();
cw.add("Error decode ").add(rf.getType().toString().toLowerCase()); cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
Utils.appendStackTrace(cw, e.getCause()); Utils.appendStackTrace(cw, e.getCause());
return ResContainer.textResource(rf.getName(), cw); return ResContainer.textResource(rf.getDeobfName(), cw.finish());
} }
} }
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf, private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream) throws IOException { InputStream inputStream) throws IOException {
RootNode root = jadxRef.getRoot();
switch (rf.getType()) { switch (rf.getType()) {
case MANIFEST: case MANIFEST:
case XML: case XML: {
CodeWriter content = jadxRef.getXmlParser().parse(inputStream); ICodeInfo content;
return ResContainer.textResource(rf.getName(), content); if (root.isProto()) {
content = jadxRef.getProtoXmlParser().parse(inputStream);
} else {
content = jadxRef.getBinaryXmlParser().parse(inputStream);
}
return ResContainer.textResource(rf.getDeobfName(), content);
}
case ARSC: case ARSC:
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream); if (root.isProto()) {
return new ResProtoParser(root).decodeFiles(inputStream);
} else {
return new ResTableParser(root).decodeFiles(inputStream);
}
case IMG: case IMG:
return decodeImage(rf, inputStream); return decodeImage(rf, inputStream);
@@ -109,12 +122,13 @@ public final class ResourcesLoader {
} }
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) { private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
String name = rf.getName(); String name = rf.getOriginalName();
if (name.endsWith(".9.png")) { if (name.endsWith(".9.png")) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder(); Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
decoder.decode(inputStream, os); if (decoder.decode(inputStream, os)) {
return ResContainer.decodedData(rf.getName(), os.toByteArray()); return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
}
} catch (Exception e) { } catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e); LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
} }
@@ -123,29 +137,17 @@ public final class ResourcesLoader {
} }
private void loadFile(List<ResourceFile> list, File file) { private void loadFile(List<ResourceFile> list, File file) {
if (file == null) { if (file == null || file.isDirectory()) {
return; return;
} }
try (ZipFile zip = new ZipFile(file)) { if (FileUtils.isZipFile(file)) {
Enumeration<? extends ZipEntry> entries = zip.entries(); ZipSecurity.visitZipEntries(file, (zipFile, entry) -> {
while (entries.hasMoreElements()) { addEntry(list, file, entry);
ZipEntry entry = entries.nextElement(); return null;
if (ZipSecurity.isValidZipEntry(entry)) { });
addEntry(list, file, entry); } else {
} ResourceType type = ResourceType.getFileType(file.getAbsolutePath());
} list.add(ResourceFile.createResourceFile(jadxRef, file, type));
} catch (Exception e) {
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
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);
} }
} }
@@ -162,9 +164,9 @@ public final class ResourcesLoader {
} }
} }
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException { public static ICodeInfo loadToCodeWriter(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE); ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
copyStream(is, baos); copyStream(is, baos);
return new CodeWriter(baos.toString("UTF-8")); return new SimpleCodeInfo(baos.toString("UTF-8"));
} }
} }
@@ -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;
});
}
}
}
@@ -17,8 +17,18 @@ public class InMemoryCodeCache implements ICodeCache {
storage.put(clsFullName, codeInfo); storage.put(clsFullName, codeInfo);
} }
@Override
public void remove(String clsFullName) {
storage.remove(clsFullName);
}
@Override @Override
public @Nullable ICodeInfo get(String clsFullName) { public @Nullable ICodeInfo get(String clsFullName) {
return storage.get(clsFullName); return storage.get(clsFullName);
} }
@Override
public String toString() {
return "InMemoryCodeCache: size=" + storage.size();
}
} }
@@ -12,8 +12,18 @@ public class NoOpCodeCache implements ICodeCache {
// do nothing // do nothing
} }
@Override
public void remove(String clsFullName) {
// do nothing
}
@Override @Override
public @Nullable ICodeInfo get(String clsFullName) { public @Nullable ICodeInfo get(String clsFullName) {
return null; return null;
} }
@Override
public String toString() {
return "NoOpCodeCache";
}
} }
@@ -0,0 +1,36 @@
package jadx.api.impl;
import java.util.Collections;
import java.util.Map;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
public class SimpleCodeInfo implements ICodeInfo {
private final String code;
public SimpleCodeInfo(String code) {
this.code = code;
}
@Override
public String getCodeStr() {
return code;
}
@Override
public Map<Integer, Integer> getLineMapping() {
return Collections.emptyMap();
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return Collections.emptyMap();
}
@Override
public String toString() {
return code;
}
}
@@ -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();
}
}
@@ -2,20 +2,21 @@ package jadx.core;
public class Consts { public class Consts {
public static final boolean DEBUG = false; public static final boolean DEBUG = false;
public static final boolean DEBUG_WITH_ERRORS = false; // TODO: fix errors
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_OBJECT = "java.lang.Object";
public static final String CLASS_STRING = "java.lang.String"; public static final String CLASS_STRING = "java.lang.String";
public static final String CLASS_CLASS = "java.lang.Class"; public static final String CLASS_CLASS = "java.lang.Class";
public static final String CLASS_THROWABLE = "java.lang.Throwable"; public static final String CLASS_THROWABLE = "java.lang.Throwable";
public static final String CLASS_EXCEPTION = "java.lang.Exception";
public static final String CLASS_ENUM = "java.lang.Enum"; public static final String CLASS_ENUM = "java.lang.Enum";
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder"; public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
public static final String OVERRIDE_ANNOTATION = "Ljava/lang/Override;";
public static final String DALVIK_ANNOTATION_PKG = "dalvik.annotation.";
public static final String DALVIK_SIGNATURE = "dalvik.annotation.Signature";
public static final String DALVIK_INNER_CLASS = "dalvik.annotation.InnerClass";
public static final String DALVIK_THROWS = "dalvik.annotation.Throws";
public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.annotation.AnnotationDefault";
public static final String DEFAULT_PACKAGE_NAME = "defpackage"; public static final String DEFAULT_PACKAGE_NAME = "defpackage";
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass"; public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
+113 -60
View File
@@ -10,33 +10,45 @@ import java.util.jar.Manifest;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.core.dex.visitors.AnonymousClassVisitor;
import jadx.core.dex.visitors.AttachCommentsVisitor;
import jadx.core.dex.visitors.AttachMethodDetails;
import jadx.core.dex.visitors.AttachTryCatchVisitor;
import jadx.core.dex.visitors.CheckCode;
import jadx.core.dex.visitors.ClassModifier; import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.ConstInlineVisitor; import jadx.core.dex.visitors.ConstInlineVisitor;
import jadx.core.dex.visitors.ConstructorVisitor; import jadx.core.dex.visitors.ConstructorVisitor;
import jadx.core.dex.visitors.DeboxingVisitor; import jadx.core.dex.visitors.DeboxingVisitor;
import jadx.core.dex.visitors.DependencyCollector;
import jadx.core.dex.visitors.DotGraphVisitor; import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor; import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.ExtractFieldInit; import jadx.core.dex.visitors.ExtractFieldInit;
import jadx.core.dex.visitors.FallbackModeVisitor; import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.FixAccessModifiers; import jadx.core.dex.visitors.FixAccessModifiers;
import jadx.core.dex.visitors.GenericTypesVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.InitCodeVariables; import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.MarkFinallyVisitor; import jadx.core.dex.visitors.InlineMethods;
import jadx.core.dex.visitors.MethodInlineVisitor; import jadx.core.dex.visitors.MarkMethodsForInline;
import jadx.core.dex.visitors.MethodInvokeVisitor;
import jadx.core.dex.visitors.ModVisitor; import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.MoveInlineVisitor;
import jadx.core.dex.visitors.OverrideMethodVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen; import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ProcessAnonymous; 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.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.SimplifyVisitor;
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler; import jadx.core.dex.visitors.blocks.BlockProcessor;
import jadx.core.dex.visitors.blocksmaker.BlockFinish; import jadx.core.dex.visitors.blocks.BlockSplitter;
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor; import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoParseVisitor; import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
import jadx.core.dex.visitors.regions.CheckRegions; import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions; import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor; import jadx.core.dex.visitors.regions.IfRegionVisitor;
@@ -44,9 +56,12 @@ import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.RegionMakerVisitor; import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.regions.ReturnVisitor; import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.regions.variables.ProcessVariables; import jadx.core.dex.visitors.regions.variables.ProcessVariables;
import jadx.core.dex.visitors.rename.CodeRenameVisitor;
import jadx.core.dex.visitors.rename.RenameVisitor;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
public class Jadx { public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class); private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
@@ -60,67 +75,105 @@ public class Jadx {
} }
} }
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) { public static List<IDexTreeVisitor> getFallbackPassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>(); List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new AttachTryCatchVisitor());
passes.add(new AttachCommentsVisitor());
passes.add(new ProcessInstructionsVisitor());
passes.add(new FallbackModeVisitor());
return passes;
}
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new SignatureProcessor());
passes.add(new OverrideMethodVisitor());
passes.add(new RenameVisitor());
passes.add(new UsageInfoVisitor());
passes.add(new ProcessAnonymous());
passes.add(new ProcessMethodsForInline());
return passes;
}
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
if (args.isFallbackMode()) { if (args.isFallbackMode()) {
passes.add(new FallbackModeVisitor()); return getFallbackPassesList();
} else { }
if (args.isDebugInfo()) { List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new DebugInfoParseVisitor());
}
passes.add(new BlockSplitter()); // instructions IR
if (args.isRawCFGOutput()) { passes.add(new CheckCode());
passes.add(DotGraphVisitor.dumpRaw()); if (args.isDebugInfo()) {
} passes.add(new DebugInfoAttachVisitor());
}
passes.add(new AttachTryCatchVisitor());
if (args.getCommentsLevel() != CommentsLevel.NONE) {
passes.add(new AttachCommentsVisitor());
}
passes.add(new AttachMethodDetails());
passes.add(new ProcessInstructionsVisitor());
passes.add(new BlockProcessor()); // blocks IR
passes.add(new BlockExceptionHandler()); passes.add(new BlockSplitter());
passes.add(new BlockFinish()); passes.add(new BlockProcessor());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
passes.add(new SSATransform()); passes.add(new SSATransform());
passes.add(new ConstructorVisitor()); passes.add(new MoveInlineVisitor());
passes.add(new InitCodeVariables()); passes.add(new ConstructorVisitor());
passes.add(new InitCodeVariables());
if (args.isExtractFinally()) {
passes.add(new MarkFinallyVisitor()); passes.add(new MarkFinallyVisitor());
passes.add(new ConstInlineVisitor()); }
passes.add(new TypeInferenceVisitor()); passes.add(new ConstInlineVisitor());
if (args.isDebugInfo()) { passes.add(new TypeInferenceVisitor());
passes.add(new DebugInfoApplyVisitor()); if (args.isDebugInfo()) {
} passes.add(new DebugInfoApplyVisitor());
}
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());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump());
}
passes.add(new DeboxingVisitor()); // regions IR
passes.add(new ModVisitor()); passes.add(new RegionMakerVisitor());
passes.add(new CodeShrinkVisitor()); passes.add(new IfRegionVisitor());
passes.add(new ReSugarCode()); passes.add(new ReturnVisitor());
if (args.isCfgOutput()) { passes.add(new CleanRegions());
passes.add(DotGraphVisitor.dump());
}
passes.add(new RegionMakerVisitor()); passes.add(new CodeShrinkVisitor());
passes.add(new IfRegionVisitor()); passes.add(new MethodInvokeVisitor());
passes.add(new ReturnVisitor()); passes.add(new SimplifyVisitor());
passes.add(new CleanRegions()); passes.add(new CheckRegions());
passes.add(new CodeShrinkVisitor()); passes.add(new EnumVisitor());
passes.add(new SimplifyVisitor()); passes.add(new ExtractFieldInit());
passes.add(new CheckRegions()); passes.add(new FixAccessModifiers());
passes.add(new ClassModifier());
passes.add(new LoopRegionVisitor());
passes.add(new ExtractFieldInit()); if (args.isInlineMethods()) {
passes.add(new FixAccessModifiers()); passes.add(new MarkMethodsForInline());
passes.add(new ProcessAnonymous()); }
passes.add(new ClassModifier()); passes.add(new ProcessVariables());
passes.add(new MethodInlineVisitor()); passes.add(new PrepareForCodeGen());
passes.add(new EnumVisitor()); if (args.isCfgOutput()) {
passes.add(new LoopRegionVisitor()); passes.add(DotGraphVisitor.dumpRegions());
passes.add(new ProcessVariables());
passes.add(new PrepareForCodeGen());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
}
passes.add(new DependencyCollector());
passes.add(new RenameVisitor());
} }
return passes; return passes;
} }
@@ -1,15 +1,18 @@
package jadx.core; package jadx.core;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeInfo; import jadx.api.ICodeInfo;
import jadx.core.codegen.CodeGen; import jadx.core.codegen.CodeGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.LoadStage;
import jadx.core.dex.visitors.DepthTraversal; import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
import static jadx.core.dex.nodes.ProcessState.LOADED; import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED; import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE; import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
@@ -20,18 +23,36 @@ public final class ProcessClass {
private ProcessClass() { private ProcessClass() {
} }
public static void process(ClassNode cls) { @Nullable
ClassNode topParentClass = cls.getTopParentClass(); private static ICodeInfo process(ClassNode cls, boolean codegen) {
if (topParentClass != cls) { if (!codegen && cls.getState() == PROCESS_COMPLETE) {
process(topParentClass);
return;
}
if (cls.getState().isProcessed()) {
// nothing to do // nothing to do
return; return null;
} }
synchronized (cls.getClassInfo()) { synchronized (cls.getClassInfo()) {
try { try {
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
cls.remove(AFlag.CLASS_DEEP_RELOAD);
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) {
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
cls.unload();
}
} else {
cls.setLoadStage(LoadStage.PROCESS_STAGE);
}
if (cls.getState() == NOT_LOADED) { if (cls.getState() == NOT_LOADED) {
cls.load(); cls.load();
} }
@@ -42,8 +63,21 @@ public final class ProcessClass {
} }
cls.setState(PROCESS_COMPLETE); cls.setState(PROCESS_COMPLETE);
} }
if (codegen) {
ICodeInfo code = CodeGen.generate(cls);
if (!cls.contains(AFlag.DONT_UNLOAD_CLASS)) {
cls.unload();
cls.setState(GENERATED_AND_UNLOADED);
}
return code;
}
return null;
} catch (Throwable e) { } catch (Throwable e) {
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e); if (codegen) {
throw e;
}
cls.addError("Class process error: " + e.getClass().getSimpleName(), e);
return null;
} }
} }
} }
@@ -55,11 +89,19 @@ public final class ProcessClass {
return generateCode(topParentClass); return generateCode(topParentClass);
} }
try { try {
process(cls); for (ClassNode depCls : cls.getDependencies()) {
cls.getDependencies().forEach(ProcessClass::process); process(depCls, false);
}
ICodeInfo code = CodeGen.generate(cls); if (!cls.getCodegenDeps().isEmpty()) {
cls.unload(); process(cls, false);
for (ClassNode codegenDep : cls.getCodegenDeps()) {
process(codegenDep, false);
}
}
ICodeInfo code = process(cls, true);
if (code == null) {
throw new JadxRuntimeException("Codegen failed");
}
return code; return code;
} catch (Throwable e) { } catch (Throwable e) {
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e); throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
+275 -260
View File
@@ -1,5 +1,6 @@
package jadx.core.clsp; package jadx.core.clsp;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
@@ -16,23 +17,27 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.GenericInfo;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
/** /**
* Classes list for import into classpath graph * Classes list for import into classpath graph
@@ -42,133 +47,130 @@ public class ClsSet {
private static final String CLST_EXTENSION = ".jcst"; private static final String CLST_EXTENSION = ".jcst";
private static final String CLST_FILENAME = "core" + CLST_EXTENSION; private static final String CLST_FILENAME = "core" + CLST_EXTENSION;
private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/'); private static final String CLST_PATH = "/clst/" + CLST_FILENAME;
private static final String JADX_CLS_SET_HEADER = "jadx-cst"; private static final String JADX_CLS_SET_HEADER = "jadx-cst";
private static final int VERSION = 2; private static final int VERSION = 3;
private static final String STRING_CHARSET = "US-ASCII"; private static final String STRING_CHARSET = "US-ASCII";
private static final NClass[] EMPTY_NCLASS_ARRAY = new NClass[0]; private static final ArgType[] EMPTY_ARGTYPE_ARRAY = new ArgType[0];
private enum TypeEnum { private final RootNode root;
WILDCARD, GENERIC, GENERIC_TYPE, OBJECT, ARRAY, PRIMITIVE
public ClsSet(RootNode root) {
this.root = root;
} }
private NClass[] classes; private enum TypeEnum {
WILDCARD,
GENERIC,
GENERIC_TYPE_VARIABLE,
OUTER_GENERIC,
OBJECT,
ARRAY,
PRIMITIVE
}
private ClspClass[] classes;
public void loadFromClstFile() throws IOException, DecodeException { public void loadFromClstFile() throws IOException, DecodeException {
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) { long startTime = System.currentTimeMillis();
try (InputStream input = ClsSet.class.getResourceAsStream(CLST_PATH)) {
if (input == null) { if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME); throw new JadxRuntimeException("Can't load classpath file: " + CLST_PATH);
} }
load(input); load(input);
} }
if (LOG.isDebugEnabled()) {
long time = System.currentTimeMillis() - startTime;
int methodsCount = Stream.of(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
LOG.debug("Clst file loaded in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount);
}
} }
public void loadFrom(RootNode root) { public void loadFrom(RootNode root) {
List<ClassNode> list = root.getClasses(true); List<ClassNode> list = root.getClasses(true);
Map<String, NClass> names = new HashMap<>(list.size()); Map<String, ClspClass> names = new HashMap<>(list.size());
int k = 0; int k = 0;
for (ClassNode cls : list) { for (ClassNode cls : list) {
String clsRawName = cls.getRawName(); ArgType clsType = cls.getClassInfo().getType();
if (cls.getAccessFlags().isPublic()) { String clsRawName = clsType.getObject();
cls.load(); cls.load();
NClass nClass = new NClass(clsRawName, k); ClspClass nClass = new ClspClass(clsType, k);
if (names.put(clsRawName, nClass) != null) { if (names.put(clsRawName, nClass) != null) {
throw new JadxRuntimeException("Duplicate class: " + clsRawName); throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
k++;
nClass.setGenerics(cls.getGenerics());
nClass.setMethods(getMethodsDetails(cls));
} else {
names.put(clsRawName, null);
} }
k++;
nClass.setTypeParameters(cls.getGenericTypeParameters());
nClass.setMethods(getMethodsDetails(cls));
} }
classes = new NClass[k]; classes = new ClspClass[k];
k = 0; k = 0;
for (ClassNode cls : list) { for (ClassNode cls : list) {
if (cls.getAccessFlags().isPublic()) { ClspClass nClass = getCls(cls, names);
NClass nClass = getCls(cls.getRawName(), names); if (nClass == null) {
if (nClass == null) { throw new JadxRuntimeException("Missing class: " + cls);
throw new JadxRuntimeException("Missing class: " + cls);
}
nClass.setParents(makeParentsArray(cls, names));
classes[k] = nClass;
k++;
} }
nClass.setParents(makeParentsArray(cls));
classes[k] = nClass;
k++;
} }
} }
private List<NMethod> getMethodsDetails(ClassNode cls) { private List<ClspMethod> getMethodsDetails(ClassNode cls) {
List<NMethod> methods = new ArrayList<>(); List<MethodNode> methodsList = cls.getMethods();
for (MethodNode m : cls.getMethods()) { List<ClspMethod> methods = new ArrayList<>(methodsList.size());
AccessInfo accessFlags = m.getAccessFlags(); for (MethodNode mth : methodsList) {
if (accessFlags.isPublic() || accessFlags.isProtected()) { processMethodDetails(mth, methods);
processMethodDetails(methods, m, accessFlags);
}
} }
return methods; return methods;
} }
private void processMethodDetails(List<NMethod> methods, MethodNode mth, AccessInfo accessFlags) { private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) {
List<ArgType> args = mth.getArgTypes(); AccessInfo accessFlags = mth.getAccessFlags();
boolean genericArg = false; if (accessFlags.isPrivate() || accessFlags.isSynthetic() || accessFlags.isBridge()) {
ArgType[] genericArgs; return;
if (args.isEmpty()) {
genericArgs = null;
} else {
int argsCount = args.size();
genericArgs = new ArgType[argsCount];
for (int i = 0; i < argsCount; i++) {
ArgType argType = args.get(i);
if (argType.isGeneric() || argType.isGenericType()) {
genericArgs[i] = argType;
genericArg = true;
}
}
}
ArgType retType = mth.getReturnType();
if (!retType.isGeneric() && !retType.isGenericType()) {
retType = null;
}
boolean varArgs = accessFlags.isVarArgs();
if (genericArg || retType != null || varArgs) {
methods.add(new NMethod(mth.getMethodInfo().getShortId(), genericArgs, retType, varArgs));
} }
ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(), mth.getArgTypes(),
mth.getReturnType(), mth.getTypeParameters(),
mth.getThrows(), accessFlags.rawValue());
methods.add(clspMethod);
} }
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) { public static ArgType[] makeParentsArray(ClassNode cls) {
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
ArgType superClass = cls.getSuperClass(); ArgType superClass = cls.getSuperClass();
if (superClass != null) { if (superClass == null) {
NClass c = getCls(superClass.getObject(), names); // cls is java.lang.Object
if (c != null) { return EMPTY_ARGTYPE_ARRAY;
parents.add(c);
}
} }
ArgType[] parents = new ArgType[1 + cls.getInterfaces().size()];
parents[0] = superClass;
int k = 1;
for (ArgType iface : cls.getInterfaces()) { for (ArgType iface : cls.getInterfaces()) {
NClass c = getCls(iface.getObject(), names); parents[k] = iface;
if (c != null) { k++;
parents.add(c);
}
} }
int size = parents.size(); return parents;
if (size == 0) {
return EMPTY_NCLASS_ARRAY;
}
return parents.toArray(new NClass[size]);
} }
private static NClass getCls(String fullName, Map<String, NClass> names) { private static ClspClass getCls(ClassNode cls, Map<String, ClspClass> names) {
NClass cls = names.get(fullName); return getCls(cls.getRawName(), names);
}
private static ClspClass getCls(ArgType clsType, Map<String, ClspClass> names) {
return getCls(clsType.getObject(), names);
}
private static ClspClass getCls(String fullName, Map<String, ClspClass> names) {
ClspClass cls = names.get(fullName);
if (cls == null) { if (cls == null) {
LOG.debug("Class not found: {}", fullName); LOG.debug("Class not found: {}", fullName);
} }
return cls; return cls;
} }
void save(Path path) throws IOException { public void save(Path path) throws IOException {
FileUtils.makeDirsForFile(path); FileUtils.makeDirsForFile(path);
String outputName = path.getFileName().toString(); String outputName = path.getFileName().toString();
if (outputName.endsWith(CLST_EXTENSION)) { if (outputName.endsWith(CLST_EXTENSION)) {
@@ -181,98 +183,107 @@ public class ClsSet {
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path)); try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) { ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME; String clst = CLST_PATH;
out.putNextEntry(new ZipEntry(clst)); boolean clstReplaced = false;
save(out);
ZipEntry entry = in.getNextEntry(); ZipEntry entry = in.getNextEntry();
while (entry != null) { while (entry != null) {
if (!entry.getName().equals(clst)) { String entryName = entry.getName();
out.putNextEntry(new ZipEntry(entry.getName())); ZipEntry copyEntry = new ZipEntry(entryName);
copyEntry.setLastModifiedTime(entry.getLastModifiedTime()); // preserve modified time
out.putNextEntry(copyEntry);
if (entryName.equals(clst)) {
save(out);
clstReplaced = true;
} else {
FileUtils.copyStream(in, out); FileUtils.copyStream(in, out);
} }
entry = in.getNextEntry(); entry = in.getNextEntry();
} }
if (!clstReplaced) {
out.putNextEntry(new ZipEntry(clst));
save(out);
}
} }
} else { } else {
throw new JadxRuntimeException("Unknown file format: " + outputName); throw new JadxRuntimeException("Unknown file format: " + outputName);
} }
} }
public void save(OutputStream output) throws IOException { private void save(OutputStream output) throws IOException {
DataOutputStream out = new DataOutputStream(output); DataOutputStream out = new DataOutputStream(output);
out.writeBytes(JADX_CLS_SET_HEADER); out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION); out.writeByte(VERSION);
LOG.info("Classes count: {}", classes.length); Map<String, ClspClass> names = new HashMap<>(classes.length);
Map<String, NClass> names = new HashMap<>(classes.length);
out.writeInt(classes.length); out.writeInt(classes.length);
for (NClass cls : classes) { for (ClspClass cls : classes) {
writeString(out, cls.getName()); String clsName = cls.getName();
names.put(cls.getName(), cls); writeString(out, clsName);
names.put(clsName, cls);
} }
for (NClass cls : classes) { for (ClspClass cls : classes) {
NClass[] parents = cls.getParents(); writeArgTypesArray(out, cls.getParents(), names);
out.writeByte(parents.length); writeArgTypesList(out, cls.getTypeParameters(), names);
for (NClass parent : parents) { List<ClspMethod> methods = cls.getSortedMethodsList();
out.writeInt(parent.getId()); out.writeShort(methods.size());
} for (ClspMethod method : methods) {
writeGenerics(out, cls, names);
List<NMethod> methods = cls.getMethodsList();
out.writeByte(methods.size());
for (NMethod method : methods) {
writeMethod(out, method, names); writeMethod(out, method, names);
} }
} }
int methodsCount = Stream.of(classes).mapToInt(c -> c.getMethodsMap().size()).sum();
LOG.info("Classes: {}, methods: {}, file size: {} bytes", classes.length, methodsCount, out.size());
} }
private static void writeGenerics(DataOutputStream out, NClass cls, Map<String, NClass> names) throws IOException { private static void writeMethod(DataOutputStream out, ClspMethod method, Map<String, ClspClass> names) throws IOException {
List<GenericInfo> genericsList = cls.getGenerics(); MethodInfo methodInfo = method.getMethodInfo();
out.writeByte(genericsList.size()); writeString(out, methodInfo.getName());
for (GenericInfo genericInfo : genericsList) { writeArgTypesList(out, methodInfo.getArgumentsTypes(), names);
writeArgType(out, genericInfo.getGenericType(), names); writeArgType(out, methodInfo.getReturnType(), names);
List<ArgType> extendsList = genericInfo.getExtendsList();
out.writeByte(extendsList.size()); writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names);
for (ArgType type : extendsList) { writeArgType(out, method.getReturnType(), names);
writeArgTypesList(out, method.getTypeParameters(), names);
out.writeInt(method.getRawAccessFlags());
writeArgTypesList(out, method.getThrows(), names);
}
private static void writeArgTypesList(DataOutputStream out, List<ArgType> list, Map<String, ClspClass> names) throws IOException {
int size = list.size();
writeUnsignedByte(out, size);
if (size != 0) {
for (ArgType type : list) {
writeArgType(out, type, names); writeArgType(out, type, names);
} }
} }
} }
private static void writeMethod(DataOutputStream out, NMethod method, Map<String, NClass> names) throws IOException { private static void writeArgTypesArray(DataOutputStream out, @Nullable ArgType[] arr, Map<String, ClspClass> names) throws IOException {
writeLongString(out, method.getShortId()); if (arr == null) {
out.writeByte(-1);
ArgType[] argTypes = method.getGenericArgs(); return;
if (argTypes == null) { }
out.writeByte(0); int size = arr.length;
} else { out.writeByte(size);
int argCount = 0; if (size != 0) {
for (ArgType arg : argTypes) { for (ArgType type : arr) {
if (arg != null) { writeArgType(out, type, names);
argCount++;
}
}
out.writeByte(argCount);
// last argument first
for (int i = argTypes.length - 1; i >= 0; i--) {
ArgType argType = argTypes[i];
if (argType != null) {
out.writeByte(i);
writeArgType(out, argType, names);
}
} }
} }
if (method.getReturnType() == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
writeArgType(out, method.getReturnType(), names);
}
out.writeBoolean(method.isVarArgs());
} }
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, NClass> names) throws IOException { private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, ClspClass> names) throws IOException {
if (argType.getWildcardType() != null) { if (argType == null) {
out.writeByte(-1);
return;
}
if (argType.isPrimitive()) {
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
} else if (argType.getOuterType() != null) {
out.writeByte(TypeEnum.OUTER_GENERIC.ordinal());
writeArgType(out, argType.getOuterType(), names);
writeArgType(out, argType.getInnerType(), names);
} else if (argType.getWildcardType() != null) {
out.writeByte(TypeEnum.WILDCARD.ordinal()); out.writeByte(TypeEnum.WILDCARD.ordinal());
ArgType.WildcardBound bound = argType.getWildcardBound(); ArgType.WildcardBound bound = argType.getWildcardBound();
out.writeByte(bound.getNum()); out.writeByte(bound.getNum());
@@ -281,28 +292,18 @@ public class ClsSet {
} }
} else if (argType.isGeneric()) { } else if (argType.isGeneric()) {
out.writeByte(TypeEnum.GENERIC.ordinal()); out.writeByte(TypeEnum.GENERIC.ordinal());
out.writeInt(names.get(argType.getObject()).getId()); out.writeInt(getCls(argType, names).getId());
ArgType[] types = argType.getGenericTypes(); writeArgTypesList(out, argType.getGenericTypes(), names);
if (types == null) {
out.writeByte(0);
} else {
out.writeByte(types.length);
for (ArgType type : types) {
writeArgType(out, type, names);
}
}
} else if (argType.isGenericType()) { } else if (argType.isGenericType()) {
out.writeByte(TypeEnum.GENERIC_TYPE.ordinal()); out.writeByte(TypeEnum.GENERIC_TYPE_VARIABLE.ordinal());
writeString(out, argType.getObject()); writeString(out, argType.getObject());
writeArgTypesList(out, argType.getExtendTypes(), names);
} else if (argType.isObject()) { } else if (argType.isObject()) {
out.writeByte(TypeEnum.OBJECT.ordinal()); out.writeByte(TypeEnum.OBJECT.ordinal());
out.writeInt(names.get(argType.getObject()).getId()); out.writeInt(getCls(argType, names).getId());
} else if (argType.isArray()) { } else if (argType.isArray()) {
out.writeByte(TypeEnum.ARRAY.ordinal()); out.writeByte(TypeEnum.ARRAY.ordinal());
writeArgType(out, argType.getArrayElement(), names); writeArgType(out, argType.getArrayElement(), names);
} else if (argType.isPrimitive()) {
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
} else { } else {
throw new JadxRuntimeException("Cannot save type: " + argType); throw new JadxRuntimeException("Cannot save type: " + argType);
} }
@@ -310,27 +311,27 @@ public class ClsSet {
private void load(File input) throws IOException, DecodeException { private void load(File input) throws IOException, DecodeException {
String name = input.getName(); String name = input.getName();
try (InputStream inputStream = new FileInputStream(input)) { if (name.endsWith(CLST_EXTENSION)) {
if (name.endsWith(CLST_EXTENSION)) { try (InputStream inputStream = new FileInputStream(input)) {
load(inputStream); load(inputStream);
} else if (name.endsWith(".jar")) { }
try (ZipInputStream in = new ZipInputStream(inputStream)) { } else if (name.endsWith(".jar")) {
ZipEntry entry = in.getNextEntry(); ZipSecurity.readZipEntries(input, (entry, in) -> {
while (entry != null) { if (entry.getName().endsWith(CLST_EXTENSION)) {
if (entry.getName().endsWith(CLST_EXTENSION) && ZipSecurity.isValidZipEntry(entry)) { try {
load(in); load(in);
} } catch (Exception e) {
entry = in.getNextEntry(); throw new JadxRuntimeException("Failed to load jadx class set");
} }
} }
} else { });
throw new JadxRuntimeException("Unknown file format: " + name); } else {
} throw new JadxRuntimeException("Unknown file format: " + name);
} }
} }
private void load(InputStream input) throws IOException, DecodeException { private void load(InputStream input) throws IOException, DecodeException {
try (DataInputStream in = new DataInputStream(input)) { try (DataInputStream in = new DataInputStream(new BufferedInputStream(input))) {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()]; byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header); int readHeaderLength = in.read(header);
int version = in.readByte(); int version = in.readByte();
@@ -339,103 +340,113 @@ public class ClsSet {
|| version != VERSION) { || version != VERSION) {
throw new DecodeException("Wrong jadx class set header"); throw new DecodeException("Wrong jadx class set header");
} }
int count = in.readInt(); int clsCount = in.readInt();
classes = new NClass[count]; classes = new ClspClass[clsCount];
for (int i = 0; i < count; i++) { for (int i = 0; i < clsCount; i++) {
String name = readString(in); String name = readString(in);
classes[i] = new NClass(name, i); classes[i] = new ClspClass(ArgType.object(name), i);
} }
for (int i = 0; i < count; i++) { for (int i = 0; i < clsCount; i++) {
int pCount = in.readByte(); ClspClass nClass = classes[i];
NClass[] parents = new NClass[pCount]; ClassInfo clsInfo = ClassInfo.fromType(root, nClass.getClsType());
for (int j = 0; j < pCount; j++) { nClass.setParents(readArgTypesArray(in));
parents[j] = classes[in.readInt()]; nClass.setTypeParameters(readArgTypesList(in));
} nClass.setMethods(readClsMethods(in, clsInfo));
NClass nClass = classes[i];
nClass.setParents(parents);
nClass.setGenerics(readGenerics(in));
nClass.setMethods(readClsMethods(in));
} }
} }
} }
private List<GenericInfo> readGenerics(DataInputStream in) throws IOException { private List<ClspMethod> readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException {
int count = in.readByte(); int mCount = in.readShort();
if (count == 0) { List<ClspMethod> methods = new ArrayList<>(mCount);
return Collections.emptyList();
}
List<GenericInfo> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
ArgType genericType = readArgType(in);
List<ArgType> extendsList;
byte extCount = in.readByte();
if (extCount == 0) {
extendsList = Collections.emptyList();
} else {
extendsList = new ArrayList<>(extCount);
for (int j = 0; j < extCount; j++) {
extendsList.add(readArgType(in));
}
}
list.add(new GenericInfo(genericType, extendsList));
}
return list;
}
private List<NMethod> readClsMethods(DataInputStream in) throws IOException {
int mCount = in.readByte();
List<NMethod> methods = new ArrayList<>(mCount);
for (int j = 0; j < mCount; j++) { for (int j = 0; j < mCount; j++) {
methods.add(readMethod(in)); methods.add(readMethod(in, clsInfo));
} }
return methods; return methods;
} }
private NMethod readMethod(DataInputStream in) throws IOException { private ClspMethod readMethod(DataInputStream in, ClassInfo clsInfo) throws IOException {
String shortId = readLongString(in); String name = readString(in);
int argCount = in.readByte(); List<ArgType> argTypes = readArgTypesList(in);
ArgType[] argTypes = null; ArgType retType = readArgType(in);
for (int i = 0; i < argCount; i++) { List<ArgType> genericArgTypes = readArgTypesList(in);
int index = in.readByte(); if (genericArgTypes.isEmpty() || Objects.equals(genericArgTypes, argTypes)) {
ArgType argType = readArgType(in); genericArgTypes = argTypes;
if (argTypes == null) {
argTypes = new ArgType[index + 1];
}
argTypes[index] = argType;
} }
ArgType retType = in.readBoolean() ? readArgType(in) : null; ArgType genericRetType = readArgType(in);
boolean varArgs = in.readBoolean(); if (Objects.equals(genericRetType, retType)) {
return new NMethod(shortId, argTypes, retType, varArgs); genericRetType = retType;
}
List<ArgType> typeParameters = readArgTypesList(in);
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, throwList, accFlags);
}
private List<ArgType> readArgTypesList(DataInputStream in) throws IOException {
int count = in.readByte();
if (count == 0) {
return Collections.emptyList();
}
List<ArgType> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
list.add(readArgType(in));
}
return list;
}
@Nullable
private ArgType[] readArgTypesArray(DataInputStream in) throws IOException {
int count = in.readByte();
if (count == -1) {
return null;
}
if (count == 0) {
return EMPTY_ARGTYPE_ARRAY;
}
ArgType[] arr = new ArgType[count];
for (int i = 0; i < count; i++) {
arr[i] = readArgType(in);
}
return arr;
} }
private ArgType readArgType(DataInputStream in) throws IOException { private ArgType readArgType(DataInputStream in) throws IOException {
int ordinal = in.readByte(); int ordinal = in.readByte();
if (ordinal == -1) {
return null;
}
if (ordinal >= TypeEnum.values().length) {
throw new JadxRuntimeException("Incorrect ordinal for type enum: " + ordinal);
}
switch (TypeEnum.values()[ordinal]) { switch (TypeEnum.values()[ordinal]) {
case WILDCARD: case WILDCARD:
int bounds = in.readByte(); ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte());
return bounds == 0 if (bound == ArgType.WildcardBound.UNBOUND) {
? ArgType.wildcard() return ArgType.WILDCARD;
: ArgType.wildcard(readArgType(in), ArgType.WildcardBound.getByNum(bounds)); }
ArgType objType = readArgType(in);
return ArgType.wildcard(objType, bound);
case OUTER_GENERIC:
ArgType outerType = readArgType(in);
ArgType innerType = readArgType(in);
return ArgType.outerGeneric(outerType, innerType);
case GENERIC: case GENERIC:
String obj = classes[in.readInt()].getName(); ArgType clsType = classes[in.readInt()].getClsType();
int typeLength = in.readByte(); return ArgType.generic(clsType, readArgTypesList(in));
ArgType[] generics;
if (typeLength == 0) {
generics = null;
} else {
generics = new ArgType[typeLength];
for (int i = 0; i < typeLength; i++) {
generics[i] = readArgType(in);
}
}
return ArgType.generic(obj, generics);
case GENERIC_TYPE: case GENERIC_TYPE_VARIABLE:
return ArgType.genericType(readString(in)); String typeVar = readString(in);
List<ArgType> extendTypes = readArgTypesList(in);
return ArgType.genericType(typeVar, extendTypes);
case OBJECT: case OBJECT:
return ArgType.object(classes[in.readInt()].getName()); return classes[in.readInt()].getClsType();
case ARRAY: case ARRAY:
return ArgType.array(readArgType(in)); return ArgType.array(readArgType(in));
@@ -451,23 +462,16 @@ public class ClsSet {
private static void writeString(DataOutputStream out, String name) throws IOException { private static void writeString(DataOutputStream out, String name) throws IOException {
byte[] bytes = name.getBytes(STRING_CHARSET); byte[] bytes = name.getBytes(STRING_CHARSET);
out.writeByte(bytes.length); int len = bytes.length;
out.write(bytes); if (len >= 0xFF) {
} throw new JadxRuntimeException("String is too long: " + name);
}
private static void writeLongString(DataOutputStream out, String name) throws IOException { writeUnsignedByte(out, bytes.length);
byte[] bytes = name.getBytes(STRING_CHARSET);
out.writeShort(bytes.length);
out.write(bytes); out.write(bytes);
} }
private static String readString(DataInputStream in) throws IOException { private static String readString(DataInputStream in) throws IOException {
int len = in.readByte(); int len = readUnsignedByte(in);
return readString(in, len);
}
private static String readLongString(DataInputStream in) throws IOException {
int len = in.readShort();
return readString(in, len); return readString(in, len);
} }
@@ -485,12 +489,23 @@ public class ClsSet {
return new String(bytes, STRING_CHARSET); return new String(bytes, STRING_CHARSET);
} }
private static void writeUnsignedByte(DataOutputStream out, int value) throws IOException {
if (value < 0 || value >= 0xFF) {
throw new JadxRuntimeException("Unsigned byte value is too big: " + value);
}
out.writeByte(value);
}
private static int readUnsignedByte(DataInputStream in) throws IOException {
return ((int) in.readByte()) & 0xFF;
}
public int getClassesCount() { public int getClassesCount() {
return classes.length; return classes.length;
} }
public void addToMap(Map<String, NClass> nameMap) { public void addToMap(Map<String, ClspClass> nameMap) {
for (NClass cls : classes) { for (ClspClass cls : classes) {
nameMap.put(cls.getName(), cls); nameMap.put(cls.getName(), cls);
} }
} }
@@ -0,0 +1,100 @@
package jadx.core.clsp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jadx.core.dex.instructions.args.ArgType;
/**
* Class node in classpath graph
*/
public class ClspClass {
private final ArgType clsType;
private final int id;
private ArgType[] parents;
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
private List<ArgType> typeParameters = Collections.emptyList();
public ClspClass(ArgType clsType, int id) {
this.clsType = clsType;
this.id = id;
}
public String getName() {
return clsType.getObject();
}
public ArgType getClsType() {
return clsType;
}
public int getId() {
return id;
}
public ArgType[] getParents() {
return parents;
}
public void setParents(ArgType[] parents) {
this.parents = parents;
}
public Map<String, ClspMethod> getMethodsMap() {
return methodsMap;
}
public List<ClspMethod> getSortedMethodsList() {
List<ClspMethod> list = new ArrayList<>(methodsMap.size());
list.addAll(methodsMap.values());
Collections.sort(list);
return list;
}
public void setMethodsMap(Map<String, ClspMethod> methodsMap) {
this.methodsMap = Objects.requireNonNull(methodsMap);
}
public void setMethods(List<ClspMethod> methods) {
Map<String, ClspMethod> map = new HashMap<>(methods.size());
for (ClspMethod mth : methods) {
map.put(mth.getMethodInfo().getShortId(), mth);
}
setMethodsMap(map);
}
public List<ArgType> getTypeParameters() {
return typeParameters;
}
public void setTypeParameters(List<ArgType> typeParameters) {
this.typeParameters = typeParameters;
}
@Override
public int hashCode() {
return clsType.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ClspClass nClass = (ClspClass) o;
return clsType.equals(nClass.clsType);
}
@Override
public String toString() {
return clsType.toString();
}
}
@@ -8,7 +8,6 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.WeakHashMap;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -17,6 +16,8 @@ import org.slf4j.LoggerFactory;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -26,13 +27,19 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public class ClspGraph { public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class); private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>()); private final RootNode root;
private Map<String, NClass> nameMap; private Map<String, ClspClass> nameMap;
private Map<String, Set<String>> superTypesCache;
private Map<String, List<String>> implementsCache;
private final Set<String> missingClasses = new HashSet<>(); private final Set<String> missingClasses = new HashSet<>();
public ClspGraph(RootNode rootNode) {
this.root = rootNode;
}
public void load() throws IOException, DecodeException { public void load() throws IOException, DecodeException {
ClsSet set = new ClsSet(); ClsSet set = new ClsSet(root);
set.loadFromClstFile(); set.loadFromClstFile();
addClasspath(set); addClasspath(set);
} }
@@ -50,64 +57,90 @@ public class ClspGraph {
if (nameMap == null) { if (nameMap == null) {
throw new JadxRuntimeException("Classpath must be loaded first"); throw new JadxRuntimeException("Classpath must be loaded first");
} }
int size = classes.size();
NClass[] nClasses = new NClass[size];
int k = 0;
for (ClassNode cls : classes) { for (ClassNode cls : classes) {
nClasses[k++] = addClass(cls); addClass(cls);
}
for (int i = 0; i < size; i++) {
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
} }
} }
public void initCache() {
fillSuperTypesCache();
fillImplementsCache();
}
public boolean isClsKnown(String fullName) { public boolean isClsKnown(String fullName) {
return nameMap.containsKey(fullName); return nameMap.containsKey(fullName);
} }
public NClass getClsDetails(ArgType type) { public ClspClass getClsDetails(ArgType type) {
return nameMap.get(type.getObject()); return nameMap.get(type.getObject());
} }
@Nullable @Nullable
public NMethod getMethodDetails(MethodInfo methodInfo) { public IMethodDetails getMethodDetails(MethodInfo methodInfo) {
NClass cls = nameMap.get(methodInfo.getDeclClass().getRawName()); ClspClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
if (cls == null) { if (cls == null) {
return null; return null;
} }
ClspMethod clspMethod = getMethodFromClass(cls, methodInfo);
if (clspMethod != null) {
return clspMethod;
}
// deep search
for (ArgType parent : cls.getParents()) {
ClspClass clspParent = getClspClass(parent);
if (clspParent != null) {
ClspMethod methodFromParent = getMethodFromClass(clspParent, methodInfo);
if (methodFromParent != null) {
return methodFromParent;
}
}
}
// unknown method
return new SimpleMethodDetails(methodInfo);
}
private ClspMethod getMethodFromClass(ClspClass cls, MethodInfo methodInfo) {
return cls.getMethodsMap().get(methodInfo.getShortId()); return cls.getMethodsMap().get(methodInfo.getShortId());
} }
private NClass addClass(ClassNode cls) { private void addClass(ClassNode cls) {
String rawName = cls.getRawName(); ArgType clsType = cls.getClassInfo().getType();
NClass nClass = new NClass(rawName, -1); String rawName = clsType.getObject();
nameMap.put(rawName, nClass); ClspClass clspClass = new ClspClass(clsType, -1);
return nClass; clspClass.setParents(ClsSet.makeParentsArray(cls));
nameMap.put(rawName, clspClass);
} }
/** /**
* @return {@code clsName} instanceof {@code implClsName} * @return {@code clsName} instanceof {@code implClsName}
*/ */
public boolean isImplements(String clsName, String implClsName) { public boolean isImplements(String clsName, String implClsName) {
Set<String> anc = getAncestors(clsName); Set<String> anc = getSuperTypes(clsName);
return anc.contains(implClsName); return anc.contains(implClsName);
} }
public List<String> getImplementations(String clsName) { public List<String> getImplementations(String clsName) {
List<String> list = new ArrayList<>(); List<String> list = implementsCache.get(clsName);
for (String cls : nameMap.keySet()) { return list == null ? Collections.emptyList() : list;
if (isImplements(cls, clsName)) { }
list.add(cls);
private void fillImplementsCache() {
Map<String, List<String>> map = new HashMap<>(nameMap.size());
List<String> classes = new ArrayList<>(nameMap.keySet());
Collections.sort(classes);
for (String cls : classes) {
for (String st : getSuperTypes(cls)) {
map.computeIfAbsent(st, v -> new ArrayList<>()).add(cls);
} }
} }
return list; implementsCache = map;
} }
public String getCommonAncestor(String clsName, String implClsName) { public String getCommonAncestor(String clsName, String implClsName) {
if (clsName.equals(implClsName)) { if (clsName.equals(implClsName)) {
return clsName; return clsName;
} }
NClass cls = nameMap.get(implClsName); ClspClass cls = nameMap.get(implClsName);
if (cls == null) { if (cls == null) {
missingClasses.add(clsName); missingClasses.add(clsName);
return null; return null;
@@ -115,52 +148,74 @@ public class ClspGraph {
if (isImplements(clsName, implClsName)) { if (isImplements(clsName, implClsName)) {
return implClsName; return implClsName;
} }
Set<String> anc = getAncestors(clsName); Set<String> anc = getSuperTypes(clsName);
return searchCommonParent(anc, cls); return searchCommonParent(anc, cls);
} }
private String searchCommonParent(Set<String> anc, NClass cls) { private String searchCommonParent(Set<String> anc, ClspClass cls) {
for (NClass p : cls.getParents()) { for (ArgType p : cls.getParents()) {
String name = p.getName(); String name = p.getObject();
if (anc.contains(name)) { if (anc.contains(name)) {
return name; return name;
} }
String r = searchCommonParent(anc, p); ClspClass nCls = getClspClass(p);
if (r != null) { if (nCls != null) {
return r; String r = searchCommonParent(anc, nCls);
if (r != null) {
return r;
}
} }
} }
return null; return null;
} }
public Set<String> getAncestors(String clsName) { public Set<String> getSuperTypes(String clsName) {
Set<String> result = ancestorCache.get(clsName); Set<String> result = superTypesCache.get(clsName);
if (result != null) { return result == null ? Collections.emptySet() : result;
return result;
}
NClass cls = nameMap.get(clsName);
if (cls == null) {
missingClasses.add(clsName);
return Collections.emptySet();
}
result = new HashSet<>();
addAncestorsNames(cls, result);
if (result.isEmpty()) {
result = Collections.emptySet();
}
ancestorCache.put(clsName, result);
return result;
} }
private void addAncestorsNames(NClass cls, Set<String> result) { private void fillSuperTypesCache() {
boolean isNew = result.add(cls.getName()); Map<String, Set<String>> map = new HashMap<>(nameMap.size());
if (isNew) { Set<String> tmpSet = new HashSet<>();
for (NClass p : cls.getParents()) { for (Map.Entry<String, ClspClass> entry : nameMap.entrySet()) {
addAncestorsNames(p, result); 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 = map;
}
private void addSuperTypes(ClspClass cls, Set<String> result) {
for (ArgType parentType : cls.getParents()) {
if (parentType == null) {
continue;
}
ClspClass parentCls = getClspClass(parentType);
if (parentCls != null) {
boolean isNew = result.add(parentCls.getName());
if (isNew) {
addSuperTypes(parentCls, result);
}
} }
} }
} }
@Nullable
private ClspClass getClspClass(ArgType clsType) {
ClspClass clspClass = nameMap.get(clsType.getObject());
if (clspClass == null) {
missingClasses.add(clsType.getObject());
}
return clspClass;
}
public void printMissingClasses() { public void printMissingClasses() {
int count = missingClasses.size(); int count = missingClasses.size();
if (count == 0) { if (count == 0) {
@@ -0,0 +1,131 @@
package jadx.core.clsp;
import java.util.List;
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;
import jadx.core.utils.Utils;
/**
* Method node in classpath graph.
*/
public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
private final MethodInfo methodInfo;
private final List<ArgType> argTypes;
private final ArgType returnType;
private final List<ArgType> typeParameters;
private final List<ArgType> throwList;
private final int accFlags;
public ClspMethod(MethodInfo methodInfo,
List<ArgType> argTypes, ArgType returnType,
List<ArgType> typeParameters, List<ArgType> throwList, int accFlags) {
this.methodInfo = methodInfo;
this.argTypes = argTypes;
this.returnType = returnType;
this.typeParameters = typeParameters;
this.throwList = throwList;
this.accFlags = accFlags;
}
@Override
public MethodInfo getMethodInfo() {
return methodInfo;
}
@Override
public ArgType getReturnType() {
return returnType;
}
@Override
public List<ArgType> getArgTypes() {
return argTypes;
}
public boolean containsGenericArgs() {
return !Objects.equals(argTypes, methodInfo.getArgumentsTypes());
}
public int getArgsCount() {
return argTypes.size();
}
@Override
public List<ArgType> getTypeParameters() {
return typeParameters;
}
@Override
public List<ArgType> getThrows() {
return throwList;
}
@Override
public boolean isVarArg() {
return (accFlags & AccessFlags.VARARGS) != 0;
}
@Override
public int getRawAccessFlags() {
return accFlags;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ClspMethod)) {
return false;
}
ClspMethod other = (ClspMethod) o;
return methodInfo.equals(other.methodInfo);
}
@Override
public int hashCode() {
return methodInfo.hashCode();
}
@Override
public int compareTo(@NotNull ClspMethod other) {
return this.methodInfo.compareTo(other.methodInfo);
}
@Override
public String toAttrString() {
return IMethodDetails.super.toAttrString() + " (c)";
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("ClspMth{");
if (Utils.notEmpty(getTypeParameters())) {
sb.append('<');
sb.append(Utils.listToString(getTypeParameters()));
sb.append("> ");
}
sb.append(getMethodInfo().getFullName());
sb.append('(');
sb.append(Utils.listToString(getArgTypes()));
sb.append("):");
sb.append(getReturnType());
if (isVarArg()) {
sb.append(" VARARG");
}
List<ArgType> throwsList = getThrows();
if (Utils.notEmpty(throwsList)) {
sb.append(" throws ").append(Utils.listToString(throwsList));
}
sb.append('}');
return sb.toString();
}
}
@@ -1,75 +0,0 @@
package jadx.core.clsp;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
/**
* Utility class for convert dex or jar to jadx classes set (.jcst)
*/
public class ConvertToClsSet {
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
public static void usage() {
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
}
public static void main(String[] args) throws IOException, DecodeException {
if (args.length < 2) {
usage();
System.exit(1);
}
Path output = Paths.get(args[0]);
List<InputFile> inputFiles = new ArrayList<>(args.length - 1);
for (int i = 1; i < args.length; i++) {
File f = new File(args[i]);
if (f.isDirectory()) {
addFilesFromDirectory(f, inputFiles);
} else {
InputFile.addFilesFrom(f, inputFiles, false);
}
}
for (InputFile inputFile : inputFiles) {
LOG.info("Loaded: {}", inputFile.getFile());
}
RootNode root = new RootNode(new JadxArgs());
root.load(inputFiles);
ClsSet set = new ClsSet();
set.loadFrom(root);
set.save(output);
LOG.info("Output: {}", output);
LOG.info("done");
}
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) {
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
addFilesFromDirectory(file, inputFiles);
} else {
try {
InputFile.addFilesFrom(file, inputFiles, false);
} catch (Exception e) {
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
}
}
}
}
}
@@ -1,96 +0,0 @@
package jadx.core.clsp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jadx.core.dex.nodes.GenericInfo;
/**
* Class node in classpath graph
*/
public class NClass {
private final String name;
private final int id;
private NClass[] parents;
private Map<String, NMethod> methodsMap = Collections.emptyMap();
private List<GenericInfo> generics = Collections.emptyList();
public NClass(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public NClass[] getParents() {
return parents;
}
public void setParents(NClass[] parents) {
this.parents = parents;
}
public Map<String, NMethod> getMethodsMap() {
return methodsMap;
}
public List<NMethod> getMethodsList() {
List<NMethod> list = new ArrayList<>(methodsMap.size());
list.addAll(methodsMap.values());
Collections.sort(list);
return list;
}
public void setMethodsMap(Map<String, NMethod> methodsMap) {
this.methodsMap = Objects.requireNonNull(methodsMap);
}
public void setMethods(List<NMethod> methods) {
Map<String, NMethod> map = new HashMap<>(methods.size());
for (NMethod mth : methods) {
map.put(mth.getShortId(), mth);
}
setMethodsMap(map);
}
public List<GenericInfo> getGenerics() {
return generics;
}
public void setGenerics(List<GenericInfo> generics) {
this.generics = generics;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NClass nClass = (NClass) o;
return name.equals(nClass.name);
}
@Override
public String toString() {
return name;
}
}
@@ -1,92 +0,0 @@
package jadx.core.clsp;
import java.util.Arrays;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.instructions.args.ArgType;
/**
* Generic method node in classpath graph.
*/
public class NMethod implements Comparable<NMethod> {
private final String shortId;
/**
* Array contains only generic args, others set to 'null', size can be less than total args count
*/
@Nullable
private final ArgType[] genericArgs;
@Nullable
private final ArgType retType;
private final boolean varArgs;
public NMethod(String shortId, @Nullable ArgType[] genericArgs, @Nullable ArgType retType, boolean varArgs) {
this.shortId = shortId;
this.genericArgs = genericArgs;
this.retType = retType;
this.varArgs = varArgs;
}
public String getShortId() {
return shortId;
}
@Nullable
public ArgType[] getGenericArgs() {
return genericArgs;
}
@Nullable
public ArgType getGenericArg(int i) {
ArgType[] args = this.genericArgs;
if (args != null && i < args.length) {
return args[i];
}
return null;
}
@Nullable
public ArgType getReturnType() {
return retType;
}
public boolean isVarArgs() {
return varArgs;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof NMethod)) {
return false;
}
NMethod other = (NMethod) o;
return shortId.equals(other.shortId);
}
@Override
public int hashCode() {
return shortId.hashCode();
}
@Override
public int compareTo(@NotNull NMethod other) {
return this.shortId.compareTo(other.shortId);
}
@Override
public String toString() {
return "NMethod{'" + shortId + '\''
+ ", argTypes=" + Arrays.toString(genericArgs)
+ ", retType=" + retType
+ ", varArgs=" + varArgs
+ '}';
}
}
@@ -0,0 +1,67 @@
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;
public SimpleMethodDetails(MethodInfo methodInfo) {
this.methodInfo = methodInfo;
}
@Override
public MethodInfo getMethodInfo() {
return methodInfo;
}
@Override
public ArgType getReturnType() {
return methodInfo.getReturnType();
}
@Override
public List<ArgType> getArgTypes() {
return methodInfo.getArgumentsTypes();
}
@Override
public List<ArgType> getTypeParameters() {
return Collections.emptyList();
}
@Override
public List<ArgType> getThrows() {
return Collections.emptyList();
}
@Override
public boolean isVarArg() {
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,17 +7,22 @@ import java.util.Map.Entry;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
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.Consts;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode; import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.StringUtils; import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -31,61 +36,61 @@ public class AnnotationGen {
this.classGen = classGen; this.classGen = classGen;
} }
public void addForClass(CodeWriter code) { public void addForClass(ICodeWriter code) {
add(cls, code); add(cls, code);
} }
public void addForMethod(CodeWriter code, MethodNode mth) { public void addForMethod(ICodeWriter code, MethodNode mth) {
add(mth, code); add(mth, code);
} }
public void addForField(CodeWriter code, FieldNode field) { public void addForField(ICodeWriter code, FieldNode field) {
add(field, code); add(field, code);
} }
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) { public void addForParameter(ICodeWriter code, MethodParamsAttr paramsAnnotations, int n) {
List<AnnotationsList> paramList = paramsAnnotations.getParamList(); List<AnnotationsAttr> paramList = paramsAnnotations.getParamList();
if (n >= paramList.size()) { if (n >= paramList.size()) {
return; return;
} }
AnnotationsList aList = paramList.get(n); AnnotationsAttr aList = paramList.get(n);
if (aList == null || aList.isEmpty()) { if (aList == null || aList.isEmpty()) {
return; return;
} }
for (Annotation a : aList.getAll()) { for (IAnnotation a : aList.getAll()) {
formatAnnotation(code, a); formatAnnotation(code, a);
code.add(' '); code.add(' ');
} }
} }
private void add(IAttributeNode node, CodeWriter code) { private void add(IAttributeNode node, ICodeWriter code) {
AnnotationsList aList = node.get(AType.ANNOTATION_LIST); AnnotationsAttr aList = node.get(JadxAttrType.ANNOTATION_LIST);
if (aList == null || aList.isEmpty()) { if (aList == null || aList.isEmpty()) {
return; return;
} }
for (Annotation a : aList.getAll()) { for (IAnnotation a : aList.getAll()) {
String aCls = a.getAnnotationClass(); String aCls = a.getAnnotationClass();
if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) { if (!aCls.equals(Consts.OVERRIDE_ANNOTATION)) {
code.startLine(); code.startLine();
formatAnnotation(code, a); formatAnnotation(code, a);
} }
} }
} }
private void formatAnnotation(CodeWriter code, Annotation a) { private void formatAnnotation(ICodeWriter code, IAnnotation a) {
code.add('@'); code.add('@');
ClassNode annCls = cls.dex().resolveClass(a.getType()); ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass());
if (annCls != null) { if (annCls != null) {
classGen.useClass(code, annCls); classGen.useClass(code, annCls);
} else { } else {
classGen.useType(code, a.getType()); classGen.useClass(code, a.getAnnotationClass());
} }
Map<String, Object> vl = a.getValues(); Map<String, EncodedValue> vl = a.getValues();
if (!vl.isEmpty()) { if (!vl.isEmpty()) {
code.add('('); code.add('(');
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext();) { for (Iterator<Entry<String, EncodedValue>> it = vl.entrySet().iterator(); it.hasNext();) {
Entry<String, Object> e = it.next(); Entry<String, EncodedValue> e = it.next();
String paramName = getParamName(annCls, e.getKey()); String paramName = getParamName(annCls, e.getKey());
if (paramName.equals("value") && vl.size() == 1) { if (paramName.equals("value") && vl.size() == 1) {
// don't add "value = " if no other parameters // don't add "value = " if no other parameters
@@ -93,7 +98,7 @@ public class AnnotationGen {
code.add(paramName); code.add(paramName);
code.add(" = "); code.add(" = ");
} }
encodeValue(code, e.getValue()); encodeValue(cls.root(), code, e.getValue());
if (it.hasNext()) { if (it.hasNext()) {
code.add(", "); code.add(", ");
} }
@@ -113,13 +118,11 @@ public class AnnotationGen {
return paramName; return paramName;
} }
@SuppressWarnings("unchecked") public void addThrows(MethodNode mth, ICodeWriter code) {
public void addThrows(MethodNode mth, CodeWriter code) { List<ArgType> throwList = mth.getThrows();
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS); if (!throwList.isEmpty()) {
if (an != null) {
Object exs = an.getDefaultValue();
code.add(" throws "); code.add(" throws ");
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext();) { for (Iterator<ArgType> it = throwList.iterator(); it.hasNext();) {
ArgType ex = it.next(); ArgType ex = it.next();
classGen.useType(code, ex); classGen.useType(code, ex);
if (it.hasNext()) { if (it.hasNext()) {
@@ -129,66 +132,93 @@ public class AnnotationGen {
} }
} }
public Object getAnnotationDefaultValue(String name) { public EncodedValue getAnnotationDefaultValue(MethodNode mth) {
Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT); AnnotationDefaultAttr defaultAttr = mth.get(JadxAttrType.ANNOTATION_DEFAULT);
if (an != null) { if (defaultAttr == null) {
Annotation defAnnotation = (Annotation) an.getDefaultValue(); return null;
return defAnnotation.getValues().get(name);
} }
return null; return defaultAttr.getValue();
} }
// TODO: refactor this boilerplate code // TODO: refactor this boilerplate code
public void encodeValue(CodeWriter code, Object val) { public void encodeValue(RootNode root, ICodeWriter code, EncodedValue encodedValue) {
if (val == null) { if (encodedValue == null) {
code.add("null"); code.add("null");
return; return;
} }
if (val instanceof String) { Object value = encodedValue.getValue();
code.add(getStringUtils().unescapeString((String) val)); switch (encodedValue.getType()) {
} else if (val instanceof Integer) { case ENCODED_NULL:
code.add(TypeGen.formatInteger((Integer) val, false)); code.add("null");
} else if (val instanceof Character) { break;
code.add(getStringUtils().unescapeChar((Character) val)); case ENCODED_BOOLEAN:
} else if (val instanceof Boolean) { code.add(Boolean.TRUE.equals(value) ? "true" : "false");
code.add(Boolean.TRUE.equals(val) ? "true" : "false"); break;
} else if (val instanceof Float) { case ENCODED_BYTE:
code.add(TypeGen.formatFloat((Float) val)); code.add(TypeGen.formatByte((Byte) value, false));
} else if (val instanceof Double) { break;
code.add(TypeGen.formatDouble((Double) val)); case ENCODED_SHORT:
} else if (val instanceof Long) { code.add(TypeGen.formatShort((Short) value, false));
code.add(TypeGen.formatLong((Long) val, false)); break;
} else if (val instanceof Short) { case ENCODED_CHAR:
code.add(TypeGen.formatShort((Short) val, false)); code.add(getStringUtils().unescapeChar((Character) value));
} else if (val instanceof Byte) { break;
code.add(TypeGen.formatByte((Byte) val, false)); case ENCODED_INT:
} else if (val instanceof ArgType) { code.add(TypeGen.formatInteger((Integer) value, false));
classGen.useType(code, (ArgType) val); break;
code.add(".class"); case ENCODED_LONG:
} else if (val instanceof FieldInfo) { code.add(TypeGen.formatLong((Long) value, false));
// must be a static field break;
FieldInfo field = (FieldInfo) val; case ENCODED_FLOAT:
InsnGen.makeStaticFieldAccess(code, field, classGen); code.add(TypeGen.formatFloat((Float) value));
} else if (val instanceof Iterable) { break;
code.add('{'); case ENCODED_DOUBLE:
Iterator<?> it = ((Iterable<?>) val).iterator(); code.add(TypeGen.formatDouble((Double) value));
while (it.hasNext()) { break;
Object obj = it.next(); case ENCODED_STRING:
encodeValue(code, obj); code.add(getStringUtils().unescapeString((String) value));
if (it.hasNext()) { break;
code.add(", "); case ENCODED_TYPE:
classGen.useType(code, ArgType.parse((String) value));
code.add(".class");
break;
case ENCODED_ENUM:
case ENCODED_FIELD:
// must be a static field
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 {
throw new JadxRuntimeException("Unexpected field type class: " + value.getClass());
} }
} break;
code.add('}'); case ENCODED_METHOD:
} else if (val instanceof Annotation) { // TODO
formatAnnotation(code, (Annotation) val); break;
} else { case ENCODED_ARRAY:
// TODO: also can be method values code.add('{');
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ')'); Iterator<?> it = ((Iterable<?>) value).iterator();
while (it.hasNext()) {
EncodedValue v = (EncodedValue) it.next();
encodeValue(cls.root(), code, v);
if (it.hasNext()) {
code.add(", ");
}
}
code.add('}');
break;
case ENCODED_ANNOTATION:
formatAnnotation(code, (IAnnotation) value);
break;
default:
throw new JadxRuntimeException("Can't decode value: " + encodedValue.getType() + " (" + encodedValue + ')');
} }
} }
private StringUtils getStringUtils() { private StringUtils getStringUtils() {
return cls.dex().root().getStringUtils(); return cls.root().getStringUtils();
} }
} }
@@ -1,6 +1,7 @@
package jadx.core.codegen; package jadx.core.codegen;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
@@ -8,34 +9,44 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.android.dx.rop.code.AccessFlags; import org.jetbrains.annotations.NotNull;
import com.google.common.collect.Streams; import org.jetbrains.annotations.Nullable;
import jadx.api.CommentsLevel;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.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.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode; import jadx.core.dex.attributes.FieldInitInsnAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField; import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.GenericInfo;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
import jadx.core.utils.CodeGenUtils; import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.ErrorsCounter; import jadx.core.utils.EncodedValueUtils;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -49,7 +60,12 @@ public class ClassGen {
private final boolean showInconsistentCode; private final boolean showInconsistentCode;
private final Set<ClassInfo> imports = new HashSet<>(); private final Set<ClassInfo> imports = new HashSet<>();
private int clsDeclLine; private int clsDeclOffset;
private boolean bodyGenStarted;
@Nullable
private NameGen outerNameGen;
public ClassGen(ClassNode cls, JadxArgs jadxArgs) { public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode()); this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
@@ -73,11 +89,11 @@ public class ClassGen {
return cls; return cls;
} }
public CodeWriter makeClass() throws CodegenException { public ICodeInfo makeClass() throws CodegenException {
CodeWriter clsBody = new CodeWriter(); ICodeWriter clsBody = cls.root().makeCodeWriter();
addClassCode(clsBody); addClassCode(clsBody);
CodeWriter clsCode = new CodeWriter(); ICodeWriter clsCode = cls.root().makeCodeWriter();
if (!"".equals(cls.getPackage())) { if (!"".equals(cls.getPackage())) {
clsCode.add("package ").add(cls.getPackage()).add(';'); clsCode.add("package ").add(cls.getPackage()).add(';');
clsCode.newLine(); clsCode.newLine();
@@ -102,37 +118,39 @@ public class ClassGen {
return clsCode.finish(); return clsCode.finish();
} }
public void addClassCode(CodeWriter code) throws CodegenException { public void addClassCode(ICodeWriter code) throws CodegenException {
if (cls.contains(AFlag.DONT_GENERATE)) { if (cls.contains(AFlag.DONT_GENERATE)) {
return; return;
} }
CodeGenUtils.addComments(code, cls); if (Consts.DEBUG_USAGE) {
insertDecompilationProblems(code, cls); addClassUsageInfo(code, cls);
}
CodeGenUtils.addErrorsAndComments(code, cls);
CodeGenUtils.addSourceFileInfo(code, cls);
addClassDeclaration(code); addClassDeclaration(code);
addClassBody(code); addClassBody(code);
} }
public void addClassDeclaration(CodeWriter clsCode) { public void addClassDeclaration(ICodeWriter clsCode) {
AccessInfo af = cls.getAccessFlags(); AccessInfo af = cls.getAccessFlags();
if (af.isInterface()) { if (af.isInterface()) {
af = af.remove(AccessFlags.ACC_ABSTRACT) af = af.remove(AccessFlags.ABSTRACT)
.remove(AccessFlags.ACC_STATIC); .remove(AccessFlags.STATIC);
} else if (af.isEnum()) { } else if (af.isEnum()) {
af = af.remove(AccessFlags.ACC_FINAL) af = af.remove(AccessFlags.FINAL)
.remove(AccessFlags.ACC_ABSTRACT) .remove(AccessFlags.ABSTRACT)
.remove(AccessFlags.ACC_STATIC); .remove(AccessFlags.STATIC);
} }
// 'static' and 'private' modifier not allowed for top classes (not inner) // 'static' and 'private' modifier not allowed for top classes (not inner)
if (!cls.getClassInfo().isInner()) { if (!cls.getClassInfo().isInner()) {
af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE); af = af.remove(AccessFlags.STATIC).remove(AccessFlags.PRIVATE);
} }
annotationGen.addForClass(clsCode); annotationGen.addForClass(clsCode);
insertRenameInfo(clsCode, cls); insertRenameInfo(clsCode, cls);
CodeGenUtils.addSourceFileInfo(clsCode, cls); CodeGenUtils.addInputFileInfo(clsCode, cls);
clsCode.startLineWithNum(cls.getSourceLine()); clsCode.startLineWithNum(cls.getSourceLine()).add(af.makeString(cls.checkCommentsLevel(CommentsLevel.INFO)));
clsCode.add(af.makeString());
if (af.isInterface()) { if (af.isInterface()) {
if (af.isAnnotation()) { if (af.isAnnotation()) {
clsCode.add('@'); clsCode.add('@');
@@ -146,7 +164,7 @@ public class ClassGen {
clsCode.attachDefinition(cls); clsCode.attachDefinition(cls);
clsCode.add(cls.getClassInfo().getAliasShortName()); clsCode.add(cls.getClassInfo().getAliasShortName());
addGenericMap(clsCode, cls.getGenerics(), true); addGenericTypeParameters(clsCode, cls.getGenericTypeParameters(), true);
clsCode.add(' '); clsCode.add(' ');
ArgType sup = cls.getSuperClass(); ArgType sup = cls.getSuperClass();
@@ -177,23 +195,22 @@ public class ClassGen {
} }
} }
public boolean addGenericMap(CodeWriter code, List<GenericInfo> generics, boolean classDeclaration) { public boolean addGenericTypeParameters(ICodeWriter code, List<ArgType> generics, boolean classDeclaration) {
if (generics == null || generics.isEmpty()) { if (generics == null || generics.isEmpty()) {
return false; return false;
} }
code.add('<'); code.add('<');
int i = 0; int i = 0;
for (GenericInfo genericInfo : generics) { for (ArgType genericInfo : generics) {
if (i != 0) { if (i != 0) {
code.add(", "); code.add(", ");
} }
ArgType type = genericInfo.getGenericType(); if (genericInfo.isGenericType()) {
if (type.isGenericType()) { code.add(genericInfo.getObject());
code.add(type.getObject());
} else { } else {
useClass(code, type); useClass(code, genericInfo);
} }
List<ArgType> list = genericInfo.getExtendsList(); List<ArgType> list = genericInfo.getExtendTypes();
if (list != null && !list.isEmpty()) { if (list != null && !list.isEmpty()) {
code.add(" extends "); code.add(" extends ");
for (Iterator<ArgType> it = list.iterator(); it.hasNext();) { for (Iterator<ArgType> it = list.iterator(); it.hasNext();) {
@@ -219,9 +236,21 @@ public class ClassGen {
return true; return true;
} }
public void addClassBody(CodeWriter clsCode) throws CodegenException { public void addClassBody(ICodeWriter clsCode) throws CodegenException {
addClassBody(clsCode, false);
}
/**
* @param printClassName allows to print the original class name as comment (e.g. for inlined
* classes)
*/
public void addClassBody(ICodeWriter clsCode, boolean printClassName) throws CodegenException {
clsCode.add('{'); clsCode.add('{');
clsDeclLine = clsCode.getLine(); if (printClassName && cls.checkCommentsLevel(CommentsLevel.INFO)) {
clsCode.add(" // from class: " + cls.getClassInfo().getFullName());
}
setBodyGenStarted(true);
clsDeclOffset = clsCode.getLength();
clsCode.incIndent(); clsCode.incIndent();
addFields(clsCode); addFields(clsCode);
addInnerClsAndMethods(clsCode); addInnerClsAndMethods(clsCode);
@@ -229,9 +258,10 @@ public class ClassGen {
clsCode.startLine('}'); clsCode.startLine('}');
} }
private void addInnerClsAndMethods(CodeWriter clsCode) { private void addInnerClsAndMethods(ICodeWriter clsCode) {
Streams.concat(cls.getInnerClasses().stream(), cls.getMethods().stream()) Stream.of(cls.getInnerClasses(), cls.getMethods())
.filter(node -> !node.contains(AFlag.DONT_GENERATE)) .flatMap(Collection::stream)
.filter(node -> !node.contains(AFlag.DONT_GENERATE) || fallback)
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine)) .sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
.forEach(node -> { .forEach(node -> {
if (node instanceof ClassNode) { if (node instanceof ClassNode) {
@@ -242,28 +272,31 @@ public class ClassGen {
}); });
} }
private void addInnerClass(CodeWriter code, ClassNode innerCls) { private void addInnerClass(ICodeWriter code, ClassNode innerCls) {
try { try {
ClassGen inClGen = new ClassGen(innerCls, getParentGen()); ClassGen inClGen = new ClassGen(innerCls, getParentGen());
code.newLine(); code.newLine();
inClGen.addClassCode(code); inClGen.addClassCode(code);
imports.addAll(inClGen.getImports()); imports.addAll(inClGen.getImports());
} catch (Exception e) { } catch (Exception e) {
ErrorsCounter.classError(innerCls, "Inner class code generation error", e); innerCls.addError("Inner class code generation error", e);
} }
} }
private boolean isInnerClassesPresents() { private boolean isInnerClassesPresents() {
for (ClassNode innerCls : cls.getInnerClasses()) { for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) { if (!innerCls.contains(AType.ANONYMOUS_CLASS)) {
return true; return true;
} }
} }
return false; return false;
} }
private void addMethod(CodeWriter code, MethodNode mth) { private void addMethod(ICodeWriter code, MethodNode mth) {
if (code.getLine() != clsDeclLine) { if (skipMethod(mth)) {
return;
}
if (code.getLength() != clsDeclOffset) {
code.newLine(); code.newLine();
} }
int savedIndent = code.getIndent(); int savedIndent = code.getIndent();
@@ -273,15 +306,35 @@ public class ClassGen {
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) { if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw new JadxRuntimeException("Method generation error", e); throw new JadxRuntimeException("Method generation error", e);
} }
code.newLine().add("/*"); mth.addError("Method generation error", e);
code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e)); CodeGenUtils.addErrors(code, mth);
Utils.appendStackTrace(code, e);
code.newLine().add("*/");
code.setIndent(savedIndent); code.setIndent(savedIndent);
mth.addError("Method generation error: " + e.getMessage(), e);
} }
} }
/**
* Additional checks for inlined methods
*/
private boolean skipMethod(MethodNode mth) {
MethodInlineAttr inlineAttr = mth.get(AType.METHOD_INLINE);
if (inlineAttr == null || inlineAttr.notNeeded()) {
return false;
}
if (mth.getUseIn().isEmpty()) {
mth.add(AFlag.DONT_GENERATE);
return true;
}
List<MethodNode> useInCompleted = mth.getUseIn().stream()
.filter(m -> m.getTopParentClass().getState().isProcessComplete())
.collect(Collectors.toList());
if (useInCompleted.isEmpty()) {
mth.add(AFlag.DONT_GENERATE);
return true;
}
mth.addDebugComment("Method not inlined, still used in: " + useInCompleted);
return false;
}
private boolean isMethodsPresents() { private boolean isMethodsPresents() {
for (MethodNode mth : cls.getMethods()) { for (MethodNode mth : cls.getMethods()) {
if (!mth.contains(AFlag.DONT_GENERATE)) { if (!mth.contains(AFlag.DONT_GENERATE)) {
@@ -291,17 +344,15 @@ public class ClassGen {
return false; return false;
} }
public void addMethodCode(CodeWriter code, MethodNode mth) throws CodegenException { public void addMethodCode(ICodeWriter code, MethodNode mth) throws CodegenException {
CodeGenUtils.addComments(code, mth); CodeGenUtils.addErrorsAndComments(code, mth);
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) { if (mth.isNoCode()) {
MethodGen mthGen = new MethodGen(this, mth); MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code); mthGen.addDefinition(code);
code.add(';'); code.add(';');
} else { } else {
insertDecompilationProblems(code, mth);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE); boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode && showInconsistentCode) { if (badCode && showInconsistentCode) {
mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false; badCode = false;
} }
MethodGen mthGen; MethodGen mthGen;
@@ -321,61 +372,56 @@ public class ClassGen {
} }
} }
public void insertDecompilationProblems(CodeWriter code, AttrNode node) { private void addFields(ICodeWriter code) throws CodegenException {
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
if (!errors.isEmpty()) {
errors.stream().distinct().sorted().forEach(err -> {
code.startLine("/* JADX ERROR: ").add(err.getError());
Throwable cause = err.getCause();
if (cause != null) {
code.incIndent();
Utils.appendStackTrace(code, cause);
code.decIndent();
}
code.add("*/");
});
}
List<String> warns = node.getAll(AType.JADX_WARN);
if (!warns.isEmpty()) {
warns.stream().distinct().sorted()
.forEach(warn -> code.startLine("/* JADX WARNING: ").addMultiLine(warn).add(" */"));
}
}
private void addFields(CodeWriter code) throws CodegenException {
addEnumFields(code); addEnumFields(code);
for (FieldNode f : cls.getFields()) { for (FieldNode f : cls.getFields()) {
addField(code, f); addField(code, f);
} }
} }
public void addField(CodeWriter code, FieldNode f) { public void addField(ICodeWriter code, FieldNode f) {
if (f.contains(AFlag.DONT_GENERATE)) { if (f.contains(AFlag.DONT_GENERATE)) {
return; return;
} }
if (Consts.DEBUG_USAGE) {
addFieldUsageInfo(code, f);
}
CodeGenUtils.addComments(code, f); CodeGenUtils.addComments(code, f);
annotationGen.addForField(code, f); annotationGen.addForField(code, f);
if (f.getFieldInfo().isRenamed()) { boolean addInfoComments = f.checkCommentsLevel(CommentsLevel.INFO);
if (f.getFieldInfo().isRenamed() && addInfoComments) {
code.newLine(); code.newLine();
CodeGenUtils.addRenamedComment(code, f, f.getName()); CodeGenUtils.addRenamedComment(code, f, f.getName());
} }
code.startLine(f.getAccessFlags().makeString()); code.startLine(f.getAccessFlags().makeString(addInfoComments));
useType(code, f.getType()); useType(code, f.getType());
code.add(' '); code.add(' ');
code.attachDefinition(f); code.attachDefinition(f);
code.add(f.getAlias()); code.add(f.getAlias());
FieldInitAttr fv = f.get(AType.FIELD_INIT);
if (fv != null) { FieldInitInsnAttr initInsnAttr = f.get(AType.FIELD_INIT_INSN);
if (initInsnAttr != null) {
InsnGen insnGen = makeInsnGen(initInsnAttr.getInsnMth());
code.add(" = "); code.add(" = ");
if (fv.getValue() == null) { addInsnBody(insnGen, code, initInsnAttr.getInsn());
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback)); } else {
} else { EncodedValue constVal = f.get(JadxAttrType.CONSTANT_VALUE);
if (fv.getValueType() == InitType.CONST) { if (constVal != null) {
annotationGen.encodeValue(code, fv.getValue()); code.add(" = ");
} else if (fv.getValueType() == InitType.INSN) { if (constVal.getType() == EncodedType.ENCODED_NULL) {
InsnGen insnGen = makeInsnGen(fv.getInsnMth()); code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
addInsnBody(insnGen, code, fv.getInsn()); } else {
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);
}
} }
} }
} }
@@ -391,7 +437,7 @@ public class ClassGen {
return false; return false;
} }
private void addEnumFields(CodeWriter code) throws CodegenException { private void addEnumFields(ICodeWriter code) throws CodegenException {
EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS); EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS);
if (enumFields == null) { if (enumFields == null) {
return; return;
@@ -399,18 +445,21 @@ public class ClassGen {
InsnGen igen = null; InsnGen igen = null;
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext();) { for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext();) {
EnumField f = it.next(); EnumField f = it.next();
CodeGenUtils.addComments(code, f.getField());
code.startLine(f.getField().getAlias()); code.startLine(f.getField().getAlias());
ConstructorInsn constrInsn = f.getConstrInsn(); ConstructorInsn constrInsn = f.getConstrInsn();
if (constrInsn.getArgsCount() > f.getStartArg()) { MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth());
int skipCount = getEnumCtrSkipArgsCount(callMth);
if (constrInsn.getArgsCount() > skipCount) {
if (igen == null) { if (igen == null) {
igen = makeInsnGen(enumFields.getStaticMethod()); igen = makeInsnGen(enumFields.getStaticMethod());
} }
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth()); igen.generateMethodArguments(code, constrInsn, 0, callMth);
igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth);
} }
if (f.getCls() != null) { if (f.getCls() != null) {
code.add(' '); code.add(' ');
new ClassGen(f.getCls(), this).addClassBody(code); new ClassGen(f.getCls(), this).addClassBody(code, true);
} }
if (it.hasNext()) { if (it.hasNext()) {
code.add(','); code.add(',');
@@ -427,20 +476,30 @@ public class ClassGen {
} }
} }
private int getEnumCtrSkipArgsCount(@Nullable MethodNode callMth) {
if (callMth != null) {
SkipMethodArgsAttr skipArgsAttr = callMth.get(AType.SKIP_MTH_ARGS);
if (skipArgsAttr != null) {
return skipArgsAttr.getSkipCount();
}
}
return 0;
}
private InsnGen makeInsnGen(MethodNode mth) { private InsnGen makeInsnGen(MethodNode mth) {
MethodGen mthGen = new MethodGen(this, mth); MethodGen mthGen = new MethodGen(this, mth);
return new InsnGen(mthGen, false); return new InsnGen(mthGen, false);
} }
private void addInsnBody(InsnGen insnGen, CodeWriter code, InsnNode insn) { private void addInsnBody(InsnGen insnGen, ICodeWriter code, InsnNode insn) {
try { try {
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP); insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
} catch (Exception e) { } catch (Exception e) {
ErrorsCounter.classError(cls, "Failed to generate init code", e); cls.addError("Failed to generate init code", e);
} }
} }
public void useType(CodeWriter code, ArgType type) { public void useType(ICodeWriter code, ArgType type) {
PrimitiveType stype = type.getPrimitiveType(); PrimitiveType stype = type.getPrimitiveType();
if (stype == null) { if (stype == null) {
code.add(type.toString()); code.add(type.toString());
@@ -458,25 +517,60 @@ public class ClassGen {
} }
} }
public void useClass(CodeWriter code, ArgType type) { public void useClass(ICodeWriter code, String rawCls) {
useClass(code, ArgType.object(rawCls));
}
public void useClass(ICodeWriter code, ArgType type) {
ArgType outerType = type.getOuterType(); ArgType outerType = type.getOuterType();
if (outerType != null) { if (outerType != null) {
useClass(code, outerType); useClass(code, outerType);
code.add('.'); code.add('.');
useClass(code, type.getInnerType()); addInnerType(code, type);
return; return;
} }
useClass(code, ClassInfo.fromType(cls.root(), type)); useClass(code, ClassInfo.fromType(cls.root(), type));
ArgType[] generics = type.getGenericTypes(); 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) { if (generics != null) {
code.add('<'); code.add('<');
int len = generics.length; int len = generics.size();
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
if (i != 0) { if (i != 0) {
code.add(", "); code.add(", ");
} }
ArgType gt = generics[i]; ArgType gt = generics.get(i);
ArgType wt = gt.getWildcardType(); ArgType wt = gt.getWildcardType();
if (wt != null) { if (wt != null) {
ArgType.WildcardBound bound = gt.getWildcardBound(); ArgType.WildcardBound bound = gt.getWildcardBound();
@@ -492,8 +586,8 @@ public class ClassGen {
} }
} }
public void useClass(CodeWriter code, ClassInfo classInfo) { public void useClass(ICodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo); ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) { if (classNode != null) {
useClass(code, classNode); useClass(code, classNode);
} else { } else {
@@ -501,12 +595,12 @@ public class ClassGen {
} }
} }
public void useClass(CodeWriter code, ClassNode classNode) { public void useClass(ICodeWriter code, ClassNode classNode) {
code.attachAnnotation(classNode); code.attachAnnotation(classNode);
addClsName(code, classNode.getClassInfo()); addClsName(code, classNode.getClassInfo());
} }
private void addClsName(CodeWriter code, ClassInfo classInfo) { public void addClsName(ICodeWriter code, ClassInfo classInfo) {
String clsName = useClassInternal(cls.getClassInfo(), classInfo); String clsName = useClassInternal(cls.getClassInfo(), classInfo);
code.add(clsName); code.add(clsName);
} }
@@ -533,12 +627,7 @@ public class ClassGen {
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) { if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
return shortName; return shortName;
} }
// don't add import if class not public (must be accessed using inheritance) if (searchCollision(cls.root(), useCls, extClsInfo)) {
ClassNode classNode = cls.dex().resolveClass(extClsInfo);
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
return shortName;
}
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
return fullName; return fullName;
} }
// ignore classes from default package // ignore classes from default package
@@ -617,7 +706,7 @@ public class ClassGen {
return false; return false;
} }
private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) { private static boolean searchCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
if (useCls == null) { if (useCls == null) {
return false; return false;
} }
@@ -625,7 +714,7 @@ public class ClassGen {
if (useCls.getAliasShortName().equals(shortName)) { if (useCls.getAliasShortName().equals(shortName)) {
return true; return true;
} }
ClassNode classNode = dex.resolveClass(useCls); ClassNode classNode = root.resolveClass(useCls);
if (classNode != null) { if (classNode != null) {
for (ClassNode inner : classNode.getInnerClasses()) { for (ClassNode inner : classNode.getInnerClasses()) {
if (inner.getShortName().equals(shortName) if (inner.getShortName().equals(shortName)
@@ -634,16 +723,50 @@ public class ClassGen {
} }
} }
} }
return searchCollision(dex, useCls.getParentClass(), searchCls); return searchCollision(root, useCls.getParentClass(), searchCls);
} }
private void insertRenameInfo(CodeWriter code, ClassNode cls) { private void insertRenameInfo(ICodeWriter code, ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo(); ClassInfo classInfo = cls.getClassInfo();
if (classInfo.hasAlias()) { if (classInfo.hasAlias() && cls.checkCommentsLevel(CommentsLevel.INFO)) {
CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject()); CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject());
} }
} }
private static void addClassUsageInfo(ICodeWriter code, ClassNode cls) {
List<ClassNode> deps = cls.getDependencies();
code.startLine("// deps - ").add(Integer.toString(deps.size()));
for (ClassNode depCls : deps) {
code.startLine("// ").add(depCls.getClassInfo().getFullName());
}
List<ClassNode> useIn = cls.getUseIn();
code.startLine("// use in - ").add(Integer.toString(useIn.size()));
for (ClassNode useCls : useIn) {
code.startLine("// ").add(useCls.getClassInfo().getFullName());
}
List<MethodNode> useInMths = cls.getUseInMth();
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
for (MethodNode useMth : useInMths) {
code.startLine("// ").add(useMth.toString());
}
}
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) {
code.startLine("// ").add(useMth.toString());
}
}
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) {
code.startLine("// ").add(useMth.toString());
}
}
public ClassGen getParentGen() { public ClassGen getParentGen() {
return parentGen == null ? this : parentGen; return parentGen == null ? this : parentGen;
} }
@@ -655,4 +778,21 @@ public class ClassGen {
public boolean isFallbackMode() { public boolean isFallbackMode() {
return fallback; return fallback;
} }
public boolean isBodyGenStarted() {
return bodyGenStarted;
}
public void setBodyGenStarted(boolean bodyGenStarted) {
this.bodyGenStarted = bodyGenStarted;
}
@Nullable
public NameGen getOuterNameGen() {
return outerNameGen;
}
public void setOuterNameGen(@NotNull NameGen outerNameGen) {
this.outerNameGen = outerNameGen;
}
} }
@@ -4,6 +4,7 @@ import java.util.concurrent.Callable;
import jadx.api.ICodeInfo; import jadx.api.ICodeInfo;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.impl.SimpleCodeInfo;
import jadx.core.codegen.json.JsonCodeGen; import jadx.core.codegen.json.JsonCodeGen;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
@@ -13,7 +14,7 @@ public class CodeGen {
public static ICodeInfo generate(ClassNode cls) { public static ICodeInfo generate(ClassNode cls) {
if (cls.contains(AFlag.DONT_GENERATE)) { if (cls.contains(AFlag.DONT_GENERATE)) {
return CodeWriter.EMPTY; return ICodeInfo.EMPTY;
} }
JadxArgs args = cls.root().getArgs(); JadxArgs args = cls.root().getArgs();
switch (args.getOutputFormat()) { switch (args.getOutputFormat()) {
@@ -36,7 +37,7 @@ public class CodeGen {
private static ICodeInfo generateJson(ClassNode cls) { private static ICodeInfo generateJson(ClassNode cls) {
JsonCodeGen codeGen = new JsonCodeGen(cls); JsonCodeGen codeGen = new JsonCodeGen(cls);
String clsJson = wrapCodeGen(cls, codeGen::process); String clsJson = wrapCodeGen(cls, codeGen::process);
return new CodeWriter(clsJson); return new SimpleCodeInfo(clsJson);
} }
private static <R> R wrapCodeGen(ClassNode cls, Callable<R> codeGenFunc) { private static <R> R wrapCodeGen(ClassNode cls, Callable<R> codeGenFunc) {
@@ -1,338 +0,0 @@
package jadx.core.codegen;
import java.io.File;
import java.io.PrintWriter;
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.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
public class CodeWriter implements ICodeInfo {
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,
};
public static final CodeWriter EMPTY = new CodeWriter().finish();
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);
}
}
// create filled instance (just string wrapper)
public CodeWriter(String code) {
this.buf = null;
this.code = code;
}
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 {
StringBuilder s = new StringBuilder(curIndent * INDENT_STR.length());
for (int i = 0; i < curIndent; i++) {
s.append(INDENT_STR);
}
this.indentStr = s.toString();
}
}
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);
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return annotations;
}
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 Map<Integer, Integer> getLineMapping() {
return lineMap;
}
public CodeWriter finish() {
removeFirstEmptyLine();
buf.trimToSize();
code = buf.toString();
buf = null;
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;
});
return this;
}
private void removeFirstEmptyLine() {
int len = NL.length();
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
buf.delete(0, len);
}
}
public int bufLength() {
return buf.length();
}
@Override
public String getCodeStr() {
if (code == null) {
throw new NullPointerException("Code not set");
}
return code;
}
@Override
public String toString() {
return buf == null ? code : buf.toString();
}
public void save(File dir, String subDir, String fileName) {
if (!ZipSecurity.isValidZipEntryName(subDir) || !ZipSecurity.isValidZipEntryName(fileName)) {
return;
}
save(dir, new File(subDir, fileName).getPath());
}
public void save(File dir, String fileName) {
if (!ZipSecurity.isValidZipEntryName(fileName)) {
return;
}
save(new File(dir, fileName));
}
public void save(File file) {
if (code == null) {
finish();
}
File outFile = FileUtils.prepareFile(file);
try (PrintWriter out = new PrintWriter(outFile, "UTF-8")) {
out.println(code);
} catch (Exception e) {
LOG.error("Save file error", e);
}
}
}
@@ -4,6 +4,7 @@ import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Queue; import java.util.Queue;
import jadx.api.ICodeWriter;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.IfOp; import jadx.core.dex.instructions.IfOp;
@@ -16,7 +17,6 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.conditions.Compare; import jadx.core.dex.regions.conditions.Compare;
import jadx.core.dex.regions.conditions.IfCondition; import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.regions.conditions.IfCondition.Mode; import jadx.core.dex.regions.conditions.IfCondition.Mode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -42,15 +42,15 @@ public class ConditionGen extends InsnGen {
super(insnGen.mgen, insnGen.fallback); super(insnGen.mgen, insnGen.fallback);
} }
void add(CodeWriter code, IfCondition condition) throws CodegenException { public void add(ICodeWriter code, IfCondition condition) throws CodegenException {
add(code, new CondStack(), condition); add(code, new CondStack(), condition);
} }
void wrap(CodeWriter code, IfCondition condition) throws CodegenException { void wrap(ICodeWriter code, IfCondition condition) throws CodegenException {
wrap(code, new CondStack(), condition); wrap(code, new CondStack(), condition);
} }
private void add(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException { private void add(ICodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
stack.push(condition); stack.push(condition);
switch (condition.getMode()) { switch (condition.getMode()) {
case COMPARE: case COMPARE:
@@ -76,7 +76,7 @@ public class ConditionGen extends InsnGen {
stack.pop(); stack.pop();
} }
private void wrap(CodeWriter code, CondStack stack, IfCondition cond) throws CodegenException { private void wrap(ICodeWriter code, CondStack stack, IfCondition cond) throws CodegenException {
boolean wrap = isWrapNeeded(cond); boolean wrap = isWrapNeeded(cond);
if (wrap) { if (wrap) {
code.add('('); code.add('(');
@@ -87,7 +87,7 @@ public class ConditionGen extends InsnGen {
} }
} }
private void wrap(CodeWriter code, InsnArg firstArg) throws CodegenException { private void wrap(ICodeWriter code, InsnArg firstArg) throws CodegenException {
boolean wrap = isArgWrapNeeded(firstArg); boolean wrap = isArgWrapNeeded(firstArg);
if (wrap) { if (wrap) {
code.add('('); code.add('(');
@@ -98,7 +98,7 @@ public class ConditionGen extends InsnGen {
} }
} }
private void addCompare(CodeWriter code, CondStack stack, Compare compare) throws CodegenException { private void addCompare(ICodeWriter code, CondStack stack, Compare compare) throws CodegenException {
IfOp op = compare.getOp(); IfOp op = compare.getOp();
InsnArg firstArg = compare.getA(); InsnArg firstArg = compare.getA();
InsnArg secondArg = compare.getB(); InsnArg secondArg = compare.getB();
@@ -123,7 +123,7 @@ public class ConditionGen extends InsnGen {
wrap(code, firstArg); wrap(code, firstArg);
return; return;
} }
ErrorsCounter.methodWarn(mth, "Unsupported boolean condition " + op.getSymbol()); mth.addWarn("Unsupported boolean condition " + op.getSymbol());
} }
addArg(code, firstArg, isArgWrapNeeded(firstArg)); addArg(code, firstArg, isArgWrapNeeded(firstArg));
@@ -131,7 +131,7 @@ public class ConditionGen extends InsnGen {
addArg(code, secondArg, isArgWrapNeeded(secondArg)); addArg(code, secondArg, isArgWrapNeeded(secondArg));
} }
private void addTernary(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException { private void addTernary(ICodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
add(code, stack, condition.first()); add(code, stack, condition.first());
code.add(" ? "); code.add(" ? ");
add(code, stack, condition.second()); add(code, stack, condition.second());
@@ -139,12 +139,12 @@ public class ConditionGen extends InsnGen {
add(code, stack, condition.third()); add(code, stack, condition.third());
} }
private void addNot(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException { private void addNot(ICodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
code.add('!'); code.add('!');
wrap(code, stack, condition.getArgs().get(0)); wrap(code, stack, condition.getArgs().get(0));
} }
private void addAndOr(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException { private void addAndOr(ICodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
String mode = condition.getMode() == Mode.AND ? " && " : " || "; String mode = condition.getMode() == Mode.AND ? " && " : " || ";
Iterator<IfCondition> it = condition.getArgs().iterator(); Iterator<IfCondition> it = condition.getArgs().iterator();
while (it.hasNext()) { while (it.hasNext()) {

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