Compare commits

..

571 Commits

Author SHA1 Message Date
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
Skylot cc29da8e81 build: fix release build 2019-12-07 15:31:24 +00:00
Skylot d1a6841c20 fix: inline assign in complex conditions (#699) 2019-11-30 16:32:29 +00:00
Skylot 600842a1a6 fix: resolve error if input file don't has extension 2019-11-30 16:22:09 +00:00
Skylot 8ba3e935a5 build: update dependencies and gradle 2019-11-24 20:34:36 +00:00
Skylot 87504dd2cc refactor: additional checks for ssa vars and registers 2019-11-24 20:33:19 +00:00
Skylot e4e6f37949 fix: sort inner classes and methods by source lines 2019-11-19 20:05:21 +00:00
Skylot 4b314e9d99 fix: don't eliminate StringBuilder if no String arg present 2019-11-19 18:26:12 +00:00
Skylot a48ce296b8 fix: resolve code generation error for interface methods (#775) 2019-11-05 09:31:12 +00:00
Jan S cf3e17c4b8 feat(gui): support APK signature v3 (PR #773) 2019-11-01 19:31:24 +03:00
Skylot bae36f9720 fix: merge const block before return (#699) 2019-10-31 15:47:29 +00:00
Skylot 11db454b84 fix: duplicate result arg on instruction copy 2019-10-30 20:59:14 +00:00
Skylot 1b60c1d1a8 test: print smali code for debug purpose 2019-10-30 13:42:58 +00:00
Skylot 8321d5e380 fix: preserve arg type on PHI insn inline (#718) 2019-10-28 17:19:52 +00:00
Skylot 64c9ce2ab0 build: update sonarqube 2019-10-27 19:35:43 +00:00
Skylot 08f9a90c95 fix: force cast for null args in method invoke (temp fix for #724) 2019-10-27 16:53:25 +00:00
Skylot 9f06d6744e fix: increase region iterative traversal limit (#767) 2019-10-27 16:19:58 +00:00
Skylot f228a72118 fix(gui): fix search if class contains not generated inner class (#755) 2019-10-21 18:46:55 +01:00
Jan S 3249a5e0bc fix: workaround for IntelliJ bug on import line in build.gradle (PR #766) 2019-10-17 20:23:44 +03:00
Skylot d1ac43de33 fix(gui): add default contructor for classes serialized with GSON (#752) 2019-10-17 17:35:01 +03:00
Skylot 00f5e83506 fix: handle incorrect args count in signature (#763) 2019-10-17 16:51:06 +03:00
Skylot d3ecc1f640 fix: add dummy class if class loading exception occur (#763) 2019-10-17 16:51:03 +03:00
Jan S 902247fcdb fix: don't stop loading classes in case of an error (PR #764)
* fix: don't stop loading classes in case of an error
* style: reformat code
2019-10-15 20:25:18 +03:00
Skylot bd9e1096cc fix: handle methods with all NOPs (#744) 2019-08-30 15:37:38 +01:00
Skylot db892adf34 fix: don't run class process from visitors to avoid deadlock (#743) 2019-08-27 17:24:18 +01:00
Skylot 1cbaad3ec9 fix: make correct class members loading in jadx api (#742) 2019-08-25 19:53:12 +01:00
Skylot 401d08ea49 refactor: move all smali libs usage to one utility class 2019-08-21 14:45:32 +01:00
Skylot ba17f7bc00 refactor: move type with outer generic to different class 2019-08-15 21:43:57 +01:00
Skylot db2b537380 fix: try to resolve generic type variables (#662) 2019-08-15 21:39:43 +01:00
Skylot 06f26ef8f5 refactor: use enum for wildcard bounds instead of int 2019-08-15 14:31:30 +01:00
Skylot a71bb7a532 fix(gui): yet another fix for broken find usage action 2019-08-12 10:32:38 +01:00
Skylot 99934b5100 chore: update dependencies 2019-08-12 10:05:24 +01:00
Skylot ff5f6fca3c fix(gui): fix "Go to declaration" and "Find usage" menu actions 2019-08-11 22:03:47 +03:00
Skylot 3578f7d68f fix(gui): use editor font on tabs 2019-08-11 21:40:46 +03:00
Skylot 7bc01dcfa8 fix(gui): ignore mouse click on empty space in tree (#737) 2019-08-11 19:39:05 +03:00
Skylot bc7a748420 feat(cli): add options for change log level (#735) 2019-08-08 13:14:36 +03:00
Skylot c0194d025d refactor: fix misuse of immutable type flag 2019-08-03 17:31:13 +03:00
Skylot 19ca8a096b chore: resolve minor code issues in debug info parser 2019-08-03 14:19:54 +03:00
Skylot cf5bfc297b test: fix regression for code auto check 2019-08-02 21:05:03 +03:00
Skylot a17f9136dd refactor: enable class unloading after code generation 2019-08-01 23:29:30 +03:00
Skylot 7d07fb0b77 chore: fix issues reported by lgtm.com 2019-08-01 12:14:29 +03:00
Skylot 99935bada6 docs: update readme and contributing rules 2019-07-31 21:40:23 +03:00
Skylot be9dae57b9 fix: add explicit cast for byte literal in method invoke (#719) 2019-07-30 22:46:28 +03:00
Skylot 4629043721 fix: convert inner enums and fix inner classes reference (#719) 2019-07-30 20:49:31 +03:00
Skylot 068234f0ca fix: remove synchronization lock for code generation (#726) 2019-07-29 14:55:50 +03:00
Skylot ccb8ed1394 fix: add assign for inlined getter methods 2019-07-29 12:48:38 +03:00
Skylot 8d68d409eb test: another deboxing issue 2019-07-28 21:09:56 +03:00
Skylot e842e022ba fix: use nice name for 'package-private' in modifiers change message 2019-07-28 20:42:07 +03:00
Skylot 1e6b30343c fix: several improvements for multi-variable type search (#720) 2019-07-28 20:22:28 +03:00
Skylot ddedb8d8a0 fix: don't override type of method parameter in const deboxing (#723) 2019-07-26 16:14:27 +03:00
Skylot 472aa52706 fix: resolve some multi-thread issues 2019-07-25 21:53:37 +03:00
Skylot ab97084058 refactor: move passes list to root node 2019-07-25 17:54:04 +03:00
Skylot 0911b2dc2f test: NYI test for issue #722 2019-07-24 17:00:05 +03:00
Skylot fd7d08cb10 feat: initial deboxing implementation (#717) 2019-07-23 20:37:37 +03:00
Skylot 3ae8359408 fix: improve exception handler remove (#703) 2019-07-22 20:38:16 +03:00
Skylot 6b76a3c787 fix: protect method from second load 2019-07-22 18:43:02 +03:00
Skylot 9fbf9ef667 fix(gui): compare files extension in case insensitive way 2019-07-22 18:43:02 +03:00
Skylot c8de7b97dd fix: instead commenting move constructor call to the top (#704) 2019-07-21 19:45:22 +03:00
Skylot b32dc17dd7 fix: don't change AST before checks in ternary transform (#710) 2019-07-20 21:33:20 +03:00
Skylot 7c53b985cd refactor(gui): remove JCertificate node 2019-07-19 18:19:08 +03:00
Skylot c8df26f227 feat(gui): add class links for AndroidManifest.xml and other minor fixes 2019-07-19 18:03:40 +03:00
Skylot 3bc9671905 perf(gui): speed up line numbers rendering (#714) 2019-07-18 23:19:06 +03:00
Skylot 7fd959e6e3 refactor: improve variables handling in instruction wrapping 2019-07-17 22:53:00 +03:00
Skylot 24dc68652e fix: check that iteration variable in for-each loop not used outside (#708) 2019-07-17 22:42:33 +03:00
Skylot aad2d24c58 fix: unbind unused ssa variable after ternary conversion (#708) 2019-07-16 19:44:48 +03:00
Skylot 15d56abeb6 fix: read correct buffer size for string pool parsing (#712) 2019-07-15 21:19:58 +03:00
Skylot d89ec67888 style: resolve compiler warnings 2019-07-15 17:12:40 +03:00
Skylot f9f840fb9d refactor: remove redundant FieldArg and change arith one arg insn 2019-07-15 17:01:02 +03:00
Skylot 8e8a2faa10 fix(res): skip string if parsing failed (#712) 2019-07-14 17:06:19 +03:00
Skylot 0c2784bb42 refactor: inline fields in arithmetic operations 2019-07-14 15:09:01 +03:00
Skylot c555cd0825 fix: rename packages with reserved names (#711) 2019-07-14 13:13:00 +03:00
Skylot 92e28326a4 misc: don't add same edge insn several times 2019-07-13 13:24:52 +03:00
Skylot 2dbdd1f079 fix: support instructions removing in SimplifyVisitor 2019-07-13 13:19:58 +03:00
Skylot fc58022d56 misc: show shorter exception stacktrace in code 2019-07-13 13:17:22 +03:00
Skylot ed9fe8a573 fix: incorrect init values of inherited fields 2019-07-13 13:10:23 +03:00
Skylot 49e234d9f8 fix: improve finally extraction 2019-07-12 23:26:46 +03:00
Skylot a587ce88ea fix: ignore finally extraction with only one 'if' instruction (#709) 2019-07-12 21:21:14 +03:00
Skylot a530371b6f fix: improve StringBuilder elimination (#704) 2019-07-11 20:07:14 +03:00
Skylot 0c5a83c021 style: fix code style in test 2019-07-10 21:32:11 +03:00
Skylot 12bb632371 fix: always cast null objects in overloaded method (#707) 2019-07-10 21:11:02 +03:00
Skylot e4fc6774b1 fix: make correct hash calculation for GenericObject type (#705) 2019-07-10 16:58:52 +03:00
Skylot f57dfb3f2e test: check method override with generic arguments (#701) 2019-07-09 13:08:32 +03:00
Skylot c3f7a049d8 fix: ignore incorrect dex files in apk (#700) 2019-07-08 12:24:54 +03:00
Skylot 3eee83c2f2 fix: adjust insn reorder check in code shrink visitor (#695) 2019-07-07 14:18:21 +03:00
Skylot ed8c662631 fix: add generic types propagation (#695) 2019-07-06 19:12:31 +03:00
Skylot 850df18d7c refactor: update duplicate methods in InsnArg classes 2019-07-05 20:55:00 +03:00
Skylot 7f4da306c9 refactor: remove cloning library dependency 2019-07-05 20:45:28 +03:00
Skylot 424a8ffaf4 fix: inline constant strings (#685) 2019-07-05 19:10:57 +03:00
Skylot 8410e62531 fix: force one branch ternary in constructors (#685) 2019-07-05 17:14:46 +03:00
Skylot 533b686e0b fix: comment out instructions also before other constructor call (#685) 2019-07-05 17:05:38 +03:00
Skylot c6c54f90dc fix: comment out instructions before super call in constructor (#685) 2019-07-03 14:39:21 +03:00
Kend 0f5fd4e48a fix(gui): update Chinese translation (PR #697) 2019-06-27 12:39:48 +03:00
Skylot a7247e8a88 build: remove unused test-app submodule 2019-06-27 11:59:25 +03:00
Skylot c10a30346b style: reformat gradle files 2019-06-27 11:53:56 +03:00
Skylot 436e86fdf2 build: update gradle and dependencies 2019-06-27 11:16:44 +03:00
Jan S 29a137bde3 fix: jadx-gui.bat and jadx.bat do not work (#692) (PR #694) 2019-06-21 17:44:45 +03:00
1255 changed files with 96151 additions and 17009 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,16 +8,12 @@ assignees: ''
---
**Checks before report**
- [ ] check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
- [ ] search existing issues by exception message
(for example `JadxRuntimeException: Can't find immediate dominator for block`)
- [ ] 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
**Describe error**
- provide full name of method or class with error
- provide full java stacktrace
**Note**: no need to copy method fallback code (commented pseudocode)
- attach or provide link to apk file (double check apk version)
- full name of method or class with error
- 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**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
+10
View File
@@ -0,0 +1,10 @@
---
name: Feature Request
about: Suggest an idea for jadx
title: "[feature]"
labels: new feature
assignees: ''
---
*Describe your idea:*
+5
View File
@@ -0,0 +1,5 @@
:exclamation: Please review the [guidelines for contributing](https://github.com/skylot/jadx/blob/master/CONTRIBUTING.md#Pull-Request-Process)
### Description
Please describe your pull request.
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
.idea/
.run/
out/
*.iml
*.ipr
@@ -24,10 +25,14 @@ node_modules/
jadx-output/
*-tmp/
**/tmp/
*.jobf
*.dex
*.class
*.dump
*.log
*.cfg
*.orig
quark.json
cliff.toml
+6 -14
View File
@@ -7,26 +7,18 @@ before_script:
stages:
- test
- check
java-8:
stage: test
image: openjdk:8
script: ./gradlew clean build
script: ./gradlew clean build dist copyExe --warning-mode=all
java-11:
stage: test
image: openjdk:11
script: ./gradlew clean build
script: ./gradlew clean build dist copyExe --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.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN -Dsonar.branch.name=dev
- ./gradlew clean dist
artifacts:
paths:
- build/jadx*.zip
java-latest:
stage: test
image: openjdk:latest
script: java -version && ./gradlew clean build dist --warning-mode=all
-3
View File
@@ -1,3 +0,0 @@
[submodule "jadx-test-app/test-app"]
path = jadx-test-app/test-app
url = git://github.com/skylot/jadx-test-app.git
-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
-40
View File
@@ -1,40 +0,0 @@
language: java
sudo: false
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
- oraclejdk8
- 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
jdk: openjdk8
if: branch = release AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-release.sh
+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at skylot@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+31 -6
View File
@@ -1,7 +1,32 @@
# Contribution
# Contributing
To support this project you can:
- Post thoughts about new features/optimizations that important to you
- Submit bug using one of following patterns:
* Java code examples which decompiles incorrectly
* Error log and link to _public available_ apk file or app page on Google play
Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions with the project.
## Open Issue
1. Before proceed please do:
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
- search existing issues by exception message
2. Describe error
**Describe error**
- full name of method or class with error
- 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**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
## Pull Request Process
1. Please don't submit any code style fixes, dependencies updates or other changes which are not fixing any issues.
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. If possible don't add additional dependencies especially if they are big.
1. Make sure your code is correctly formatted, see description here: [Code Formatting](https://github.com/skylot/jadx/wiki/Code-Formatting).
1. Make sure your changes is passing build: `./gradlew clean build dist`
+78 -54
View File
@@ -1,111 +1,135 @@
<img src="https://raw.githubusercontent.com/skylot/jadx/master/jadx-gui/src/main/resources/logos/jadx-logo.png" width="64" align="left" />
## JADX
[![Build Status](https://travis-ci.org/skylot/jadx.png?branch=master)](https://travis-ci.org/skylot/jadx)
[![Code Coverage](https://codecov.io/gh/skylot/jadx/branch/master/graph/badge.svg)](https://codecov.io/gh/skylot/jadx)
[![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)
[![Build status](https://github.com/skylot/jadx/workflows/Build/badge.svg)](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
[![Alerts from lgtm.com](https://img.shields.io/lgtm/alerts/g/skylot/jadx.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/skylot/jadx/alerts/)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
**jadx** - Dex to Java decompiler
Command line and GUI tools for 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
![jadx-gui screenshot](https://i.imgur.com/h917IBZ.png)
:exclamation: :exclamation: :exclamation: Please note that in most cases Jadx can't decompile all 100% of the code, so errors will occur. Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds
**Main features:**
- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
- deobfuscator included
### Downloads
- latest [unstable build: ![Download](https://api.bintray.com/packages/skylot/jadx/unstable/images/download.svg) ](https://bintray.com/skylot/jadx/unstable/_latestVersion#files)
**jadx-gui features:**
- view decompiled code with highlighted syntax
- jump to declaration
- find usage
- full text search
- smali debugger (thanks to [@LBJ-the-GOAT](https://github.com/LBJ-the-GOAT)), check [wiki page](https://github.com/skylot/jadx/wiki/Smali-debugger) for setup and usage
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
### Download
- release from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
- release from [bintray: ![Download](https://api.bintray.com/packages/skylot/jadx/releases/images/download.svg) ](https://bintray.com/skylot/jadx/releases/_latestVersion#files)
- latest [unstable build](https://nightly.link/skylot/jadx/workflows/build/master)
After download unpack zip file go to `bin` directory and run:
- `jadx` - command line version
- `jadx-gui` - graphical version
- `jadx-gui` - UI version
On Windows run `.bat` files with double-click\
**Note:** ensure you have installed Java 8 64-bit version
**Note:** ensure you have installed Java 11 or later 64-bit version.
For windows you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
### Install
1. Arch linux
```bash
sudo pacman -S jadx
```
2. macOS
```bash
brew install jadx
```
### Related projects:
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
### 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)
### Building jadx from source
### Build from source
JDK 8 or higher must be installed:
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew dist
```
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew dist
```
(on Windows, use `gradlew.bat` instead of `./gradlew`)
Scripts for run jadx will be placed in `build/jadx/bin`
and also packed to `build/jadx-<version>.zip`
### macOS
You can install using brew:
brew install jadx
### Run
Run **jadx** on itself:
cd build/jadx/
bin/jadx -d out lib/jadx-core-*.jar
# or
bin/jadx-gui lib/jadx-core-*.jar
### Usage
```
jadx[-gui] [options] <input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)
jadx[-gui] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab)
options:
-d, --output-dir - output directory
-ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources
-j, --threads-count - processing threads count
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--single-class - 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
-j, --threads-count - processing threads count, default: 4
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info
--add-debug-lines - add comments with debug line numbers if available
--no-inline-anonymous - disable anonymous classes inline
--no-inline-methods - disable methods inline
--no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u)
--respect-bytecode-access-modifiers - don't change original access modifiers
--deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter (default: 3)
--deobf-max - max length of name, renamed if longer (default: 64)
--deobf-rewrite-cfg - force to save deobfuscation map
--deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64
--deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension
--deobf-rewrite-cfg - force to ignore and overwrite deobfuscation map file
--deobf-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'
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
--rename-flags - fix options (comma-separated list of):
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
'valid' - rename java identifiers to make them valid,
'printable' - remove non-printable chars from identifiers,
or single 'none' - to disable all renames
or single 'all' - to enable all (default)
--fs-case-sensitive - treat filesystem as case sensitive, false by default
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
-v, --verbose - verbose output
--use-dx - use dx/d8 to convert java bytecode
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
--version - print jadx version
-h, --help - print this help
Example:
jadx -d out classes.dex
jadx --rename-flags "none" classes.dex
jadx --rename-flags "valid,printable" classes.dex
Examples:
jadx -d out classes.dex
jadx --rename-flags "none" classes.dex
jadx --rename-flags "valid, printable" classes.dex
jadx --log-level ERROR app.apk
```
These options also worked on jadx-gui running from command line and override options from preferences dialog
### Troubleshooting
##### Out of memory error:
- Reduce processing threads count (`-j` option)
- Increase maximum java heap size:
* command line (example for linux):
`JAVA_OPTS="-Xmx4G" jadx -j 1 some.apk`
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
`DEFAULT_JVM_OPTS="-Xmx2500M"`
Please check wiki page [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A)
### Contributing
To support this project you can:
- Post thoughts about new features/optimizations that important to you
- 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)
---------------------------------------
*Licensed under the Apache 2.0 License*
*Copyright 2018 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 -78
View File
@@ -1,9 +1,6 @@
import com.diffplug.spotless.LineEnding
plugins {
id 'org.sonarqube' version '2.7'
id 'com.github.ben-manes.versions' version '0.21.0'
id "com.diffplug.gradle.spotless" version "3.21.1"
id 'com.github.ben-manes.versions' version '0.41.0'
id 'com.diffplug.spotless' version '6.2.0'
}
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -12,42 +9,37 @@ println("jadx version: ${jadxVersion}")
allprojects {
apply plugin: 'java'
apply plugin: 'jacoco'
apply plugin: 'checkstyle'
version = jadxVersion
tasks.withType(JavaCompile) {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
if (!"$it".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
compileJava {
options.encoding = "UTF-8"
}
jar {
version = jadxVersion
manifest {
mainAttributes('jadx-version': jadxVersion)
}
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.26'
implementation 'org.slf4j:slf4j-api:1.7.33'
compileOnly 'org.jetbrains:annotations:23.0.0'
testCompile 'ch.qos.logback:logback-classic:1.2.3'
testCompile 'org.hamcrest:hamcrest-library:2.1'
testCompile 'org.mockito:mockito-core:2.25.1'
testImplementation 'ch.qos.logback:logback-classic:1.2.10'
testImplementation 'org.hamcrest:hamcrest-library:2.2'
testImplementation 'org.mockito:mockito-core:4.2.0'
testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.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 {
@@ -57,31 +49,8 @@ allprojects {
repositories {
mavenLocal()
mavenCentral()
jcenter()
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 {
@@ -90,73 +59,98 @@ spotless {
include 'jadx-cli/src/**/java/**/*.java'
include 'jadx-core/src/**/java/**/*.java'
include 'jadx-gui/src/**/java/**/*.java'
include 'jadx-plugins/**/java/**/*.java'
}
importOrderFile 'config/code-formatter/eclipse.importorder'
eclipse().configFile 'config/code-formatter/eclipse.xml'
removeUnusedImports()
if (JavaVersion.current() < JavaVersion.VERSION_16) {
removeUnusedImports()
} else {
// google-format broken on java 16 (https://github.com/diffplug/spotless/issues/834)
println('Warning! Unused imports remove is disabled for Java 16')
}
lineEndings(LineEnding.UNIX)
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8")
trimTrailingWhitespace()
endWithNewline()
}
format 'misc', {
target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties'
targetExclude "jadx-test-app/test-app/**", ".gradle/**", ".idea/**"
targetExclude ".gradle/**", ".idea/**", "*/build/**"
lineEndings(LineEnding.UNIX)
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8")
trimTrailingWhitespace()
endWithNewline()
}
}
dependencyUpdates.resolutionStrategy = {
componentSelection { rules ->
rules.all { ComponentSelection selection ->
boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
}
if (rejected) {
selection.reject('Release candidate')
dependencyUpdates {
resolutionStrategy {
componentSelection { rules ->
rules.all { ComponentSelection selection ->
boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
}
if (rejected) {
selection.reject('Release candidate')
}
}
}
}
}
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
destinationDir file("$buildDir/jadx")
['jadx-cli', 'jadx-gui'].each {
from tasks.getByPath(":${it}:installDist").destinationDir
}
task copyArtifacts(type: Copy) {
from tasks.getByPath(":jadx-cli:installDist")
from tasks.getByPath(":jadx-gui:installDist")
into layout.buildDirectory.dir("jadx")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
task pack(type: Zip, dependsOn: copyArtifacts) {
destinationDir buildDir
archiveName "jadx-${jadxVersion}.zip"
from copyArtifacts.destinationDir
task pack(type: Zip) {
from copyArtifacts
archiveFileName = "jadx-${jadxVersion}.zip"
destinationDirectory = layout.buildDirectory
}
task copyExe(type: Copy, dependsOn: 'jadx-gui:createExe') {
task copyExe(type: Copy) {
group 'jadx'
description = 'Copy exe to build dir'
destinationDir buildDir
from tasks.getByPath('jadx-gui:createExe').outputs
mustRunAfter pack // not needed, but gradle throws warning because of same output dir
from tasks.getByPath('jadx-gui:createExe')
include '*.exe'
into layout.buildDirectory
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
task 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'
description = 'Build jadx distribution zip'
}
task samples(dependsOn: 'jadx-samples:samples') {
group 'jadx'
}
dependsOn(pack)
task testAppCheck(dependsOn: 'jadx-test-app:testAppCheck') {
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) {
@@ -164,6 +158,4 @@ task cleanBuildDir(type: Delete) {
delete buildDir
}
test.dependsOn(samples)
clean.dependsOn(cleanBuildDir)
+3
View File
@@ -0,0 +1,3 @@
plugins {
id 'groovy-gradle-plugin'
}
@@ -0,0 +1,78 @@
plugins {
id 'java-library'
id 'maven-publish'
id 'signing'
}
group = 'io.github.skylot'
version = jadxVersion
java {
withJavadocJar()
withSourcesJar()
}
publishing {
publications {
mavenJava(MavenPublication) {
artifactId = project.name
from components.java
versionMapping {
usage('java-api') {
fromResolutionOf('runtimeClasspath')
}
usage('java-runtime') {
fromResolutionResult()
}
}
pom {
name = project.name
description = 'Dex to Java decompiler'
url = 'https://github.com/skylot/jadx'
licenses {
license {
name = 'The Apache License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id = 'skylot'
name = 'Skylot'
email = 'skylot@gmail.com'
url = 'https://github.com/skylot'
}
}
scm {
connection = 'scm:git:git://github.com/skylot/jadx.git'
developerConnection = 'scm:git:ssh://github.com:skylot/jadx.git'
url = 'https://github.com/skylot/jadx'
}
}
}
}
repositories {
maven {
def releasesRepoUrl = uri('https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/')
def snapshotsRepoUrl = uri('https://s01.oss.sonatype.org/content/repositories/snapshots/')
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials {
username = project.properties['ossrhUser'].toString()
password = project.properties['ossrhPassword'].toString()
}
}
}
}
signing {
sign publishing.publications.mavenJava
}
javadoc {
if (JavaVersion.current().isJava9Compatible()) {
options.addBooleanOption('html5', true)
}
// disable 'missing' warnings
options.addStringOption('Xdoclint:all,-missing', '-quiet')
}
+14 -1
View File
@@ -78,7 +78,9 @@
<!-- annotations -->
<module name="AnnotationLocation"/>
<module name="AnnotationUseStyle"/>
<module name="AnnotationUseStyle">
<property name="elementStyle" value="compact"/>
</module>
<module name="MissingOverride"/>
<!-- <module name="MissingDeprecated"/> -->
@@ -117,6 +119,17 @@
<module name="OuterTypeNumber"/>
<module name="SuppressWarningsHolder"/>
<module name="IllegalType"/>
<module name="IllegalImport">
<property name="illegalClasses" value="jadx.core.utils.DebugUtils"/>
</module>
<module name="RegexpSinglelineJava">
<property name="id" value="printstacktrace"/>
<property name="format" value="\.printStackTrace\(\)"/>
<property name="ignoreComments" value="true"/>
<property name="message" value="Using Throwable.printStackTrace() is forbidden. Use logger to print exception"/>
</module>
</module>
<module name="NewlineAtEndOfFile"/>
+1
View File
@@ -0,0 +1 @@
SmaliTokenMaker.java
+5
View File
@@ -0,0 +1,5 @@
Refer to the following instructions to modify and use to generate SmaliTokenMarker
```shell
jflex SmaliTokenMaker.flex --skel skeleton.default
```
+681
View File
@@ -0,0 +1,681 @@
/*
* Generated on 11/22/21, 8:58 PM
*/
package jadx.gui.ui.codeearea;
import java.io.*;
import javax.swing.text.Segment;
import org.fife.ui.rsyntaxtextarea.*;
/**
* 用于Smali代码高亮
* MartinKay@qq.com
*/
%%
%public
%class SmaliTokenMaker
%extends AbstractJFlexCTokenMaker
%unicode
/* Case sensitive */
%type org.fife.ui.rsyntaxtextarea.Token
%{
/**
* Constructor. This must be here because JFlex does not generate a
* no-parameter constructor.
*/
public SmaliTokenMaker() {
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
* @see #addToken(int, int, int)
*/
private void addHyperlinkToken(int start, int end, int tokenType) {
int so = start + offsetShift;
addToken(zzBuffer, start,end, tokenType, so, true);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
*/
private void addToken(int tokenType) {
addToken(zzStartRead, zzMarkedPos-1, tokenType);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
* @see #addHyperlinkToken(int, int, int)
*/
private void addToken(int start, int end, int tokenType) {
int so = start + offsetShift;
addToken(zzBuffer, start,end, tokenType, so, false);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param array The character array.
* @param start The starting offset in the array.
* @param end The ending offset in the array.
* @param tokenType The token's type.
* @param startOffset The offset in the document at which this token
* occurs.
* @param hyperlink Whether this token is a hyperlink.
*/
public void addToken(char[] array, int start, int end, int tokenType,
int startOffset, boolean hyperlink) {
super.addToken(array, start,end, tokenType, startOffset, hyperlink);
zzStartRead = zzMarkedPos;
}
/**
* {@inheritDoc}
*/
public String[] getLineCommentStartAndEnd(int languageIndex) {
return new String[] { "#", null };
}
/**
* Returns the first token in the linked list of tokens generated
* from <code>text</code>. This method must be implemented by
* subclasses so they can correctly implement syntax highlighting.
*
* @param text The text from which to get tokens.
* @param initialTokenType The token type we should start with.
* @param startOffset The offset into the document at which
* <code>text</code> starts.
* @return The first <code>Token</code> in a linked list representing
* the syntax highlighted text.
*/
public Token getTokenList(Segment text, int initialTokenType, int startOffset) {
resetTokenList();
this.offsetShift = -text.offset + startOffset;
// Start off in the proper state.
int state = Token.NULL;
switch (initialTokenType) {
/* No multi-line comments */
/* No documentation comments */
default:
state = Token.NULL;
}
s = text;
try {
yyreset(zzReader);
yybegin(state);
return yylex();
} catch (IOException ioe) {
ioe.printStackTrace();
return new TokenImpl();
}
}
/**
* Refills the input buffer.
*
* @return <code>true</code> if EOF was reached, otherwise
* <code>false</code>.
*/
private boolean zzRefill() {
return zzCurrentPos>=s.offset+s.count;
}
/**
* Resets the scanner to read from a new input stream.
* Does not close the old reader.
*
* All internal variables are reset, the old input stream
* <b>cannot</b> be reused (internal buffer is discarded and lost).
* Lexical state is set to <tt>YY_INITIAL</tt>.
*
* @param reader the new input stream
*/
public final void yyreset(Reader reader) {
// 's' has been updated.
zzBuffer = s.array;
/*
* We replaced the line below with the two below it because zzRefill
* no longer "refills" the buffer (since the way we do it, it's always
* "full" the first time through, since it points to the segment's
* array). So, we assign zzEndRead here.
*/
//zzStartRead = zzEndRead = s.offset;
zzStartRead = s.offset;
zzEndRead = zzStartRead + s.count - 1;
zzCurrentPos = zzMarkedPos = zzPushbackPos = s.offset;
zzLexicalState = YYINITIAL;
zzReader = reader;
zzAtBOL = true;
zzAtEOF = false;
}
%}
Letter = [A-Za-z]
LetterOrUnderscore = ({Letter}|"_")
NonzeroDigit = [1-9]
Digit = ("0"|{NonzeroDigit})
HexDigit = ({Digit}|[A-Fa-f])
OctalDigit = ([0-7])
AnyCharacterButApostropheOrBackSlash = ([^\\'])
AnyCharacterButDoubleQuoteOrBackSlash = ([^\\\"\n])
EscapedSourceCharacter = ("u"{HexDigit}{HexDigit}{HexDigit}{HexDigit})
Escape = ("\\"(([btnfr\"'\\])|([0123]{OctalDigit}?{OctalDigit}?)|({OctalDigit}{OctalDigit}?)|{EscapedSourceCharacter}))
NonSeparator = ([^\t\f\r\n\ \(\)\{\}\[\]\;\,\.\=\>\<\!\~\?\:\+\-\*\/\&\|\^\%\"\']|"#"|"\\")
IdentifierStart = ({LetterOrUnderscore}|"$")
IdentifierPart = ({IdentifierStart}|{Digit}|("\\"{EscapedSourceCharacter}))
LineTerminator = (\n)
WhiteSpace = ([ \t\f]+)
CharLiteral = ([\']({AnyCharacterButApostropheOrBackSlash}|{Escape})[\'])
UnclosedCharLiteral = ([\'][^\'\n]*)
ErrorCharLiteral = ({UnclosedCharLiteral}[\'])
StringLiteral = ([\"]({AnyCharacterButDoubleQuoteOrBackSlash}|{Escape})*[\"])
UnclosedStringLiteral = ([\"]([\\].|[^\\\"])*[^\"]?)
ErrorStringLiteral = ({UnclosedStringLiteral}[\"])
/* No multi-line comments */
/* No documentation comments */
LineCommentBegin = "#"
IntegerLiteral = ({Digit}+)
HexLiteral = (0x{HexDigit}+)
FloatLiteral = (({Digit}+)("."{Digit}+)?(e[+-]?{Digit}+)? | ({Digit}+)?("."{Digit}+)(e[+-]?{Digit}+)?)
ErrorNumberFormat = (({IntegerLiteral}|{HexLiteral}|{FloatLiteral}){NonSeparator}+)
BooleanLiteral = ("true"|"false")
Separator = ([\(\)\{\}\[\]])
Separator2 = ([\;,.])
Identifier = ({IdentifierStart}{IdentifierPart}*)
URLGenDelim = ([:\/\?#\[\]@])
URLSubDelim = ([\!\$&'\(\)\*\+,;=])
URLUnreserved = ({LetterOrUnderscore}|{Digit}|[\-\.\~])
URLCharacter = ({URLGenDelim}|{URLSubDelim}|{URLUnreserved}|[%])
URLCharacters = ({URLCharacter}*)
URLEndCharacter = ([\/\$]|{Letter}|{Digit})
URL = (((https?|f(tp|ile))"://"|"www.")({URLCharacters}{URLEndCharacter})?)
/* Custom Regex */
/* fully-qualified name Rules */
SimpleName = ([a-zA-Z0-9_$]*)
QUALIFIED_TYPE_NAME = ("L"({SimpleName}{SLASH})*{SimpleName}*";")
/* Types */
VOID_TYPE = ("V")
BOOLEAN_TYPE = ("Z")
BYTE_TYPE = ("B")
SHORT_TYPE = ("S")
CHAR_TYPE = ("C")
INT_TYPE = ("I")
LONG_TYPE = ("J")
FLOAT_TYPE = ("F")
DOUBLE_TYPE = ("D")
/* Multi Args Types Highlight */
MULTI_ARGS_TYPES = (({BOOLEAN_TYPE}|{BYTE_TYPE}|{SHORT_TYPE}|{CHAR_TYPE}|{INT_TYPE}|{LONG_TYPE}|{FLOAT_TYPE}|{DOUBLE_TYPE})+);
/* Types fully-qualified name */
COMPOUND_METHOD_ARG_LITERAL = (({BOOLEAN_TYPE}|{BYTE_TYPE}|{SHORT_TYPE}|{CHAR_TYPE}|{INT_TYPE}|{LONG_TYPE}|{FLOAT_TYPE}|{DOUBLE_TYPE})+{QUALIFIED_TYPE_NAME})
LBRACK = ("[")
RBRACK = ("]")
LPAREN = ("(")
RPAREN = (")")
LBRACE = ("{")
RBRACE = ("}")
COLON = (":")
ASSIGN = ("=")
DOT = (".")
SUB = ("-")
COMMA = (",")
SLASH = ("/")
LT = ("<")
GT = (">")
ARROW = ("->")
SEMI = (";")
ARROW_FUNCTION = ({ARROW}[a-zA-Z_$<>]*)
CustomSeparator = ({Separator}|{ARROW_FUNCTION})
/* Register */
VREGISTER = ("v"("0"|[1-9])*)
PREGISTER = ("p"("0"|[1-9])*)
/* Flags */
FLAG_PSWITCH = (":pswitch_"{SimpleName})
FLAG_PSWITCH_DATA = (":pswitch_data_"{SimpleName})
FLAG_GOTO = (":goto_"{SimpleName})
FLAG_COND = (":cond_"{SimpleName})
FLAG_TRY_START = (":try_start_"{SimpleName})
FLAG_TRY_END = (":try_end_"{SimpleName})
FLAG_CATCH = (":catch_"{SimpleName})
FLAG_CATCHALL = (":catchall_"{SimpleName})
FLAG_ARRAY = (":array_"{SimpleName})
/* No string state */
/* No char state */
/* No MLC state */
/* No documentation comment state */
%state EOL_COMMENT
%%
<YYINITIAL> {
/* Keywords Instructions Highlight */
"nop" |
"move" |
"move/from16" |
"move/16" |
"move-wide" |
"move-wide/from16" |
"move-wide/16" |
"move-object" |
"move-object/from16" |
"move-object/16" |
"move-result" |
"move-result-wide" |
"move-result-object" |
"move-exception" |
"return-void" |
"return" |
"return-wide" |
"return-object" |
"const/4" |
"const/16" |
"const" |
"const/high16" |
"const-wide/16" |
"const-wide/32" |
"const-wide" |
"const-wide/high16" |
"const-string" |
"const-string/jumbo" |
"const-class" |
"monitor-enter" |
"monitor-exit" |
"check-cast" |
"instance-of" |
"array-length" |
"new-instance" |
"new-array" |
"filled-new-array" |
"filled-new-array/range" |
"fill-array-data" |
"throw" |
"goto" |
"goto/16" |
"goto/32" |
"cmpl-float" |
"cmpg-float" |
"cmpl-double" |
"cmpg-double" |
"cmp-long" |
"if-eq" |
"if-ne" |
"if-lt" |
"if-ge" |
"if-gt" |
"if-le" |
"if-eqz" |
"if-nez" |
"if-ltz" |
"if-gez" |
"if-gtz" |
"if-lez" |
"aget" |
"aget-wide" |
"aget-object" |
"aget-boolean" |
"aget-byte" |
"aget-char" |
"aget-short" |
"aput" |
"aput-wide" |
"aput-object" |
"aput-boolean" |
"aput-byte" |
"aput-char" |
"aput-short" |
"iget" |
"iget-wide" |
"iget-object" |
"iget-boolean" |
"iget-byte" |
"iget-char" |
"iget-short" |
"iput" |
"iput-wide" |
"iput-object" |
"iput-boolean" |
"iput-byte" |
"iput-char" |
"iput-short" |
"sget" |
"sget-wide" |
"sget-object" |
"sget-boolean" |
"sget-byte" |
"sget-char" |
"sget-short" |
"sput" |
"sput-wide" |
"sput-object" |
"sput-boolean" |
"sput-byte" |
"sput-char" |
"sput-short" |
"invoke-virtual" |
"invoke-super" |
"invoke-direct" |
"invoke-static" |
"invoke-interface" |
"invoke-virtual/range" |
"invoke-super/range" |
"invoke-direct/range" |
"invoke-static/range" |
"invoke-interface/range" |
"neg-int" |
"not-int" |
"neg-long" |
"not-long" |
"neg-float" |
"neg-double" |
"int-to-long" |
"int-to-float" |
"int-to-double" |
"long-to-int" |
"long-to-float" |
"long-to-double" |
"float-to-int" |
"float-to-long" |
"float-to-double" |
"double-to-int" |
"double-to-long" |
"double-to-float" |
"int-to-byte" |
"int-to-char" |
"int-to-short" |
"add-int" |
"sub-int" |
"mul-int" |
"div-int" |
"rem-int" |
"and-int" |
"or-int" |
"xor-int" |
"shl-int" |
"shr-int" |
"ushr-int" |
"add-long" |
"sub-long" |
"mul-long" |
"div-long" |
"rem-long" |
"and-long" |
"or-long" |
"xor-long" |
"shl-long" |
"shr-long" |
"ushr-long" |
"add-float" |
"sub-float" |
"mul-float" |
"div-float" |
"rem-float" |
"add-double" |
"sub-double" |
"mul-double" |
"div-double" |
"rem-double" |
"add-int/2addr" |
"sub-int/2addr" |
"mul-int/2addr" |
"div-int/2addr" |
"rem-int/2addr" |
"and-int/2addr" |
"or-int/2addr" |
"xor-int/2addr" |
"shl-int/2addr" |
"shr-int/2addr" |
"ushr-int/2addr" |
"add-long/2addr" |
"sub-long/2addr" |
"mul-long/2addr" |
"div-long/2addr" |
"rem-long/2addr" |
"and-long/2addr" |
"or-long/2addr" |
"xor-long/2addr" |
"shl-long/2addr" |
"shr-long/2addr" |
"ushr-long/2addr" |
"add-float/2addr" |
"sub-float/2addr" |
"mul-float/2addr" |
"div-float/2addr" |
"rem-float/2addr" |
"add-double/2addr" |
"sub-double/2addr" |
"mul-double/2addr" |
"div-double/2addr" |
"rem-double/2addr" |
"add-int/lit16" |
"rsub-int" |
"mul-int/lit16" |
"div-int/lit16" |
"rem-int/lit16" |
"and-int/lit16" |
"or-int/lit16" |
"xor-int/lit16" |
"add-int/lit8" |
"rsub-int/lit8" |
"mul-int/lit8" |
"div-int/lit8" |
"rem-int/lit8" |
"and-int/lit8" |
"or-int/lit8" |
"xor-int/lit8" |
"shl-int/lit8" |
"shr-int/lit8" |
"ushr-int/lit8" |
"invoke-polymorphic" |
"invoke-polymorphic/range" |
"invoke-custom" |
"invoke-custom/range" |
"const-method-handle" |
"const-method-type" |
"packed-switch" |
"sparse-switch" { addToken(Token.FUNCTION); }
/* Keywords Modifiers(IDENTIFIER标识符、修饰符) Highlight */
"public" |
"private" |
"protected" |
"final" |
"annotation" |
"static" |
"synthetic" |
"constructor" |
"abstract" |
"enum" |
"interface" |
"transient" |
"bridge" |
"declared-synchronized" |
"volatile" |
"strictfp" |
"varargs" |
"native" { addToken(Token.RESERVED_WORD); }
/* Keywords Directives Highlight */
".method" |
".end method" |
".implements" |
".class" |
".prologue" |
".source" |
".super" |
".field" |
".end field" |
".registers" |
".locals" |
".param" |
".line" |
".catch" |
".catchall" |
".annotation" |
".end annotation" |
".local" |
".end local" |
".restart local" |
".packed-switch" |
".end packed-switch" |
".array-data" |
".end array-data" |
".sparse-switch" |
".end sparse-switch" |
".end param" { addToken(Token.RESERVED_WORD_2); }
/* VARIABLE Register Highlight */
{VREGISTER} |
{PREGISTER} { addToken(Token.VARIABLE); }
/* Data types Highlight */
{QUALIFIED_TYPE_NAME} |
{COMPOUND_METHOD_ARG_LITERAL} |
{MULTI_ARGS_TYPES} |
{QUALIFIED_TYPE_NAME} |
{VOID_TYPE} |
{BOOLEAN_TYPE} |
{BYTE_TYPE} |
{SHORT_TYPE} |
{CHAR_TYPE} |
{INT_TYPE} |
{LONG_TYPE} |
{FLOAT_TYPE} |
{DOUBLE_TYPE} { addToken(Token.DATA_TYPE); }
/* FLAGS */
{FLAG_PSWITCH} |
{FLAG_PSWITCH_DATA} |
{FLAG_GOTO} |
{FLAG_COND} |
{FLAG_TRY_START} |
{FLAG_TRY_END} |
{FLAG_CATCHALL} |
{FLAG_ARRAY} |
{FLAG_CATCH} { addToken(Token.MARKUP_TAG_NAME); }
/* Functions */
/* No functions */
{BooleanLiteral} { addToken(Token.LITERAL_BOOLEAN); }
{LineTerminator} { addNullToken(); return firstToken; }
{Identifier} { addToken(Token.IDENTIFIER); }
{WhiteSpace} { addToken(Token.WHITESPACE); }
/* String/Character literals. */
{CharLiteral} { addToken(Token.LITERAL_CHAR); }
{UnclosedCharLiteral} { addToken(Token.ERROR_CHAR); addNullToken(); return firstToken; }
{ErrorCharLiteral} { addToken(Token.ERROR_CHAR); }
{StringLiteral} { addToken(Token.LITERAL_STRING_DOUBLE_QUOTE); }
{UnclosedStringLiteral} { addToken(Token.ERROR_STRING_DOUBLE); addNullToken(); return firstToken; }
{ErrorStringLiteral} { addToken(Token.ERROR_STRING_DOUBLE); }
/* Comment literals. */
/* No multi-line comments */
/* No documentation comments */
{LineCommentBegin} { start = zzMarkedPos-1; yybegin(EOL_COMMENT); }
/* Separators. */
{CustomSeparator} { addToken(Token.SEPARATOR); }
{Separator2} { addToken(Token.IDENTIFIER); }
/* Operators. */
"!" |
";" |
"." |
"=" |
"/" |
"'" |
"(" |
")" |
"," |
"->" |
";->" |
"<" |
">" |
"@" |
"[" |
"]" |
"{" |
"}" { addToken(Token.OPERATOR); }
/* Numbers */
{IntegerLiteral} { addToken(Token.LITERAL_NUMBER_DECIMAL_INT); }
{HexLiteral} { addToken(Token.LITERAL_NUMBER_HEXADECIMAL); }
{FloatLiteral} { addToken(Token.LITERAL_NUMBER_FLOAT); }
{ErrorNumberFormat} { addToken(Token.ERROR_NUMBER_FORMAT); }
/* Ended with a line not in a string or comment. */
<<EOF>> { addNullToken(); return firstToken; }
/* Catch any other (unhandled) characters. */
. { addToken(Token.IDENTIFIER); }
}
/* No char state */
/* No string state */
/* No multi-line comment state */
/* No documentation comment state */
<EOL_COMMENT> {
[^hwf\n]+ {}
{URL} { int temp=zzStartRead; addToken(start,zzStartRead-1, Token.COMMENT_EOL); addHyperlinkToken(temp,zzMarkedPos-1, Token.COMMENT_EOL); start = zzMarkedPos; }
[hwf] {}
\n { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
<<EOF>> { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
}
+243
View File
@@ -0,0 +1,243 @@
/** This character denotes the end of file */
public static final int YYEOF = -1;
/** initial size of the lookahead buffer */
--- private static final int ZZ_BUFFERSIZE = ...;
/** lexical states */
--- lexical states, charmap
/* error codes */
private static final int ZZ_UNKNOWN_ERROR = 0;
private static final int ZZ_NO_MATCH = 1;
private static final int ZZ_PUSHBACK_2BIG = 2;
/* error messages for the codes above */
private static final String ZZ_ERROR_MSG[] = {
"Unkown internal scanner error",
"Error: could not match input",
"Error: pushback value was too large"
};
--- isFinal list
/** the input device */
private java.io.Reader zzReader;
/** the current state of the DFA */
private int zzState;
/** the current lexical state */
private int zzLexicalState = YYINITIAL;
/** this buffer contains the current text to be matched and is
the source of the yytext() string */
private char zzBuffer[];
/** the textposition at the last accepting state */
private int zzMarkedPos;
/** the textposition at the last state to be included in yytext */
private int zzPushbackPos;
/** the current text position in the buffer */
private int zzCurrentPos;
/** startRead marks the beginning of the yytext() string in the buffer */
private int zzStartRead;
/** endRead marks the last character in the buffer, that has been read
from input */
private int zzEndRead;
/** number of newlines encountered up to the start of the matched text */
private int yyline;
/** the number of characters up to the start of the matched text */
private int yychar;
/**
* the number of characters from the last newline up to the start of the
* matched text
*/
private int yycolumn;
/**
* zzAtBOL == true <=> the scanner is currently at the beginning of a line
*/
private boolean zzAtBOL = true;
/** zzAtEOF == true <=> the scanner is at the EOF */
private boolean zzAtEOF;
--- user class code
/**
* Creates a new scanner
* There is also a java.io.InputStream version of this constructor.
*
* @param in the java.io.Reader to read input from.
*/
--- constructor declaration
/**
* Closes the input stream.
*/
public final void yyclose() throws java.io.IOException {
zzAtEOF = true; /* indicate end of file */
zzEndRead = zzStartRead; /* invalidate buffer */
if (zzReader != null)
zzReader.close();
}
/**
* Enters a new lexical state
*
* @param newState the new lexical state
*/
public final void yybegin(int newState) {
zzLexicalState = newState;
}
/**
* Returns the text matched by the current regular expression.
*/
public final String yytext() {
return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
}
/**
* Returns the character at position <tt>pos</tt> from the
* matched text.
*
* It is equivalent to yytext().charAt(pos), but faster
*
* @param pos the position of the character to fetch.
* A value from 0 to yylength()-1.
*
* @return the character at position pos
*/
public final char yycharat(int pos) {
return zzBuffer[zzStartRead+pos];
}
/**
* Returns the length of the matched text region.
*/
public final int yylength() {
return zzMarkedPos-zzStartRead;
}
/**
* Reports an error that occured while scanning.
*
* In a wellformed scanner (no or only correct usage of
* yypushback(int) and a match-all fallback rule) this method
* will only be called with things that "Can't Possibly Happen".
* If this method is called, something is seriously wrong
* (e.g. a JFlex bug producing a faulty scanner etc.).
*
* Usual syntax/scanner level error handling should be done
* in error fallback rules.
*
* @param errorCode the code of the errormessage to display
*/
--- zzScanError declaration
String message;
try {
message = ZZ_ERROR_MSG[errorCode];
}
catch (ArrayIndexOutOfBoundsException e) {
message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
}
--- throws clause
}
/**
* Pushes the specified amount of characters back into the input stream.
*
* They will be read again by then next call of the scanning method
*
* @param number the number of characters to be read again.
* This number must not be greater than yylength()!
*/
--- yypushback decl (contains zzScanError exception)
if ( number > yylength() )
zzScanError(ZZ_PUSHBACK_2BIG);
zzMarkedPos -= number;
}
--- zzDoEOF
/**
* Resumes scanning until the next regular expression is matched,
* the end of input is encountered or an I/O-Error occurs.
*
* @return the next token
* @exception java.io.IOException if any I/O-Error occurs
*/
--- yylex declaration
int zzInput;
int zzAction;
// cached fields:
int zzCurrentPosL;
int zzMarkedPosL;
int zzEndReadL = zzEndRead;
char [] zzBufferL = zzBuffer;
char [] zzCMapL = ZZ_CMAP;
--- local declarations
while (true) {
zzMarkedPosL = zzMarkedPos;
--- start admin (line, char, col count)
zzAction = -1;
zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
--- start admin (lexstate etc)
zzForAction: {
while (true) {
--- next input, line, col, char count, next transition, isFinal action
zzAction = zzState;
zzMarkedPosL = zzCurrentPosL;
--- line count update
}
}
}
// store back cached position
zzMarkedPos = zzMarkedPosL;
--- char count update
--- actions
default:
if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
zzAtEOF = true;
--- eofvalue
}
else {
--- no match
}
}
}
}
--- main
}
+1 -1
View File
@@ -1 +1 @@
org.gradle.daemon=false
org.gradle.warning.mode=all
Binary file not shown.
+2 -1
View File
@@ -1,5 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
distributionSha256Sum=b586e04868a22fd817c8971330fec37e298f3242eb85c374181b12d637f80302
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+174 -112
View File
@@ -1,78 +1,129 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -89,84 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; 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=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
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" "$@"
Vendored
+24 -19
View File
@@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
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.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,28 +64,14 @@ echo location of your Java installation.
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
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@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
@rem End local scope for the variables with windows NT shell
+24 -22
View File
@@ -1,28 +1,30 @@
apply plugin: 'application'
mainClassName = 'jadx.cli.JadxCLI'
applicationName = 'jadx'
plugins {
id 'application'
}
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.74'
compile 'ch.qos.logback:logback-classic:1.2.3'
implementation(project(':jadx-core'))
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 {
applicationName = 'jadx'
mainClass.set('jadx.cli.JadxCLI')
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
}
applicationDistribution.with {
into('') {
from '../.'
include 'README.md'
include 'NOTICE'
include 'LICENSE'
}
}
startScripts {
defaultJvmOpts = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
doLast {
def str = windowsScript.text
str = str.replaceAll('set JAVA_EXE=%JAVA_HOME%/bin/java.exe', 'set JAVA_EXE="%JAVA_HOME%/bin/java.exe"')
windowsScript.text = str
}
into('') {
from '../.'
include 'README.md'
include 'NOTICE'
include 'LICENSE'
}
}
@@ -5,10 +5,12 @@ import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
@@ -78,23 +80,37 @@ public class JCommanderWrapper<T> {
}
StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames());
String description = p.getDescription();
addSpaces(opt, maxNamesLen - opt.length() + 3);
opt.append("- ").append(p.getDescription());
addDefaultValue(args, f, opt);
if (description.contains("\n")) {
String[] lines = description.split("\n");
opt.append("- ").append(lines[0]);
for (int i = 1; i < lines.length; i++) {
opt.append('\n');
addSpaces(opt, maxNamesLen + 5);
opt.append(lines[i]);
}
} else {
opt.append("- ").append(description);
}
String defaultValue = getDefaultValue(args, f, opt);
if (defaultValue != null) {
opt.append(", default: ").append(defaultValue);
}
out.println(opt);
}
out.println("Example:");
out.println("Examples:");
out.println(" jadx -d out classes.dex");
out.println(" jadx --rename-flags \"none\" classes.dex");
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
out.println(" jadx --log-level ERROR app.apk");
}
/**
* Get all declared fields of the specified class and all super classes
*
* @param clazz
* @return
*/
private List<Field> getFields(Class<?> clazz) {
List<Field> fieldList = new LinkedList<>();
List<Field> fieldList = new ArrayList<>();
while (clazz != null) {
fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
@@ -102,26 +118,26 @@ public class JCommanderWrapper<T> {
return fieldList;
}
private void addDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
Class<?> fieldType = f.getType();
if (fieldType == int.class) {
try {
int val = f.getInt(args);
opt.append(" (default: ").append(val).append(')');
} catch (Exception e) {
// ignore
@Nullable
private String getDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
try {
Class<?> fieldType = f.getType();
if (fieldType == int.class) {
return Integer.toString(f.getInt(args));
}
}
if (fieldType == String.class) {
try {
String val = (String) f.get(args);
if (fieldType == String.class) {
return (String) f.get(args);
}
if (Enum.class.isAssignableFrom(fieldType)) {
Enum<?> val = (Enum<?>) f.get(args);
if (val != null) {
opt.append(" (default: ").append(val).append(')');
return val.name().toLowerCase(Locale.ROOT);
}
} catch (Exception e) {
// ignore
}
} catch (Exception e) {
// ignore
}
return null;
}
private static void addSpaces(StringBuilder str, int count) {
+36 -20
View File
@@ -5,7 +5,10 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.impl.NoOpCodeCache;
import jadx.api.impl.SimpleCodeWriter;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.files.FileUtils;
public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
@@ -13,35 +16,48 @@ public class JadxCLI {
public static void main(String[] args) {
int result = 0;
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) {
result = processAndSave(jadxArgs);
}
result = execute(args);
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
result = 1;
} catch (Exception e) {
LOG.error("jadx error: {}", e.getMessage(), e);
result = 1;
} finally {
FileUtils.deleteTempRootDir();
System.exit(result);
}
}
static int processAndSave(JadxCLIArgs inputArgs) {
JadxArgs args = inputArgs.toJadxArgs();
JadxDecompiler jadx = new JadxDecompiler(args);
try {
public static int execute(String[] args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) {
return processAndSave(jadxArgs.toJadxArgs());
}
return 0;
}
private static int processAndSave(JadxArgs jadxArgs) {
jadxArgs.setCodeCache(new NoOpCodeCache());
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load();
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
return 1;
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);
});
}
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors, count: {}", errorsCount);
} else {
LOG.info("done");
}
}
jadx.save();
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors");
} else {
LOG.info("done");
}
return errorsCount;
return 0;
}
}
+125 -32
View File
@@ -1,31 +1,27 @@
package jadx.cli;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.stream.Stream;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils;
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);
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
@@ -64,9 +60,15 @@ public class JadxCLIArgs {
@Parameter(names = { "--no-debug-info" }, description = "disable debug info")
protected boolean debugInfo = true;
@Parameter(names = { "--add-debug-lines" }, description = "add comments with debug line numbers if available")
protected boolean addDebugLines = false;
@Parameter(names = { "--no-inline-anonymous" }, description = "disable anonymous classes inline")
protected boolean inlineAnonymousClasses = true;
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
protected boolean inlineMethods = true;
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
protected boolean replaceConsts = true;
@@ -85,19 +87,36 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer")
protected int deobfuscationMaxLength = 64;
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "force to save deobfuscation map")
@Parameter(
names = { "--deobf-cfg-file" },
description = "deobfuscation map file, default: same dir and name as input file with '.jobf' extension"
)
protected String deobfuscationMapFile;
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "force to ignore and overwrite deobfuscation map file")
protected boolean deobfuscationForceSave = false;
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
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(
names = { "--rename-flags" },
description = "what to rename, comma-separated,"
+ " 'case' for system case sensitivity,"
+ " 'valid' for java identifiers,"
+ " 'printable' characters,"
+ " 'none' or 'all' (default)",
description = "fix options (comma-separated list of):"
+ "\n 'case' - fix case sensitivity issues (according to --fs-case-sensitive option),"
+ "\n 'valid' - rename java identifiers to make them valid,"
+ "\n 'printable' - remove non-printable chars from identifiers,"
+ "\nor single 'none' - to disable all renames"
+ "\nor single 'all' - to enable all (default)",
converter = RenameConverter.class
)
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
@@ -114,9 +133,29 @@ public class JadxCLIArgs {
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false;
@Parameter(names = { "-v", "--verbose" }, description = "verbose output")
@Parameter(names = { "--use-dx" }, description = "use dx/d8 to convert java bytecode")
protected boolean useDx = false;
@Parameter(
names = { "--comments-level" },
description = "set code comments level, values: error, warn, info, debug, user-only, none",
converter = CommentsLevelConverter.class
)
protected CommentsLevel commentsLevel = CommentsLevel.INFO;
@Parameter(
names = { "--log-level" },
description = "set log level, values: quiet, progress, error, warn, info, debug",
converter = LogHelper.LogLevelConverter.class
)
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
@Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)")
protected boolean verbose = false;
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
protected boolean quiet = false;
@Parameter(names = { "--version" }, description = "print jadx version")
protected boolean printVersion = false;
@@ -158,15 +197,7 @@ public class JadxCLIArgs {
if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive, got: " + threadsCount);
}
if (verbose) {
ch.qos.logback.classic.Logger rootLogger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
// remove INFO ThresholdFilter
Appender<ILoggingEvent> appender = rootLogger.getAppender("STDOUT");
if (appender != null) {
appender.clearAllFilters();
}
}
LogHelper.setLogLevelFromArgs(this);
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
jcw.printUsage();
@@ -194,20 +225,25 @@ public class JadxCLIArgs {
args.setRawCFGOutput(rawCfgOutput);
args.setReplaceConsts(replaceConsts);
args.setDeobfuscationOn(deobfuscationOn);
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
args.setDeobfuscationForceSave(deobfuscationForceSave);
args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject);
args.setUseImports(useImports);
args.setDebugInfo(debugInfo);
args.setInsertDebugLines(addDebugLines);
args.setInlineAnonymousClasses(inlineAnonymousClasses);
args.setRenameCaseSensitive(isRenameCaseSensitive());
args.setRenameValid(isRenameValid());
args.setRenamePrintable(isRenamePrintable());
args.setInlineMethods(inlineMethods);
args.setRenameFlags(renameFlags);
args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel);
args.setUseDxInput(useDx);
return args;
}
@@ -243,6 +279,10 @@ public class JadxCLIArgs {
return fallbackMode;
}
public boolean isUseDx() {
return useDx;
}
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
@@ -255,10 +295,18 @@ public class JadxCLIArgs {
return debugInfo;
}
public boolean isAddDebugLines() {
return addDebugLines;
}
public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses;
}
public boolean isInlineMethods() {
return inlineMethods;
}
public boolean isDeobfuscationOn() {
return deobfuscationOn;
}
@@ -271,6 +319,10 @@ public class JadxCLIArgs {
return deobfuscationMaxLength;
}
public String getDeobfuscationMapFile() {
return deobfuscationMapFile;
}
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
}
@@ -279,6 +331,14 @@ public class JadxCLIArgs {
return deobfuscationUseSourceNameAsAlias;
}
public boolean isDeobfuscationParseKotlinMetadata() {
return deobfuscationParseKotlinMetadata;
}
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public boolean isEscapeUnicode() {
return escapeUnicode;
}
@@ -319,6 +379,10 @@ public class JadxCLIArgs {
return fsCaseSensitive;
}
public CommentsLevel getCommentsLevel() {
return commentsLevel;
}
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
private final String paramName;
@@ -337,17 +401,46 @@ public class JadxCLIArgs {
Set<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
for (String s : value.split(",")) {
try {
set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT)));
set.add(RenameEnum.valueOf(s.trim().toUpperCase(Locale.ROOT)));
} catch (IllegalArgumentException e) {
String values = Arrays.stream(RenameEnum.values())
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
.collect(Collectors.joining(", "));
throw new IllegalArgumentException(
'\'' + s + "' is unknown for parameter " + paramName
+ ", possible values are " + values);
+ ", possible values are " + enumValuesString(RenameEnum.values()));
}
}
return set;
}
}
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 String enumValuesString(Enum<?>[] values) {
return Stream.of(values)
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
.collect(Collectors.joining(", "));
}
}
@@ -0,0 +1,107 @@
package jadx.cli;
import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import jadx.api.JadxDecompiler;
public class LogHelper {
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(LogHelper.class);
public enum LogLevelEnum {
QUIET(Level.OFF),
PROGRESS(Level.OFF),
ERROR(Level.ERROR),
WARN(Level.WARN),
INFO(Level.INFO),
DEBUG(Level.DEBUG);
private final Level level;
LogLevelEnum(Level level) {
this.level = level;
}
public Level getLevel() {
return level;
}
}
private static LogLevelEnum logLevelValue;
public static void setLogLevelFromArgs(JadxCLIArgs args) {
if (isCustomLogConfig()) {
return;
}
LogLevelEnum logLevel = args.logLevel;
if (args.quiet) {
logLevel = LogLevelEnum.QUIET;
} else if (args.verbose) {
logLevel = LogLevelEnum.DEBUG;
}
applyLogLevel(logLevel);
}
public static void applyLogLevel(LogLevelEnum logLevel) {
logLevelValue = logLevel;
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(logLevel.getLevel());
if (logLevel != LogLevelEnum.QUIET) {
// show progress for all levels except quiet
setLevelForClass(JadxCLI.class, Level.INFO);
setLevelForClass(JadxDecompiler.class, Level.INFO);
}
}
@Nullable
public static LogLevelEnum getLogLevel() {
return logLevelValue;
}
public static void setLevelForClass(Class<?> cls, Level level) {
((Logger) LoggerFactory.getLogger(cls)).setLevel(level);
}
public static void setLevelForPackage(String pkgName, Level level) {
((Logger) LoggerFactory.getLogger(pkgName)).setLevel(level);
}
/**
* Try to detect if user provide custom logback config via -Dlogback.configurationFile=
*/
private static boolean isCustomLogConfig() {
try {
String logbackConfig = System.getProperty("logback.configurationFile");
if (logbackConfig == null) {
return false;
}
LOG.debug("Use custom log config: {}", logbackConfig);
return true;
} catch (Exception e) {
LOG.error("Failed to detect custom log config", e);
}
return false;
}
public static class LogLevelConverter implements IStringConverter<LogLevelEnum> {
@Override
public LogLevelEnum convert(String value) {
try {
return LogLevelEnum.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown log level, possible values are "
+ JadxCLIArgs.enumValuesString(LogLevelEnum.values()));
}
}
}
}
@@ -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++;
}
}
}
}
+1 -4
View File
@@ -1,14 +1,11 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%-5level - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
@@ -42,8 +42,7 @@ public class RenameConverterTest {
() -> converter.convert("wrong"),
"Expected convert() to throw, but it didn't");
assertEquals("'wrong' is unknown for parameter someParam, "
+ "possible values are 'case', 'valid', 'printable'",
assertEquals("'wrong' is unknown for parameter someParam, possible values are case, valid, printable",
thrown.getMessage());
}
}
@@ -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.
+21 -13
View File
@@ -1,20 +1,28 @@
ext.jadxClasspath = 'clsp-data/android-5.1.jar'
plugins {
id 'jadx-library'
}
dependencies {
runtime files(jadxClasspath)
api(project(':jadx-plugins:jadx-plugins-api'))
compile files('lib/dx-1.16.jar')
implementation 'com.google.code.gson:gson:2.8.9'
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
constraints {
// Force protobuf version to prevent Java-7 issue
implementation 'com.google.protobuf:protobuf-java:3.11.4'
}
compile 'org.ow2.asm:asm:7.1'
compile 'org.jetbrains:annotations:17.0.0'
compile 'uk.com.robust-it:cloning:1.9.12'
compile 'com.google.code.gson:gson:2.8.5'
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
compile 'org.smali:baksmali:2.2.7'
compile('org.smali:smali:2.2.7') {
exclude group: 'com.google.guava'
}
compile 'com.google.guava:guava:27.1-jre'
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'))
testCompile 'org.apache.commons:commons-lang3:3.8.1'
testImplementation('tools.profiler:async-profiler:1.8.3')
}
test {
exclude '**/tmp/*'
}
Binary file not shown.
Binary file not shown.
@@ -2,32 +2,27 @@ package jadx.api;
public final class CodePosition {
private final JavaNode node;
private final int line;
private final int offset;
private final int pos;
public CodePosition(JavaNode node, int line, int offset) {
this.node = node;
public CodePosition(int line, int offset, int pos) {
this.line = line;
this.offset = offset;
this.pos = pos;
}
public CodePosition(int line) {
this(line, 0, -1);
}
@Deprecated
public CodePosition(int line, int offset) {
this.node = null;
this.line = line;
this.offset = offset;
this(line, offset, -1);
}
public JavaNode getNode() {
return node;
}
public JavaClass getJavaClass() {
JavaClass parent = node.getDeclaringClass();
if (parent == null && node instanceof JavaClass) {
return (JavaClass) node;
}
return parent;
public int getPos() {
return pos;
}
public int getLine() {
@@ -62,8 +57,8 @@ public final class CodePosition {
if (offset != 0) {
sb.append(':').append(offset);
}
if (node != null) {
sb.append(' ').append(node);
if (pos > 0) {
sb.append('@').append(pos);
}
return sb.toString();
}
@@ -0,0 +1,14 @@
package jadx.api;
public enum CommentsLevel {
NONE,
USER_ONLY,
ERROR,
WARN,
INFO,
DEBUG;
public boolean filter(CommentsLevel limit) {
return this.ordinal() <= limit.ordinal();
}
}
@@ -0,0 +1,13 @@
package jadx.api;
import org.jetbrains.annotations.Nullable;
public interface ICodeCache {
void add(String clsFullName, ICodeInfo codeInfo);
void remove(String clsFullName);
@Nullable
ICodeInfo get(String clsFullName);
}
@@ -0,0 +1,16 @@
package jadx.api;
import java.util.Map;
import jadx.api.impl.SimpleCodeInfo;
public interface ICodeInfo {
ICodeInfo EMPTY = new SimpleCodeInfo("");
String getCodeStr();
Map<Integer, Integer> getLineMapping();
Map<CodePosition, Object> getAnnotations();
}
@@ -0,0 +1,60 @@
package jadx.api;
import java.util.Map;
import jadx.core.dex.attributes.ILineAttributeNode;
public interface ICodeWriter {
String NL = System.getProperty("line.separator");
String INDENT_STR = " ";
boolean isMetadataSupported();
ICodeWriter startLine();
ICodeWriter startLine(char c);
ICodeWriter startLine(String str);
ICodeWriter startLineWithNum(int sourceLine);
ICodeWriter addMultiLine(String str);
ICodeWriter add(String str);
ICodeWriter add(char c);
ICodeWriter add(ICodeWriter code);
ICodeWriter newLine();
ICodeWriter addIndent();
void incIndent();
void decIndent();
int getIndent();
void setIndent(int indent);
int getLine();
void attachDefinition(ILineAttributeNode obj);
void attachAnnotation(Object obj);
void attachLineAnnotation(Object obj);
void attachSourceLine(int sourceLine);
ICodeInfo finish();
String getCodeStr();
int getLength();
StringBuilder getRawBuf();
Map<CodePosition, Object> getRawAnnotations();
}
@@ -0,0 +1,7 @@
package jadx.api;
import java.util.List;
public interface IDecompileScheduler {
List<List<JavaClass>> buildBatches(List<JavaClass> classes);
}
+129 -1
View File
@@ -6,8 +6,13 @@ import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import jadx.api.data.ICodeData;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache;
public class JadxArgs {
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
@@ -22,6 +27,9 @@ public class JadxArgs {
private File outDirSrc;
private File outDirRes;
private ICodeCache codeCache = new InMemoryCodeCache();
private Function<JadxArgs, ICodeWriter> codeWriterProvider = AnnotatedCodeWriter::new;
private int threadsCount = DEFAULT_THREADS_COUNT;
private boolean cfgOutput = false;
@@ -32,7 +40,10 @@ public class JadxArgs {
private boolean useImports = true;
private boolean debugInfo = true;
private boolean insertDebugLines = false;
private boolean extractFinally = true;
private boolean inlineAnonymousClasses = true;
private boolean inlineMethods = true;
private boolean skipResources = false;
private boolean skipSources = false;
@@ -45,6 +56,8 @@ public class JadxArgs {
private boolean deobfuscationOn = false;
private boolean deobfuscationForceSave = false;
private boolean useSourceNameAsClassAlias = false;
private boolean parseKotlinMetadata = false;
private File deobfuscationMapFile = null;
private int deobfuscationMinLength = 0;
private int deobfuscationMaxLength = Integer.MAX_VALUE;
@@ -68,6 +81,18 @@ public class JadxArgs {
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
private ICodeData codeData;
private CommentsLevel commentsLevel = CommentsLevel.INFO;
private boolean useDxInput = false;
public enum UseKotlinMethodsForVarNames {
DISABLE, APPLY, APPLY_AND_HIDE
}
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
public JadxArgs() {
// use default options
}
@@ -119,7 +144,7 @@ public class JadxArgs {
}
public void setThreadsCount(int threadsCount) {
this.threadsCount = threadsCount;
this.threadsCount = Math.max(1, threadsCount); // make sure threadsCount >= 1
}
public boolean isCfgOutput() {
@@ -170,6 +195,14 @@ public class JadxArgs {
this.debugInfo = debugInfo;
}
public boolean isInsertDebugLines() {
return insertDebugLines;
}
public void setInsertDebugLines(boolean insertDebugLines) {
this.insertDebugLines = insertDebugLines;
}
public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses;
}
@@ -178,6 +211,22 @@ public class JadxArgs {
this.inlineAnonymousClasses = inlineAnonymousClasses;
}
public boolean isInlineMethods() {
return inlineMethods;
}
public void setInlineMethods(boolean inlineMethods) {
this.inlineMethods = inlineMethods;
}
public boolean isExtractFinally() {
return extractFinally;
}
public void setExtractFinally(boolean extractFinally) {
this.extractFinally = extractFinally;
}
public boolean isSkipResources() {
return skipResources;
}
@@ -226,6 +275,14 @@ public class JadxArgs {
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
}
public boolean isParseKotlinMetadata() {
return parseKotlinMetadata;
}
public void setParseKotlinMetadata(boolean parseKotlinMetadata) {
this.parseKotlinMetadata = parseKotlinMetadata;
}
public int getDeobfuscationMinLength() {
return deobfuscationMinLength;
}
@@ -242,6 +299,14 @@ public class JadxArgs {
this.deobfuscationMaxLength = deobfuscationMaxLength;
}
public File getDeobfuscationMapFile() {
return deobfuscationMapFile;
}
public void setDeobfuscationMapFile(File deobfuscationMapFile) {
this.deobfuscationMapFile = deobfuscationMapFile;
}
public boolean isEscapeUnicode() {
return escapeUnicode;
}
@@ -314,6 +379,14 @@ public class JadxArgs {
}
}
public void setRenameFlags(Set<RenameEnum> renameFlags) {
this.renameFlags = renameFlags;
}
public Set<RenameEnum> getRenameFlags() {
return renameFlags;
}
public OutputFormatEnum getOutputFormat() {
return outputFormat;
}
@@ -326,6 +399,54 @@ public class JadxArgs {
this.outputFormat = outputFormat;
}
public ICodeCache getCodeCache() {
return codeCache;
}
public void setCodeCache(ICodeCache 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;
}
@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -341,8 +462,11 @@ public class JadxArgs {
+ ", skipResources=" + skipResources
+ ", skipSources=" + skipSources
+ ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationMapFile=" + deobfuscationMapFile
+ ", deobfuscationForceSave=" + deobfuscationForceSave
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
+ ", deobfuscationMinLength=" + deobfuscationMinLength
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
+ ", escapeUnicode=" + escapeUnicode
@@ -352,6 +476,10 @@ public class JadxArgs {
+ ", fsCaseSensitive=" + fsCaseSensitive
+ ", renameFlags=" + renameFlags
+ ", outputFormat=" + outputFormat
+ ", commentsLevel=" + commentsLevel
+ ", codeCache=" + codeCache
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
+ ", useDxInput=" + useDxInput
+ '}';
}
}
@@ -27,14 +27,11 @@ public class JadxArgsValidator {
if (inputFiles.isEmpty()) {
throw new JadxArgsValidateException("Please specify input file");
}
if (inputFiles.size() > 1) {
for (File inputFile : inputFiles) {
String fileName = inputFile.getName();
if (fileName.startsWith("--")) {
throw new JadxArgsValidateException("Unknown argument: " + fileName);
}
for (File inputFile : inputFiles) {
String fileName = inputFile.getName();
if (fileName.startsWith("--")) {
throw new JadxArgsValidateException("Unknown argument: " + fileName);
}
throw new JadxArgsValidateException("Only one input file supported");
}
for (File file : inputFiles) {
checkFile(file);
@@ -88,9 +85,6 @@ public class JadxArgsValidator {
if (!file.exists()) {
throw new JadxArgsValidateException("File not found " + file.getAbsolutePath());
}
if (file.isDirectory()) {
throw new JadxArgsValidateException("Expected file but found directory instead: " + file.getAbsolutePath());
}
}
private static void checkDir(File dir, String desc) {
@@ -1,44 +1,51 @@
package jadx.api;
import java.io.Closeable;
import java.io.File;
import java.io.StringWriter;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.jf.baksmali.Adaptors.ClassDefinition;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedClassDef;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.util.IndentingWriter;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.data.annotations.VarRef;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject;
import jadx.core.utils.DecompilerScheduler;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ProtoXMLParser;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResourcesSaver;
/**
@@ -46,13 +53,14 @@ import jadx.core.xmlgen.ResourcesSaver;
*
* <pre>
* <code>
*
* JadxArgs args = new JadxArgs();
* args.getInputFiles().add(new File("test.apk"));
* args.setOutDir(new File("jadx-test-output"));
*
* JadxDecompiler jadx = new JadxDecompiler(args);
* jadx.load();
* jadx.save();
* try (JadxDecompiler jadx = new JadxDecompiler(args)) {
* jadx.load();
* jadx.save();
* }
* </code>
* </pre>
* <p>
@@ -60,30 +68,32 @@ import jadx.core.xmlgen.ResourcesSaver;
*
* <pre>
* <code>
*
* for(JavaClass cls : jadx.getClasses()) {
* System.out.println(cls.getCode());
* }
* </code>
* </pre>
*/
public final class JadxDecompiler {
public final class JadxDecompiler implements Closeable {
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
private JadxArgs args;
private final List<InputFile> inputFiles = new ArrayList<>();
private final JadxArgs args;
private final JadxPluginManager pluginManager = new JadxPluginManager();
private final List<ILoadResult> loadedInputs = new ArrayList<>();
private RootNode root;
private List<IDexTreeVisitor> passes;
private List<JavaClass> classes;
private List<ResourceFile> resources;
private BinaryXMLParser xmlParser;
private BinaryXMLParser binaryXmlParser;
private ProtoXMLParser protoXmlParser;
private Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
private Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
public JadxDecompiler() {
this(new JadxArgs());
@@ -96,54 +106,102 @@ public final class JadxDecompiler {
public void load() {
reset();
JadxArgsValidator.validate(args);
init();
LOG.info("loading ...");
loadFiles(args.getInputFiles());
loadPlugins(args);
loadInputFiles();
root = new RootNode(args);
root.load(inputFiles);
root.loadClasses(loadedInputs);
root.initClassPath();
root.loadResources(getResources());
initVisitors();
root.runPreDecompileStage();
root.initPasses();
}
void init() {
this.passes = Jadx.getPassesList(args);
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);
}
}
}
void reset() {
private void reset() {
root = null;
classes = null;
resources = null;
xmlParser = null;
root = null;
passes = null;
binaryXmlParser = null;
protoXmlParser = null;
classesMap.clear();
methodsMap.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() {
return Jadx.getVersion();
}
private void loadFiles(List<File> files) {
if (files.isEmpty()) {
throw new JadxRuntimeException("Empty file list");
}
inputFiles.clear();
for (File file : files) {
try {
InputFile.addFilesFrom(file, inputFiles, args.isSkipSources());
} catch (Exception e) {
throw new JadxRuntimeException("Error load file: " + file, e);
}
}
}
public void save() {
save(!args.isSkipSources(), !args.isSkipResources());
}
public interface ProgressListener {
void progress(long done, long total);
}
@SuppressWarnings("BusyWait")
public void save(int intervalInMillis, ProgressListener listener) {
ThreadPoolExecutor ex = (ThreadPoolExecutor) getSaveExecutor();
ex.shutdown();
try {
long total = ex.getTaskCount();
while (ex.isTerminating()) {
long done = ex.getCompletedTaskCount();
listener.progress(done, total);
Thread.sleep(intervalInMillis);
}
} catch (InterruptedException e) {
LOG.error("Save interrupted", e);
Thread.currentThread().interrupt();
}
}
public void saveSources() {
save(true, false);
}
@@ -167,20 +225,44 @@ public final class JadxDecompiler {
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
}
public List<Runnable> getSaveTasks() {
return getSaveTasks(!args.isSkipSources(), !args.isSkipResources());
}
private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) {
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
LOG.info("processing ...");
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
List<Runnable> tasks = getSaveTasks(saveSources, saveResources);
tasks.forEach(executor::execute);
return executor;
}
private List<Runnable> getSaveTasks(boolean saveSources, boolean saveResources) {
if (root == null) {
throw new JadxRuntimeException("No loaded files");
}
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
LOG.info("processing ...");
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
File sourcesOutDir;
File resOutDir;
if (args.isExportAsGradleProject()) {
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir());
ResourceFile androidManifest = resources.stream()
.filter(resourceFile -> resourceFile.getType() == ResourceType.MANIFEST)
.findFirst()
.orElseThrow(IllegalStateException::new);
ResContainer strings = resources.stream()
.filter(resourceFile -> resourceFile.getType() == ResourceType.ARSC)
.findFirst()
.orElseThrow(IllegalStateException::new)
.loadContent()
.getSubFiles()
.stream()
.filter(resContainer -> resContainer.getFileName().contains("strings.xml"))
.findFirst()
.orElseThrow(IllegalStateException::new);
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir(), androidManifest, strings);
export.init();
sourcesOutDir = export.getSrcOutDir();
resOutDir = export.getResOutDir();
@@ -188,36 +270,51 @@ public final class JadxDecompiler {
sourcesOutDir = args.getOutDirSrc();
resOutDir = args.getOutDirRes();
}
List<Runnable> tasks = new ArrayList<>();
// save resources first because decompilation can hang or fail
if (saveResources) {
appendResourcesSave(executor, resOutDir);
appendResourcesSaveTasks(tasks, resOutDir);
}
if (saveSources) {
appendSourcesSave(executor, sourcesOutDir);
appendSourcesSave(tasks, sourcesOutDir);
}
return executor;
return tasks;
}
private void appendResourcesSave(ExecutorService executor, File outDir) {
private void appendResourcesSaveTasks(List<Runnable> tasks, File outDir) {
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
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) {
final Predicate<String> classFilter = args.getClassFilter();
for (JavaClass cls : getClasses()) {
private void appendSourcesSave(List<Runnable> tasks, File outDir) {
Predicate<String> classFilter = args.getClassFilter();
List<JavaClass> classes = getClasses();
List<JavaClass> processQueue = new ArrayList<>(classes.size());
for (JavaClass cls : classes) {
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
continue;
}
if (classFilter != null && !classFilter.test(cls.getFullName())) {
continue;
}
executor.execute(() -> {
try {
cls.decompile();
SaveCode.save(outDir, cls.getClassNode());
} catch (Exception e) {
LOG.error("Error saving class: {}", cls.getFullName(), e);
processQueue.add(cls);
}
for (List<JavaClass> decompileBatch : decompileScheduler.buildBatches(processQueue)) {
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);
}
}
});
}
@@ -248,7 +345,7 @@ public final class JadxDecompiler {
if (root == null) {
return Collections.emptyList();
}
resources = new ResourcesLoader(this).load(inputFiles);
resources = new ResourcesLoader(this).load();
}
return resources;
}
@@ -297,103 +394,272 @@ public final class JadxDecompiler {
root.getErrorsCounter().printReport();
}
private void initVisitors() {
for (IDexTreeVisitor pass : passes) {
try {
pass.init(root);
} catch (Exception e) {
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
}
}
}
void processClass(ClassNode cls) {
ProcessClass.process(cls, passes, true);
}
void generateSmali(ClassNode cls) {
Path path = cls.dex().getDexFile().getPath();
String className = Utils.makeQualifiedObjectName(cls.getClassInfo().getType().getObject());
try {
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(path.toFile(), Opcodes.getDefault());
boolean decompiled = false;
for (DexBackedClassDef classDef : dexFile.getClasses()) {
if (classDef.getType().equals(className)) {
ClassDefinition classDefinition = new ClassDefinition(new BaksmaliOptions(), classDef);
StringWriter sw = new StringWriter();
classDefinition.writeTo(new IndentingWriter(sw));
cls.setSmali(sw.toString());
decompiled = true;
break;
}
}
if (!decompiled) {
LOG.error("Failed to find smali class {}", className);
}
} catch (Exception e) {
LOG.error("Error generating smali", e);
}
}
RootNode getRoot() {
/**
* Internal API. Not Stable!
*/
public RootNode getRoot() {
return root;
}
List<IDexTreeVisitor> getPasses() {
return passes;
}
synchronized BinaryXMLParser getXmlParser() {
if (xmlParser == null) {
xmlParser = new BinaryXMLParser(root);
synchronized BinaryXMLParser getBinaryXmlParser() {
if (binaryXmlParser == null) {
binaryXmlParser = new BinaryXMLParser(root);
}
return xmlParser;
return binaryXmlParser;
}
Map<ClassNode, JavaClass> getClassesMap() {
return classesMap;
synchronized ProtoXMLParser getProtoXmlParser() {
if (protoXmlParser == null) {
protoXmlParser = new ProtoXMLParser(root);
}
return protoXmlParser;
}
Map<MethodNode, JavaMethod> getMethodsMap() {
return methodsMap;
private void loadJavaClass(JavaClass javaClass) {
javaClass.getMethods().forEach(mth -> methodsMap.put(mth.getMethodNode(), mth));
javaClass.getFields().forEach(fld -> fieldsMap.put(fld.getFieldNode(), fld));
for (JavaClass innerCls : javaClass.getInnerClasses()) {
classesMap.put(innerCls.getClassNode(), innerCls);
loadJavaClass(innerCls);
}
for (JavaClass inlinedCls : javaClass.getInlinedClasses()) {
classesMap.put(inlinedCls.getClassNode(), inlinedCls);
loadJavaClass(inlinedCls);
}
}
JavaMethod getJavaMethodByNode(MethodNode mth) {
/**
* 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")
@ApiStatus.Internal
public JavaClass getJavaClassByNode(ClassNode cls) {
JavaClass javaClass = classesMap.get(cls);
if (javaClass != null && javaClass.getClassNode() == cls) {
return javaClass;
}
// load parent class if inner
ClassNode parentClass = cls.getTopParentClass();
if (parentClass.contains(AFlag.DONT_GENERATE)) {
return null;
}
if (parentClass != cls) {
JavaClass parentJavaClass = classesMap.get(parentClass);
if (parentJavaClass == null) {
getClasses();
parentJavaClass = classesMap.get(parentClass);
}
loadJavaClass(parentJavaClass);
javaClass = classesMap.get(cls);
if (javaClass != null) {
return javaClass;
}
}
// class or parent classes can be excluded from generation
if (cls.hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaClass not found by ClassNode: " + cls);
}
@Nullable
private JavaMethod getJavaMethodByNode(MethodNode mth) {
JavaMethod javaMethod = methodsMap.get(mth);
if (javaMethod != null && javaMethod.getMethodNode() == mth) {
return javaMethod;
}
if (mth.contains(AFlag.DONT_GENERATE)) {
return null;
}
// parent class not loaded yet
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
if (javaClass == null) {
return null;
}
loadJavaClass(javaClass);
javaMethod = methodsMap.get(mth);
if (javaMethod != null) {
return javaMethod;
}
// parent class not loaded yet
JavaClass javaClass = classesMap.get(mth.getParentClass());
if (javaClass != null) {
javaClass.decompile();
return methodsMap.get(mth);
if (mth.getParentClass().hasNotGeneratedParent()) {
return null;
}
return null;
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
}
Map<FieldNode, JavaField> getFieldsMap() {
return fieldsMap;
}
JavaField getJavaFieldByNode(FieldNode fld) {
@Nullable
private JavaField getJavaFieldByNode(FieldNode fld) {
JavaField javaField = fieldsMap.get(fld);
if (javaField != null) {
if (javaField != null && javaField.getFieldNode() == fld) {
return javaField;
}
// parent class not loaded yet
JavaClass javaClass = classesMap.get(fld.getParentClass());
if (javaClass != null) {
javaClass.decompile();
return fieldsMap.get(fld);
JavaClass javaClass = getJavaClassByNode(fld.getParentClass().getTopParentClass());
if (javaClass == null) {
return null;
}
loadJavaClass(javaClass);
javaField = fieldsMap.get(fld);
if (javaField != null) {
return javaField;
}
if (fld.getParentClass().hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
}
@Nullable
public JavaClass searchJavaClassByOrigFullName(String fullName) {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
.findFirst()
.map(this::getJavaClassByNode)
.orElse(null);
}
@Nullable
public ClassNode searchClassNodeByOrigFullName(String fullName) {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
.findFirst()
.orElse(null);
}
// returns parent if class contains DONT_GENERATE flag.
@Nullable
public JavaClass searchJavaClassOrItsParentByOrigFullName(String fullName) {
ClassNode node = getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
.findFirst()
.orElse(null);
if (node != null) {
if (node.contains(AFlag.DONT_GENERATE)) {
return getJavaClassByNode(node.getTopParentClass());
} else {
return getJavaClassByNode(node);
}
}
return null;
}
@Nullable
public JavaClass searchJavaClassByAliasFullName(String fullName) {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
.findFirst()
.map(this::getJavaClassByNode)
.orElse(null);
}
@Nullable
JavaNode convertNode(Object obj) {
if (obj instanceof VarRef) {
VarRef varRef = (VarRef) obj;
MethodNode mthNode = varRef.getMth();
JavaMethod mth = getJavaMethodByNode(mthNode);
if (mth == null) {
return null;
}
return new JavaVariable(mth, varRef);
}
if (!(obj instanceof LineAttrNode)) {
return null;
}
LineAttrNode node = (LineAttrNode) obj;
if (node.contains(AFlag.DONT_GENERATE)) {
return null;
}
if (obj instanceof ClassNode) {
return convertClassNode((ClassNode) obj);
}
if (obj instanceof MethodNode) {
return getJavaMethodByNode(((MethodNode) obj));
}
if (obj instanceof FieldNode) {
return getJavaFieldByNode((FieldNode) obj);
}
throw new JadxRuntimeException("Unexpected node type: " + obj);
}
// 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
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
Map<CodePosition, Object> map = codeInfo.getAnnotations();
if (map.isEmpty()) {
return null;
}
Object obj = map.get(new CodePosition(line, offset));
if (obj == null) {
return null;
}
return convertNode(obj);
}
@Nullable
public CodePosition getDefinitionPosition(JavaNode javaNode) {
JavaClass jCls = javaNode.getTopParentClass();
jCls.decompile();
int defLine = javaNode.getDecompiledLine();
if (defLine == 0) {
return null;
}
return new CodePosition(defLine, 0, javaNode.getDefPos());
}
public void reloadCodeData() {
root.notifyCodeDataListeners();
}
public JadxArgs getArgs() {
return args;
}
public JadxPluginManager getPluginManager() {
return pluginManager;
}
public IDecompileScheduler getDecompileScheduler() {
return decompileScheduler;
}
@Override
public String toString() {
return "jadx decompiler " + getVersion();
+131 -81
View File
@@ -7,11 +7,10 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
@@ -24,8 +23,10 @@ public final class JavaClass implements JavaNode {
private final JavaClass parent;
private List<JavaClass> innerClasses = Collections.emptyList();
private List<JavaClass> inlinedClasses = Collections.emptyList();
private List<JavaField> fields = Collections.emptyList();
private List<JavaMethod> methods = Collections.emptyList();
private boolean listsLoaded;
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
this.decompiler = decompiler;
@@ -43,69 +44,81 @@ public final class JavaClass implements JavaNode {
}
public String getCode() {
CodeWriter code = cls.getCode();
ICodeInfo code = getCodeInfo();
if (code == null) {
decompile();
code = cls.getCode();
if (code == null) {
return "";
}
return "";
}
return code.getCodeStr();
}
public synchronized void decompile() {
if (decompiler == null) {
return;
}
if (cls.getCode() == null) {
decompiler.processClass(cls);
load();
}
public ICodeInfo getCodeInfo() {
return cls.decompile();
}
public void decompile() {
cls.decompile();
}
public synchronized void reload() {
listsLoaded = false;
cls.reloadCode();
}
public void unload() {
listsLoaded = false;
cls.unloadCode();
}
public synchronized String getSmali() {
if (decompiler == null) {
return null;
}
if (cls.getSmali() == null) {
decompiler.generateSmali(cls);
}
return cls.getSmali();
}
public synchronized void unload() {
cls.unload();
return cls.getDisassembledCode();
}
/**
* Internal API. Not Stable!
*/
@ApiStatus.Internal
public ClassNode getClassNode() {
return cls;
}
private void load() {
private synchronized void loadLists() {
if (listsLoaded) {
return;
}
listsLoaded = true;
decompile();
JadxDecompiler rootDecompiler = getRootDecompiler();
int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(inner, this);
javaClass.load();
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists();
list.add(javaClass);
rootDecompiler.getClassesMap().put(inner, javaClass);
}
}
this.innerClasses = Collections.unmodifiableList(list);
}
int inlinedClsCount = cls.getInlinedClasses().size();
if (inlinedClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
for (ClassNode inner : cls.getInlinedClasses()) {
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists();
list.add(javaClass);
}
this.inlinedClasses = Collections.unmodifiableList(list);
}
int fieldsCount = cls.getFields().size();
if (fieldsCount != 0) {
List<JavaField> flds = new ArrayList<>(fieldsCount);
for (FieldNode f : cls.getFields()) {
if (!f.contains(AFlag.DONT_GENERATE)) {
JavaField javaField = new JavaField(f, this);
JavaField javaField = new JavaField(this, f);
flds.add(javaField);
rootDecompiler.getFieldsMap().put(f, javaField);
}
}
this.fields = Collections.unmodifiableList(flds);
@@ -118,7 +131,6 @@ public final class JavaClass implements JavaNode {
if (!m.contains(AFlag.DONT_GENERATE)) {
JavaMethod javaMethod = new JavaMethod(this, m);
mths.add(javaMethod);
rootDecompiler.getMethodsMap().put(m, javaMethod);
}
}
mths.sort(Comparator.comparing(JavaMethod::getName));
@@ -126,22 +138,25 @@ public final class JavaClass implements JavaNode {
}
}
private JadxDecompiler getRootDecompiler() {
protected JadxDecompiler getRootDecompiler() {
if (parent != null) {
return parent.getRootDecompiler();
}
return decompiler;
}
private Map<CodePosition, Object> getCodeAnnotations() {
decompile();
CodeWriter code = cls.getCode();
public Map<CodePosition, Object> getCodeAnnotations() {
ICodeInfo code = getCodeInfo();
if (code == null) {
return Collections.emptyMap();
}
return code.getAnnotations();
}
public Object getAnnotationAt(CodePosition pos) {
return getCodeAnnotations().get(pos);
}
public Map<CodePosition, JavaNode> getUsageMap() {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty() || decompiler == null) {
@@ -151,60 +166,50 @@ public final class JavaClass implements JavaNode {
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
if (obj instanceof LineAttrNode) {
JavaNode node = convertNode(obj);
if (node != null) {
resultMap.put(codePosition, node);
}
JavaNode node = getRootDecompiler().convertNode(obj);
if (node != null) {
resultMap.put(codePosition, node);
}
}
return resultMap;
}
@Nullable
private JavaNode convertNode(Object obj) {
if (!(obj instanceof LineAttrNode)) {
return null;
}
if (obj instanceof ClassNode) {
return getRootDecompiler().getClassesMap().get(obj);
}
if (obj instanceof MethodNode) {
return getRootDecompiler().getJavaMethodByNode(((MethodNode) obj));
}
if (obj instanceof FieldNode) {
return getRootDecompiler().getJavaFieldByNode((FieldNode) obj);
}
return null;
}
@Nullable
public JavaNode getJavaNodeAtPosition(int line, int offset) {
public List<CodePosition> getUsageFor(JavaNode javaNode) {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty()) {
return null;
if (map.isEmpty() || decompiler == null) {
return Collections.emptyList();
}
Object obj = map.get(new CodePosition(line, offset));
if (obj == null) {
return null;
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 convertNode(obj);
return result;
}
@Override
public List<JavaNode> getUseIn() {
return getRootDecompiler().convertNodes(cls.getUseIn());
}
@Nullable
public CodePosition getDefinitionPosition(JavaNode javaNode) {
JavaClass jCls = javaNode.getTopParentClass();
jCls.decompile();
int defLine = javaNode.getDecompiledLine();
if (defLine == 0) {
return null;
}
return new CodePosition(jCls, defLine, 0);
@Deprecated
public JavaNode getJavaNodeAtPosition(int line, int offset) {
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
}
@Nullable
@Deprecated
public CodePosition getDefinitionPosition() {
return getRootDecompiler().getDefinitionPosition(this);
}
public Integer getSourceLine(int decompiledLine) {
decompile();
return cls.getCode().getLineMapping().get(decompiledLine);
return getCodeInfo().getLineMapping().get(decompiledLine);
}
@Override
@@ -217,6 +222,10 @@ public final class JavaClass implements JavaNode {
return cls.getFullName();
}
public String getRawName() {
return cls.getRawName();
}
public String getPackage() {
return cls.getPackage();
}
@@ -228,32 +237,73 @@ public final class JavaClass implements JavaNode {
@Override
public JavaClass getTopParentClass() {
if (cls.contains(AFlag.ANONYMOUS_CLASS)) {
// moved to usage class
return getParentForAnonymousClass();
}
return parent == null ? this : parent.getTopParentClass();
}
private JavaClass getParentForAnonymousClass() {
List<JavaNode> useIn = getUseIn();
if (useIn.isEmpty()) {
return this;
}
JavaNode useNode = useIn.get(0);
if (useNode.equals(this)) {
return this;
}
return useNode.getTopParentClass();
}
public AccessInfo getAccessInfo() {
return cls.getAccessFlags();
}
public List<JavaClass> getInnerClasses() {
decompile();
loadLists();
return innerClasses;
}
public List<JavaClass> getInlinedClasses() {
loadLists();
return inlinedClasses;
}
public List<JavaField> getFields() {
decompile();
loadLists();
return fields;
}
public List<JavaMethod> getMethods() {
decompile();
loadLists();
return methods;
}
@Nullable
public JavaMethod searchMethodByShortId(String shortId) {
MethodNode methodNode = cls.searchMethodByShortId(shortId);
if (methodNode == null) {
return null;
}
return new JavaMethod(this, methodNode);
}
@Override
public void removeAlias() {
this.cls.getClassInfo().removeAlias();
}
@Override
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
@Override
public int getDefPos() {
return cls.getDefPosition();
}
@Override
public boolean equals(Object o) {
return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
@@ -1,5 +1,9 @@
package jadx.api;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
@@ -9,7 +13,7 @@ public final class JavaField implements JavaNode {
private final FieldNode field;
private final JavaClass parent;
JavaField(FieldNode f, JavaClass cls) {
JavaField(JavaClass cls, FieldNode f) {
this.field = f;
this.parent = cls;
}
@@ -39,13 +43,37 @@ public final class JavaField implements JavaNode {
}
public ArgType getType() {
return ArgType.tryToResolveClassAlias(field.dex(), field.getType());
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
}
@Override
public int getDecompiledLine() {
return field.getDecompiledLine();
}
@Override
public int getDefPos() {
return field.getDefPosition();
}
@Override
public List<JavaNode> getUseIn() {
return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn());
}
@Override
public void removeAlias() {
this.field.getFieldInfo().removeAlias();
}
/**
* Internal API. Not Stable!
*/
@ApiStatus.Internal
public FieldNode getFieldNode() {
return field;
}
@Override
public int hashCode() {
return field.hashCode();
@@ -3,12 +3,15 @@ package jadx.api;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jetbrains.annotations.ApiStatus;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils;
public final class JavaMethod implements JavaNode {
private final MethodNode mth;
@@ -44,27 +47,34 @@ public final class JavaMethod implements JavaNode {
}
public List<ArgType> getArguments() {
if (mth.getMethodInfo().getArgumentsTypes().isEmpty()) {
List<ArgType> infoArgTypes = mth.getMethodInfo().getArgumentsTypes();
if (infoArgTypes.isEmpty()) {
return Collections.emptyList();
}
List<RegisterArg> arguments = mth.getArguments(false);
Stream<ArgType> argTypeStream;
if (arguments == null || arguments.isEmpty() || mth.isNoCode()) {
argTypeStream = mth.getMethodInfo().getArgumentsTypes().stream();
} else {
argTypeStream = arguments.stream().map(RegisterArg::getType);
}
return argTypeStream
.map(type -> ArgType.tryToResolveClassAlias(mth.dex(), type))
.collect(Collectors.toList());
List<ArgType> arguments = mth.getArgTypes();
return Utils.collectionMap(arguments,
type -> ArgType.tryToResolveClassAlias(mth.root(), type));
}
public ArgType getReturnType() {
ArgType retType = mth.getReturnType();
if (retType == null) {
retType = mth.getMethodInfo().getReturnType();
return ArgType.tryToResolveClassAlias(mth.root(), retType);
}
@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() {
@@ -75,10 +85,29 @@ public final class JavaMethod implements JavaNode {
return mth.getMethodInfo().isClassInit();
}
@Override
public int getDecompiledLine() {
return mth.getDecompiledLine();
}
@Override
public int getDefPos() {
return mth.getDefPosition();
}
@Override
public void removeAlias() {
this.mth.getMethodInfo().removeAlias();
}
/**
* Internal API. Not Stable!
*/
@ApiStatus.Internal
public MethodNode getMethodNode() {
return mth;
}
@Override
public int hashCode() {
return mth.hashCode();
@@ -1,5 +1,7 @@
package jadx.api;
import java.util.List;
public interface JavaNode {
String getName();
@@ -11,4 +13,11 @@ public interface JavaNode {
JavaClass getTopParentClass();
int getDecompiledLine();
int getDefPos();
List<JavaNode> getUseIn();
default void removeAlias() {
}
}
@@ -1,5 +1,6 @@
package jadx.api;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.NotNull;
@@ -43,6 +44,16 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return 0;
}
@Override
public int getDefPos() {
return 0;
}
@Override
public List<JavaNode> getUseIn() {
return Collections.emptyList();
}
@Override
public int compareTo(@NotNull JavaPackage o) {
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 jadx.core.utils.files.ZipSecurity;
import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.entry.ResourceEntry;
public class ResourceFile {
@@ -34,6 +35,11 @@ public class ResourceFile {
private final String name;
private final ResourceType type;
private ZipRef zipRef;
private String deobfName;
public static ResourceFile createResourceFile(JadxDecompiler decompiler, File file, ResourceType type) {
return new ResourceFile(decompiler, file.getAbsolutePath(), type);
}
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) {
@@ -48,10 +54,14 @@ public class ResourceFile {
this.type = type;
}
public String getName() {
public String getOriginalName() {
return name;
}
public String getDeobfName() {
return deobfName != null ? deobfName : name;
}
public ResourceType getType() {
return type;
}
@@ -64,6 +74,15 @@ public class ResourceFile {
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() {
return zipRef;
}
@@ -1,18 +1,17 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.xmlgen.ResContainer;
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);
this.content = content;
}
@Override
public ResContainer loadContent() {
return ResContainer.textResource(getName(), content);
return ResContainer.textResource(getDeobfName(), content);
}
}
@@ -1,13 +1,20 @@
package jadx.api;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import jadx.core.utils.exceptions.JadxRuntimeException;
public enum ResourceType {
CODE(".dex", ".jar", ".class"),
MANIFEST("AndroidManifest.xml"),
XML(".xml"),
ARSC(".arsc"),
FONT(".ttf"),
FONT(".ttf", ".otf"),
IMG(".png", ".gif", ".jpg"),
MEDIA(".mp3", ".wav"),
LIB(".so"),
MANIFEST,
UNKNOWN;
private final String[] exts;
@@ -20,14 +27,34 @@ public enum ResourceType {
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 (String ext : type.getExts()) {
if (fileName.endsWith(ext)) {
return type;
ResourceType prev = EXT_MAP.put(ext, 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;
}
}
@@ -7,7 +7,6 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -16,13 +15,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResProtoParser;
import jadx.core.xmlgen.ResTableParser;
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
@@ -38,10 +39,11 @@ public final class ResourcesLoader {
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());
for (InputFile file : inputFiles) {
loadFile(list, file.getFile());
for (File file : inputFiles) {
loadFile(list, file);
}
return list;
}
@@ -54,7 +56,7 @@ public final class ResourcesLoader {
try {
ZipRef zipRef = rf.getZipRef();
if (zipRef == null) {
File file = new File(rf.getName());
File file = new File(rf.getOriginalName());
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
return decoder.decode(file.length(), inputStream);
}
@@ -67,13 +69,13 @@ public final class ResourcesLoader {
if (!ZipSecurity.isValidZipEntry(entry)) {
return null;
}
try (InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(entry))) {
try (InputStream inputStream = ZipSecurity.getInputStreamForEntry(zipFile, entry)) {
return decoder.decode(entry.getSize(), inputStream);
}
}
}
} 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));
} catch (JadxException e) {
LOG.error("Decode error", e);
CodeWriter cw = new CodeWriter();
ICodeWriter cw = jadxRef.getRoot().makeCodeWriter();
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
Utils.appendStackTrace(cw, e.getCause());
return ResContainer.textResource(rf.getName(), cw);
return ResContainer.textResource(rf.getDeobfName(), cw.finish());
}
}
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream) throws IOException {
RootNode root = jadxRef.getRoot();
switch (rf.getType()) {
case MANIFEST:
case XML:
CodeWriter content = jadxRef.getXmlParser().parse(inputStream);
return ResContainer.textResource(rf.getName(), content);
case XML: {
ICodeInfo content;
if (root.isProto()) {
content = jadxRef.getProtoXmlParser().parse(inputStream);
} else {
content = jadxRef.getBinaryXmlParser().parse(inputStream);
}
return ResContainer.textResource(rf.getDeobfName(), content);
}
case ARSC:
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream);
if (root.isProto()) {
return new ResProtoParser(root).decodeFiles(inputStream);
} else {
return new ResTableParser(root).decodeFiles(inputStream);
}
case IMG:
return decodeImage(rf, inputStream);
@@ -109,13 +122,12 @@ public final class ResourcesLoader {
}
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
String name = rf.getName();
String name = rf.getOriginalName();
if (name.endsWith(".9.png")) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
decoder.decode(inputStream, os);
return ResContainer.decodedData(rf.getName(), os.toByteArray());
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
} catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
}
@@ -124,29 +136,17 @@ public final class ResourcesLoader {
}
private void loadFile(List<ResourceFile> list, File file) {
if (file == null) {
if (file == null || file.isDirectory()) {
return;
}
try (ZipFile zip = new ZipFile(file)) {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (ZipSecurity.isValidZipEntry(entry)) {
addEntry(list, file, entry);
}
}
} 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);
if (FileUtils.isZipFile(file)) {
ZipSecurity.visitZipEntries(file, (zipFile, entry) -> {
addEntry(list, file, entry);
return null;
});
} else {
ResourceType type = ResourceType.getFileType(file.getAbsolutePath());
list.add(ResourceFile.createResourceFile(jadxRef, file, type));
}
}
@@ -163,11 +163,9 @@ public final class ResourcesLoader {
}
}
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
CodeWriter cw = new CodeWriter();
public static ICodeInfo loadToCodeWriter(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
copyStream(is, baos);
cw.add(baos.toString("UTF-8"));
return cw;
return new SimpleCodeInfo(baos.toString("UTF-8"));
}
}
@@ -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;
});
}
}
}
@@ -0,0 +1,34 @@
package jadx.api.impl;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
public class InMemoryCodeCache implements ICodeCache {
private final Map<String, ICodeInfo> storage = new ConcurrentHashMap<>();
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
storage.put(clsFullName, codeInfo);
}
@Override
public void remove(String clsFullName) {
storage.remove(clsFullName);
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
return storage.get(clsFullName);
}
@Override
public String toString() {
return "InMemoryCodeCache: size=" + storage.size();
}
}
@@ -0,0 +1,29 @@
package jadx.api.impl;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
public class NoOpCodeCache implements ICodeCache {
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
// do nothing
}
@Override
public void remove(String clsFullName) {
// do nothing
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
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 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_STRING = "java.lang.String";
public static final String CLASS_CLASS = "java.lang.Class";
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_STRING_BUILDER = "java.lang.StringBuilder";
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 OVERRIDE_ANNOTATION = "Ljava/lang/Override;";
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
+114 -59
View File
@@ -10,32 +10,45 @@ import java.util.jar.Manifest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs;
import jadx.core.dex.visitors.AnonymousClassVisitor;
import jadx.core.dex.visitors.AttachCommentsVisitor;
import jadx.core.dex.visitors.AttachMethodDetails;
import jadx.core.dex.visitors.AttachTryCatchVisitor;
import jadx.core.dex.visitors.CheckCode;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.ConstInlineVisitor;
import jadx.core.dex.visitors.ConstructorVisitor;
import jadx.core.dex.visitors.DependencyCollector;
import jadx.core.dex.visitors.DeboxingVisitor;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.ExtractFieldInit;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.FixAccessModifiers;
import jadx.core.dex.visitors.GenericTypesVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.MarkFinallyVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.InlineMethods;
import jadx.core.dex.visitors.MarkMethodsForInline;
import jadx.core.dex.visitors.MethodInvokeVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.MoveInlineVisitor;
import jadx.core.dex.visitors.OverrideMethodVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ProcessAnonymous;
import jadx.core.dex.visitors.ProcessInstructionsVisitor;
import jadx.core.dex.visitors.ProcessMethodsForInline;
import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.RenameVisitor;
import jadx.core.dex.visitors.ShadowFieldVisitor;
import jadx.core.dex.visitors.SignatureProcessor;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.dex.visitors.blocks.BlockProcessor;
import jadx.core.dex.visitors.blocks.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.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.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
@@ -43,9 +56,12 @@ import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
import jadx.core.dex.visitors.rename.CodeRenameVisitor;
import jadx.core.dex.visitors.rename.RenameVisitor;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
@@ -59,66 +75,105 @@ public class Jadx {
}
}
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
public static List<IDexTreeVisitor> getFallbackPassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new AttachTryCatchVisitor());
passes.add(new AttachCommentsVisitor());
passes.add(new ProcessInstructionsVisitor());
passes.add(new FallbackModeVisitor());
return passes;
}
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new SignatureProcessor());
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()) {
passes.add(new FallbackModeVisitor());
} else {
if (args.isDebugInfo()) {
passes.add(new DebugInfoParseVisitor());
}
return getFallbackPassesList();
}
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new BlockSplitter());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
// instructions IR
passes.add(new CheckCode());
if (args.isDebugInfo()) {
passes.add(new DebugInfoAttachVisitor());
}
passes.add(new AttachTryCatchVisitor());
if (args.getCommentsLevel() != CommentsLevel.NONE) {
passes.add(new AttachCommentsVisitor());
}
passes.add(new AttachMethodDetails());
passes.add(new ProcessInstructionsVisitor());
passes.add(new BlockProcessor());
passes.add(new BlockExceptionHandler());
passes.add(new BlockFinish());
// blocks IR
passes.add(new BlockSplitter());
passes.add(new BlockProcessor());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
passes.add(new SSATransform());
passes.add(new ConstructorVisitor());
passes.add(new InitCodeVariables());
passes.add(new SSATransform());
passes.add(new MoveInlineVisitor());
passes.add(new ConstructorVisitor());
passes.add(new InitCodeVariables());
if (args.isExtractFinally()) {
passes.add(new MarkFinallyVisitor());
passes.add(new ConstInlineVisitor());
passes.add(new TypeInferenceVisitor());
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
}
passes.add(new ConstInlineVisitor());
passes.add(new TypeInferenceVisitor());
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 ModVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump());
}
// regions IR
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
passes.add(new CleanRegions());
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
passes.add(new CleanRegions());
passes.add(new CodeShrinkVisitor());
passes.add(new MethodInvokeVisitor());
passes.add(new SimplifyVisitor());
passes.add(new CheckRegions());
passes.add(new CodeShrinkVisitor());
passes.add(new SimplifyVisitor());
passes.add(new CheckRegions());
passes.add(new EnumVisitor());
passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers());
passes.add(new ClassModifier());
passes.add(new LoopRegionVisitor());
passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers());
passes.add(new ProcessAnonymous());
passes.add(new ClassModifier());
passes.add(new MethodInlineVisitor());
passes.add(new EnumVisitor());
passes.add(new LoopRegionVisitor());
passes.add(new ProcessVariables());
passes.add(new PrepareForCodeGen());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
}
passes.add(new DependencyCollector());
passes.add(new RenameVisitor());
if (args.isInlineMethods()) {
passes.add(new MarkMethodsForInline());
}
passes.add(new ProcessVariables());
passes.add(new PrepareForCodeGen());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
}
return passes;
}
@@ -1,51 +1,110 @@
package jadx.core;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeInfo;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.LoadStage;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.ErrorsCounter;
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.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESSED;
import static jadx.core.dex.nodes.ProcessState.STARTED;
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
public final class ProcessClass {
private ProcessClass() {
}
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, boolean generateCode) {
if (!generateCode && cls.getState() == PROCESSED) {
return;
@Nullable
private static ICodeInfo process(ClassNode cls, boolean codegen) {
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
// nothing to do
return null;
}
synchronized (getSyncObj(cls)) {
synchronized (cls.getClassInfo()) {
try {
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
cls.remove(AFlag.CLASS_DEEP_RELOAD);
cls.deepUnload();
cls.root().runPreDecompileStageForClass(cls);
}
if (cls.contains(AFlag.CLASS_UNLOADED)) {
cls.remove(AFlag.CLASS_UNLOADED);
cls.root().runPreDecompileStageForClass(cls);
}
if (codegen) {
if (cls.getState() == GENERATED_AND_UNLOADED) {
// allow to run code generation again
cls.setState(NOT_LOADED);
}
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
cls.unload();
}
} else {
cls.setLoadStage(LoadStage.PROCESS_STAGE);
}
if (cls.getState() == NOT_LOADED) {
cls.load();
cls.setState(STARTED);
for (IDexTreeVisitor visitor : passes) {
}
if (cls.getState() == LOADED) {
cls.setState(PROCESS_STARTED);
for (IDexTreeVisitor visitor : cls.root().getPasses()) {
DepthTraversal.visit(visitor, cls);
}
cls.setState(PROCESSED);
cls.setState(PROCESS_COMPLETE);
}
if (cls.getState() == PROCESSED && generateCode) {
processDependencies(cls, passes);
CodeGen.generate(cls);
if (codegen) {
ICodeInfo code = CodeGen.generate(cls);
if (!cls.contains(AFlag.DONT_UNLOAD_CLASS)) {
cls.unload();
cls.setState(GENERATED_AND_UNLOADED);
}
return code;
}
} catch (Exception e) {
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
return null;
} catch (Throwable e) {
if (codegen) {
throw e;
}
cls.addError("Class process error: " + e.getClass().getSimpleName(), e);
return null;
}
}
}
public static Object getSyncObj(ClassNode cls) {
return cls.getClassInfo();
}
private static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
cls.getDependencies().forEach(depCls -> process(depCls, passes, false));
@NotNull
public static ICodeInfo generateCode(ClassNode cls) {
ClassNode topParentClass = cls.getTopParentClass();
if (topParentClass != cls) {
return generateCode(topParentClass);
}
try {
for (ClassNode depCls : cls.getDependencies()) {
process(depCls, false);
}
if (!cls.getCodegenDeps().isEmpty()) {
process(cls, false);
for (ClassNode codegenDep : cls.getCodegenDeps()) {
process(codegenDep, false);
}
}
ICodeInfo code = process(cls, true);
if (code == null) {
throw new JadxRuntimeException("Codegen failed");
}
return code;
} catch (Throwable e) {
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
}
}
}
+312 -266
View File
@@ -1,5 +1,6 @@
package jadx.core.clsp;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -12,25 +13,31 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
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 java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.utils.ZipSecurity;
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.RegisterArg;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
/**
* Classes list for import into classpath graph
@@ -40,126 +47,130 @@ public class ClsSet {
private static final String CLST_EXTENSION = ".jcst";
private static final String CLST_FILENAME = "core" + CLST_EXTENSION;
private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/');
private static final String CLST_PATH = "/clst/" + CLST_FILENAME;
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
private static final int VERSION = 2;
private static final int VERSION = 3;
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 final RootNode root;
public ClsSet(RootNode root) {
this.root = root;
}
private enum TypeEnum {
WILDCARD, GENERIC, GENERIC_TYPE, OBJECT, ARRAY, PRIMITIVE
WILDCARD,
GENERIC,
GENERIC_TYPE_VARIABLE,
OUTER_GENERIC,
OBJECT,
ARRAY,
PRIMITIVE
}
private NClass[] classes;
private ClspClass[] classes;
public void load(RootNode root) {
public void loadFromClstFile() throws IOException, DecodeException {
long startTime = System.currentTimeMillis();
try (InputStream input = ClsSet.class.getResourceAsStream(CLST_PATH)) {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_PATH);
}
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) {
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;
for (ClassNode cls : list) {
String clsRawName = cls.getRawName();
if (cls.getAccessFlags().isPublic()) {
cls.load();
NClass nClass = new NClass(clsRawName, k);
if (names.put(clsRawName, nClass) != null) {
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
k++;
nClass.setMethods(loadMethods(cls, nClass));
} else {
names.put(clsRawName, null);
ArgType clsType = cls.getClassInfo().getType();
String clsRawName = clsType.getObject();
cls.load();
ClspClass nClass = new ClspClass(clsType, k);
if (names.put(clsRawName, nClass) != null) {
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
k++;
nClass.setTypeParameters(cls.getGenericTypeParameters());
nClass.setMethods(getMethodsDetails(cls));
}
classes = new NClass[k];
classes = new ClspClass[k];
k = 0;
for (ClassNode cls : list) {
if (cls.getAccessFlags().isPublic()) {
NClass nClass = getCls(cls.getRawName(), names);
if (nClass == null) {
throw new JadxRuntimeException("Missing class: " + cls);
}
nClass.setParents(makeParentsArray(cls, names));
classes[k] = nClass;
k++;
ClspClass nClass = getCls(cls, names);
if (nClass == null) {
throw new JadxRuntimeException("Missing class: " + cls);
}
nClass.setParents(makeParentsArray(cls));
classes[k] = nClass;
k++;
}
}
private NMethod[] loadMethods(ClassNode cls, NClass nClass) {
List<NMethod> methods = new ArrayList<>();
for (MethodNode m : cls.getMethods()) {
if (!m.getAccessFlags().isPublic()
&& !m.getAccessFlags().isProtected()) {
continue;
}
List<ArgType> args = new ArrayList<>();
boolean genericArg = false;
for (RegisterArg r : m.getArguments(false)) {
ArgType argType = r.getType();
if (argType.isGeneric() || argType.isGenericType()) {
args.add(argType);
genericArg = true;
} else {
args.add(null);
}
}
ArgType retType = m.getReturnType();
if (!retType.isGeneric() && !retType.isGenericType()) {
retType = null;
}
boolean varArgs = m.getAccessFlags().isVarArgs();
if (genericArg || retType != null || varArgs) {
methods.add(new NMethod(
m.getMethodInfo().getShortId(),
args.isEmpty()
? new ArgType[0]
: args.toArray(new ArgType[args.size()]),
retType,
varArgs));
}
private List<ClspMethod> getMethodsDetails(ClassNode cls) {
List<MethodNode> methodsList = cls.getMethods();
List<ClspMethod> methods = new ArrayList<>(methodsList.size());
for (MethodNode mth : methodsList) {
processMethodDetails(mth, methods);
}
return methods.toArray(new NMethod[methods.size()]);
return methods;
}
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) {
AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isPrivate() || accessFlags.isSynthetic() || accessFlags.isBridge()) {
return;
}
ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(), mth.getArgTypes(),
mth.getReturnType(), mth.getTypeParameters(),
mth.getThrows(), accessFlags.rawValue());
methods.add(clspMethod);
}
public static ArgType[] makeParentsArray(ClassNode cls) {
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
NClass c = getCls(superClass.getObject(), names);
if (c != null) {
parents.add(c);
}
if (superClass == null) {
// cls is java.lang.Object
return EMPTY_ARGTYPE_ARRAY;
}
ArgType[] parents = new ArgType[1 + cls.getInterfaces().size()];
parents[0] = superClass;
int k = 1;
for (ArgType iface : cls.getInterfaces()) {
NClass c = getCls(iface.getObject(), names);
if (c != null) {
parents.add(c);
}
parents[k] = iface;
k++;
}
int size = parents.size();
if (size == 0) {
return EMPTY_NCLASS_ARRAY;
}
return parents.toArray(new NClass[size]);
return parents;
}
private static NClass getCls(String fullName, Map<String, NClass> names) {
NClass cls = names.get(fullName);
private static ClspClass getCls(ClassNode cls, Map<String, ClspClass> names) {
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) {
LOG.debug("Class not found: {}", fullName);
}
return cls;
}
void save(Path path) throws IOException {
public void save(Path path) throws IOException {
FileUtils.makeDirsForFile(path);
String outputName = path.getFileName().toString();
if (outputName.endsWith(CLST_EXTENSION)) {
@@ -172,149 +183,155 @@ public class ClsSet {
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME;
out.putNextEntry(new ZipEntry(clst));
save(out);
String clst = CLST_PATH;
boolean clstReplaced = false;
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (!entry.getName().equals(clst)) {
out.putNextEntry(new ZipEntry(entry.getName()));
String entryName = 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);
}
entry = in.getNextEntry();
}
if (!clstReplaced) {
out.putNextEntry(new ZipEntry(clst));
save(out);
}
}
} else {
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);
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
LOG.info("Classes count: {}", classes.length);
Map<String, NClass> names = new HashMap<>(classes.length);
Map<String, ClspClass> names = new HashMap<>(classes.length);
out.writeInt(classes.length);
for (NClass cls : classes) {
writeString(out, cls.getName());
names.put(cls.getName(), cls);
for (ClspClass cls : classes) {
String clsName = cls.getName();
writeString(out, clsName);
names.put(clsName, cls);
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
}
NMethod[] methods = cls.getMethods();
out.writeByte(methods.length);
for (NMethod method : methods) {
for (ClspClass cls : classes) {
writeArgTypesArray(out, cls.getParents(), names);
writeArgTypesList(out, cls.getTypeParameters(), names);
List<ClspMethod> methods = cls.getSortedMethodsList();
out.writeShort(methods.size());
for (ClspMethod method : methods) {
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 writeMethod(DataOutputStream out, NMethod method, Map<String, NClass> names) throws IOException {
int argCount = 0;
ArgType[] argTypes = method.getArgType();
for (ArgType arg : argTypes) {
if (arg != null) {
argCount++;
}
}
private static void writeMethod(DataOutputStream out, ClspMethod method, Map<String, ClspClass> names) throws IOException {
MethodInfo methodInfo = method.getMethodInfo();
writeString(out, methodInfo.getName());
writeArgTypesList(out, methodInfo.getArgumentsTypes(), names);
writeArgType(out, methodInfo.getReturnType(), names);
writeLongString(out, method.getShortId());
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(true);
writeArgType(out, method.getReturnType(), names);
} else {
out.writeBoolean(false);
}
out.writeBoolean(method.isVarArgs());
writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names);
writeArgType(out, method.getReturnType(), names);
writeArgTypesList(out, method.getTypeParameters(), names);
out.writeInt(method.getRawAccessFlags());
writeArgTypesList(out, method.getThrows(), names);
}
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, NClass> names) throws IOException {
if (argType.getWildcardType() != null) {
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);
}
}
}
private static void writeArgTypesArray(DataOutputStream out, @Nullable ArgType[] arr, Map<String, ClspClass> names) throws IOException {
if (arr == null) {
out.writeByte(-1);
return;
}
int size = arr.length;
out.writeByte(size);
if (size != 0) {
for (ArgType type : arr) {
writeArgType(out, type, names);
}
}
}
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, ClspClass> names) throws IOException {
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());
int bounds = argType.getWildcardBounds();
out.writeByte(bounds);
if (bounds != 0) {
ArgType.WildcardBound bound = argType.getWildcardBound();
out.writeByte(bound.getNum());
if (bound != ArgType.WildcardBound.UNBOUND) {
writeArgType(out, argType.getWildcardType(), names);
}
} else if (argType.isGeneric()) {
out.writeByte(TypeEnum.GENERIC.ordinal());
out.writeInt(names.get(argType.getObject()).getId());
ArgType[] types = argType.getGenericTypes();
if (types == null) {
out.writeByte(0);
} else {
out.writeByte(types.length);
for (ArgType type : types) {
writeArgType(out, type, names);
}
}
out.writeInt(getCls(argType, names).getId());
writeArgTypesList(out, argType.getGenericTypes(), names);
} else if (argType.isGenericType()) {
out.writeByte(TypeEnum.GENERIC_TYPE.ordinal());
out.writeByte(TypeEnum.GENERIC_TYPE_VARIABLE.ordinal());
writeString(out, argType.getObject());
writeArgTypesList(out, argType.getExtendTypes(), names);
} else if (argType.isObject()) {
out.writeByte(TypeEnum.OBJECT.ordinal());
out.writeInt(names.get(argType.getObject()).getId());
out.writeInt(getCls(argType, names).getId());
} else if (argType.isArray()) {
out.writeByte(TypeEnum.ARRAY.ordinal());
writeArgType(out, argType.getArrayElement(), names);
} else if (argType.isPrimitive()) {
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
} else {
throw new JadxRuntimeException("Cannot save type: " + argType);
}
}
public void load() throws IOException, DecodeException {
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
}
}
public void load(File input) throws IOException, DecodeException {
private void load(File input) throws IOException, DecodeException {
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);
} else if (name.endsWith(".jar")) {
try (ZipInputStream in = new ZipInputStream(inputStream)) {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(CLST_EXTENSION) && ZipSecurity.isValidZipEntry(entry)) {
load(in);
}
entry = in.getNextEntry();
}
} else if (name.endsWith(".jar")) {
ZipSecurity.readZipEntries(input, (entry, in) -> {
if (entry.getName().endsWith(CLST_EXTENSION)) {
try {
load(in);
} catch (Exception e) {
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);
}
}
public void load(InputStream input) throws IOException, DecodeException {
try (DataInputStream in = new DataInputStream(input)) {
private void load(InputStream input) throws IOException, DecodeException {
try (DataInputStream in = new DataInputStream(new BufferedInputStream(input))) {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
@@ -323,96 +340,121 @@ public class ClsSet {
|| version != VERSION) {
throw new DecodeException("Wrong jadx class set header");
}
int count = in.readInt();
classes = new NClass[count];
for (int i = 0; i < count; i++) {
int clsCount = in.readInt();
classes = new ClspClass[clsCount];
for (int i = 0; i < clsCount; i++) {
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++) {
int pCount = in.readByte();
NClass[] parents = new NClass[pCount];
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
}
classes[i].setParents(parents);
int mCount = in.readByte();
NMethod[] methods = new NMethod[mCount];
for (int j = 0; j < mCount; j++) {
methods[j] = readMethod(in);
}
classes[i].setMethods(methods);
for (int i = 0; i < clsCount; i++) {
ClspClass nClass = classes[i];
ClassInfo clsInfo = ClassInfo.fromType(root, nClass.getClsType());
nClass.setParents(readArgTypesArray(in));
nClass.setTypeParameters(readArgTypesList(in));
nClass.setMethods(readClsMethods(in, clsInfo));
}
}
}
private NMethod readMethod(DataInputStream in) throws IOException {
String shortId = readLongString(in);
int argCount = in.readByte();
ArgType[] argTypes = null;
for (int i = 0; i < argCount; i++) {
int index = in.readByte();
ArgType argType = readArgType(in);
if (argTypes == null) {
argTypes = new ArgType[index + 1];
}
argTypes[index] = argType;
private List<ClspMethod> readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException {
int mCount = in.readShort();
List<ClspMethod> methods = new ArrayList<>(mCount);
for (int j = 0; j < mCount; j++) {
methods.add(readMethod(in, clsInfo));
}
ArgType retType = in.readBoolean() ? readArgType(in) : null;
boolean varArgs = in.readBoolean();
return new NMethod(shortId, argTypes, retType, varArgs);
return methods;
}
private ClspMethod readMethod(DataInputStream in, ClassInfo clsInfo) throws IOException {
String name = readString(in);
List<ArgType> argTypes = readArgTypesList(in);
ArgType retType = readArgType(in);
List<ArgType> genericArgTypes = readArgTypesList(in);
if (genericArgTypes.isEmpty() || Objects.equals(genericArgTypes, argTypes)) {
genericArgTypes = argTypes;
}
ArgType genericRetType = readArgType(in);
if (Objects.equals(genericRetType, retType)) {
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 {
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]) {
case WILDCARD:
int bounds = in.readByte();
return bounds == 0
? ArgType.wildcard()
: ArgType.wildcard(readArgType(in), bounds);
case GENERIC:
String obj = classes[in.readInt()].getName();
int typeLength = in.readByte();
ArgType[] generics;
if (typeLength == 0) {
generics = null;
} else {
generics = new ArgType[typeLength];
for (int i = 0; i < typeLength; i++) {
generics[i] = readArgType(in);
}
ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte());
if (bound == ArgType.WildcardBound.UNBOUND) {
return ArgType.WILDCARD;
}
return ArgType.generic(obj, generics);
case GENERIC_TYPE:
return ArgType.genericType(readString(in));
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:
ArgType clsType = classes[in.readInt()].getClsType();
return ArgType.generic(clsType, readArgTypesList(in));
case GENERIC_TYPE_VARIABLE:
String typeVar = readString(in);
List<ArgType> extendTypes = readArgTypesList(in);
return ArgType.genericType(typeVar, extendTypes);
case OBJECT:
return ArgType.object(classes[in.readInt()].getName());
return classes[in.readInt()].getClsType();
case ARRAY:
return ArgType.array(readArgType(in));
case PRIMITIVE:
int shortName = in.readByte();
switch (shortName) {
case 'Z':
return ArgType.BOOLEAN;
case 'C':
return ArgType.CHAR;
case 'B':
return ArgType.BYTE;
case 'S':
return ArgType.SHORT;
case 'I':
return ArgType.INT;
case 'F':
return ArgType.FLOAT;
case 'J':
return ArgType.LONG;
case 'D':
return ArgType.DOUBLE;
default:
return ArgType.VOID;
}
char shortName = (char) in.readByte();
return ArgType.parse(shortName);
default:
throw new JadxRuntimeException("Unsupported Arg Type: " + ordinal);
}
@@ -420,23 +462,16 @@ public class ClsSet {
private static void writeString(DataOutputStream out, String name) throws IOException {
byte[] bytes = name.getBytes(STRING_CHARSET);
out.writeByte(bytes.length);
out.write(bytes);
}
private static void writeLongString(DataOutputStream out, String name) throws IOException {
byte[] bytes = name.getBytes(STRING_CHARSET);
out.writeShort(bytes.length);
int len = bytes.length;
if (len >= 0xFF) {
throw new JadxRuntimeException("String is too long: " + name);
}
writeUnsignedByte(out, bytes.length);
out.write(bytes);
}
private static String readString(DataInputStream in) throws IOException {
int len = in.readByte();
return readString(in, len);
}
private static String readLongString(DataInputStream in) throws IOException {
int len = in.readShort();
int len = readUnsignedByte(in);
return readString(in, len);
}
@@ -454,12 +489,23 @@ public class ClsSet {
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() {
return classes.length;
}
public void addToMap(Map<String, NClass> nameMap) {
for (NClass cls : classes) {
public void addToMap(Map<String, ClspClass> nameMap) {
for (ClspClass cls : classes) {
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,29 +8,39 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
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.JadxRuntimeException;
/**
* Classes hierarchy graph
* Classes hierarchy graph with methods additional info
*/
public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>());
private Map<String, NClass> nameMap;
private final RootNode root;
private Map<String, ClspClass> nameMap;
private Map<String, Set<String>> superTypesCache;
private Map<String, List<String>> implementsCache;
private final Set<String> missingClasses = new HashSet<>();
public ClspGraph(RootNode rootNode) {
this.root = rootNode;
}
public void load() throws IOException, DecodeException {
ClsSet set = new ClsSet();
set.load();
ClsSet set = new ClsSet(root);
set.loadFromClstFile();
addClasspath(set);
}
@@ -47,51 +57,90 @@ public class ClspGraph {
if (nameMap == null) {
throw new JadxRuntimeException("Classpath must be loaded first");
}
int size = classes.size();
NClass[] nClasses = new NClass[size];
int k = 0;
for (ClassNode cls : classes) {
nClasses[k++] = addClass(cls);
}
for (int i = 0; i < size; i++) {
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
addClass(cls);
}
}
public void initCache() {
fillSuperTypesCache();
fillImplementsCache();
}
public boolean isClsKnown(String fullName) {
return nameMap.containsKey(fullName);
}
private NClass addClass(ClassNode cls) {
String rawName = cls.getRawName();
NClass nClass = new NClass(rawName, -1);
nameMap.put(rawName, nClass);
return nClass;
public ClspClass getClsDetails(ArgType type) {
return nameMap.get(type.getObject());
}
@Nullable
public IMethodDetails getMethodDetails(MethodInfo methodInfo) {
ClspClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
if (cls == 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());
}
private void addClass(ClassNode cls) {
ArgType clsType = cls.getClassInfo().getType();
String rawName = clsType.getObject();
ClspClass clspClass = new ClspClass(clsType, -1);
clspClass.setParents(ClsSet.makeParentsArray(cls));
nameMap.put(rawName, clspClass);
}
/**
* @return {@code clsName} instanceof {@code implClsName}
*/
public boolean isImplements(String clsName, String implClsName) {
Set<String> anc = getAncestors(clsName);
Set<String> anc = getSuperTypes(clsName);
return anc.contains(implClsName);
}
public List<String> getImplementations(String clsName) {
List<String> list = new ArrayList<>();
for (String cls : nameMap.keySet()) {
if (isImplements(cls, clsName)) {
list.add(cls);
List<String> list = implementsCache.get(clsName);
return list == null ? Collections.emptyList() : list;
}
private void fillImplementsCache() {
Map<String, List<String>> map = new HashMap<>(nameMap.size());
List<String> classes = new ArrayList<>(nameMap.keySet());
Collections.sort(classes);
for (String cls : classes) {
for (String st : getSuperTypes(cls)) {
map.computeIfAbsent(st, v -> new ArrayList<>()).add(cls);
}
}
return list;
implementsCache = map;
}
public String getCommonAncestor(String clsName, String implClsName) {
if (clsName.equals(implClsName)) {
return clsName;
}
NClass cls = nameMap.get(implClsName);
ClspClass cls = nameMap.get(implClsName);
if (cls == null) {
missingClasses.add(clsName);
return null;
@@ -99,52 +148,74 @@ public class ClspGraph {
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> anc = getAncestors(clsName);
Set<String> anc = getSuperTypes(clsName);
return searchCommonParent(anc, cls);
}
private String searchCommonParent(Set<String> anc, NClass cls) {
for (NClass p : cls.getParents()) {
String name = p.getName();
private String searchCommonParent(Set<String> anc, ClspClass cls) {
for (ArgType p : cls.getParents()) {
String name = p.getObject();
if (anc.contains(name)) {
return name;
}
String r = searchCommonParent(anc, p);
if (r != null) {
return r;
ClspClass nCls = getClspClass(p);
if (nCls != null) {
String r = searchCommonParent(anc, nCls);
if (r != null) {
return r;
}
}
}
return null;
}
public Set<String> getAncestors(String clsName) {
Set<String> result = ancestorCache.get(clsName);
if (result != null) {
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;
public Set<String> getSuperTypes(String clsName) {
Set<String> result = superTypesCache.get(clsName);
return result == null ? Collections.emptySet() : result;
}
private void addAncestorsNames(NClass cls, Set<String> result) {
boolean isNew = result.add(cls.getName());
if (isNew) {
for (NClass p : cls.getParents()) {
addAncestorsNames(p, result);
private void fillSuperTypesCache() {
Map<String, Set<String>> map = new HashMap<>(nameMap.size());
Set<String> tmpSet = new HashSet<>();
for (Map.Entry<String, ClspClass> entry : nameMap.entrySet()) {
ClspClass cls = entry.getValue();
tmpSet.clear();
addSuperTypes(cls, tmpSet);
Set<String> result;
if (tmpSet.isEmpty()) {
result = Collections.emptySet();
} else {
result = new HashSet<>(tmpSet);
}
map.put(cls.getName(), result);
}
superTypesCache = 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() {
int count = missingClasses.size();
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.load(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,63 +0,0 @@
package jadx.core.clsp;
/**
* Class node in classpath graph
*/
public class NClass {
private final String name;
private NClass[] parents;
private NMethod[] methods;
private final int id;
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;
}
@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;
}
public void setMethods(NMethod[] methods) {
this.methods = methods;
}
public NMethod[] getMethods() {
return methods;
}
}
@@ -1,37 +0,0 @@
package jadx.core.clsp;
import jadx.core.dex.instructions.args.ArgType;
/**
* Generic method node in classpath graph.
*/
public class NMethod {
private final String shortId;
private final ArgType[] argType;
private final ArgType retType;
private final boolean varArgs;
public NMethod(String shortId, ArgType[] argType, ArgType retType, boolean varArgs) {
this.shortId = shortId;
this.argType = argType;
this.retType = retType;
this.varArgs = varArgs;
}
public String getShortId() {
return shortId;
}
public ArgType[] getArgType() {
return argType;
}
public ArgType getReturnType() {
return retType;
}
public boolean isVarArgs() {
return 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 jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.IFieldRef;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr;
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
import jadx.core.Consts;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.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.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -31,61 +36,61 @@ public class AnnotationGen {
this.classGen = classGen;
}
public void addForClass(CodeWriter code) {
public void addForClass(ICodeWriter code) {
add(cls, code);
}
public void addForMethod(CodeWriter code, MethodNode mth) {
public void addForMethod(ICodeWriter code, MethodNode mth) {
add(mth, code);
}
public void addForField(CodeWriter code, FieldNode field) {
public void addForField(ICodeWriter code, FieldNode field) {
add(field, code);
}
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
List<AnnotationsList> paramList = paramsAnnotations.getParamList();
public void addForParameter(ICodeWriter code, MethodParamsAttr paramsAnnotations, int n) {
List<AnnotationsAttr> paramList = paramsAnnotations.getParamList();
if (n >= paramList.size()) {
return;
}
AnnotationsList aList = paramList.get(n);
AnnotationsAttr aList = paramList.get(n);
if (aList == null || aList.isEmpty()) {
return;
}
for (Annotation a : aList.getAll()) {
for (IAnnotation a : aList.getAll()) {
formatAnnotation(code, a);
code.add(' ');
}
}
private void add(IAttributeNode node, CodeWriter code) {
AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
private void add(IAttributeNode node, ICodeWriter code) {
AnnotationsAttr aList = node.get(JadxAttrType.ANNOTATION_LIST);
if (aList == null || aList.isEmpty()) {
return;
}
for (Annotation a : aList.getAll()) {
for (IAnnotation a : aList.getAll()) {
String aCls = a.getAnnotationClass();
if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
if (!aCls.equals(Consts.OVERRIDE_ANNOTATION)) {
code.startLine();
formatAnnotation(code, a);
}
}
}
private void formatAnnotation(CodeWriter code, Annotation a) {
private void formatAnnotation(ICodeWriter code, IAnnotation a) {
code.add('@');
ClassNode annCls = cls.dex().resolveClass(a.getType());
ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass());
if (annCls != null) {
classGen.useClass(code, annCls);
} 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()) {
code.add('(');
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext();) {
Entry<String, Object> e = it.next();
for (Iterator<Entry<String, EncodedValue>> it = vl.entrySet().iterator(); it.hasNext();) {
Entry<String, EncodedValue> e = it.next();
String paramName = getParamName(annCls, e.getKey());
if (paramName.equals("value") && vl.size() == 1) {
// don't add "value = " if no other parameters
@@ -93,7 +98,7 @@ public class AnnotationGen {
code.add(paramName);
code.add(" = ");
}
encodeValue(code, e.getValue());
encodeValue(cls.root(), code, e.getValue());
if (it.hasNext()) {
code.add(", ");
}
@@ -113,13 +118,11 @@ public class AnnotationGen {
return paramName;
}
@SuppressWarnings("unchecked")
public void addThrows(MethodNode mth, CodeWriter code) {
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
if (an != null) {
Object exs = an.getDefaultValue();
public void addThrows(MethodNode mth, ICodeWriter code) {
List<ArgType> throwList = mth.getThrows();
if (!throwList.isEmpty()) {
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();
classGen.useType(code, ex);
if (it.hasNext()) {
@@ -129,66 +132,93 @@ public class AnnotationGen {
}
}
public Object getAnnotationDefaultValue(String name) {
Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
if (an != null) {
Annotation defAnnotation = (Annotation) an.getDefaultValue();
return defAnnotation.getValues().get(name);
public EncodedValue getAnnotationDefaultValue(MethodNode mth) {
AnnotationDefaultAttr defaultAttr = mth.get(JadxAttrType.ANNOTATION_DEFAULT);
if (defaultAttr == null) {
return null;
}
return null;
return defaultAttr.getValue();
}
// TODO: refactor this boilerplate code
public void encodeValue(CodeWriter code, Object val) {
if (val == null) {
public void encodeValue(RootNode root, ICodeWriter code, EncodedValue encodedValue) {
if (encodedValue == null) {
code.add("null");
return;
}
if (val instanceof String) {
code.add(getStringUtils().unescapeString((String) val));
} else if (val instanceof Integer) {
code.add(TypeGen.formatInteger((Integer) val));
} else if (val instanceof Character) {
code.add(getStringUtils().unescapeChar((Character) val));
} else if (val instanceof Boolean) {
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
} else if (val instanceof Float) {
code.add(TypeGen.formatFloat((Float) val));
} else if (val instanceof Double) {
code.add(TypeGen.formatDouble((Double) val));
} else if (val instanceof Long) {
code.add(TypeGen.formatLong((Long) val));
} else if (val instanceof Short) {
code.add(TypeGen.formatShort((Short) val));
} else if (val instanceof Byte) {
code.add(TypeGen.formatByte((Byte) val));
} else if (val instanceof ArgType) {
classGen.useType(code, (ArgType) val);
code.add(".class");
} else if (val instanceof FieldInfo) {
// must be a static field
FieldInfo field = (FieldInfo) val;
InsnGen.makeStaticFieldAccess(code, field, classGen);
} else if (val instanceof Iterable) {
code.add('{');
Iterator<?> it = ((Iterable<?>) val).iterator();
while (it.hasNext()) {
Object obj = it.next();
encodeValue(code, obj);
if (it.hasNext()) {
code.add(", ");
Object value = encodedValue.getValue();
switch (encodedValue.getType()) {
case ENCODED_NULL:
code.add("null");
break;
case ENCODED_BOOLEAN:
code.add(Boolean.TRUE.equals(value) ? "true" : "false");
break;
case ENCODED_BYTE:
code.add(TypeGen.formatByte((Byte) value, false));
break;
case ENCODED_SHORT:
code.add(TypeGen.formatShort((Short) value, false));
break;
case ENCODED_CHAR:
code.add(getStringUtils().unescapeChar((Character) value));
break;
case ENCODED_INT:
code.add(TypeGen.formatInteger((Integer) value, false));
break;
case ENCODED_LONG:
code.add(TypeGen.formatLong((Long) value, false));
break;
case ENCODED_FLOAT:
code.add(TypeGen.formatFloat((Float) value));
break;
case ENCODED_DOUBLE:
code.add(TypeGen.formatDouble((Double) value));
break;
case ENCODED_STRING:
code.add(getStringUtils().unescapeString((String) value));
break;
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());
}
}
code.add('}');
} else if (val instanceof Annotation) {
formatAnnotation(code, (Annotation) val);
} else {
// TODO: also can be method values
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ')');
break;
case ENCODED_METHOD:
// TODO
break;
case ENCODED_ARRAY:
code.add('{');
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() {
return cls.dex().root().getStringUtils();
return cls.root().getStringUtils();
}
}
@@ -1,39 +1,52 @@
package jadx.core.codegen;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
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 org.jetbrains.annotations.Nullable;
import jadx.api.CommentsLevel;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.FieldInitInsnAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.EncodedValueUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -47,7 +60,12 @@ public class ClassGen {
private final boolean showInconsistentCode;
private final Set<ClassInfo> imports = new HashSet<>();
private int clsDeclLine;
private int clsDeclOffset;
private boolean bodyGenStarted;
@Nullable
private NameGen outerNameGen;
public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
@@ -71,11 +89,11 @@ public class ClassGen {
return cls;
}
public CodeWriter makeClass() throws CodegenException {
CodeWriter clsBody = new CodeWriter();
public ICodeInfo makeClass() throws CodegenException {
ICodeWriter clsBody = cls.root().makeCodeWriter();
addClassCode(clsBody);
CodeWriter clsCode = new CodeWriter();
ICodeWriter clsCode = cls.root().makeCodeWriter();
if (!"".equals(cls.getPackage())) {
clsCode.add("package ").add(cls.getPackage()).add(';');
clsCode.newLine();
@@ -100,36 +118,39 @@ public class ClassGen {
return clsCode.finish();
}
public void addClassCode(CodeWriter code) throws CodegenException {
public void addClassCode(ICodeWriter code) throws CodegenException {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
CodeGenUtils.addComments(code, cls);
insertDecompilationProblems(code, cls);
if (Consts.DEBUG_USAGE) {
addClassUsageInfo(code, cls);
}
CodeGenUtils.addErrorsAndComments(code, cls);
CodeGenUtils.addSourceFileInfo(code, cls);
addClassDeclaration(code);
addClassBody(code);
}
public void addClassDeclaration(CodeWriter clsCode) {
public void addClassDeclaration(ICodeWriter clsCode) {
AccessInfo af = cls.getAccessFlags();
if (af.isInterface()) {
af = af.remove(AccessFlags.ACC_ABSTRACT)
.remove(AccessFlags.ACC_STATIC);
af = af.remove(AccessFlags.ABSTRACT)
.remove(AccessFlags.STATIC);
} else if (af.isEnum()) {
af = af.remove(AccessFlags.ACC_FINAL)
.remove(AccessFlags.ACC_ABSTRACT)
.remove(AccessFlags.ACC_STATIC);
af = af.remove(AccessFlags.FINAL)
.remove(AccessFlags.ABSTRACT)
.remove(AccessFlags.STATIC);
}
// 'static' and 'private' modifier not allowed for top classes (not inner)
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);
insertRenameInfo(clsCode, cls);
CodeGenUtils.addSourceFileInfo(clsCode, cls);
clsCode.startLine(af.makeString());
CodeGenUtils.addInputFileInfo(clsCode, cls);
clsCode.startLineWithNum(cls.getSourceLine()).add(af.makeString(cls.checkCommentsLevel(CommentsLevel.INFO)));
if (af.isInterface()) {
if (af.isAnnotation()) {
clsCode.add('@');
@@ -143,13 +164,13 @@ public class ClassGen {
clsCode.attachDefinition(cls);
clsCode.add(cls.getClassInfo().getAliasShortName());
addGenericMap(clsCode, cls.getGenericMap(), true);
addGenericTypeParameters(clsCode, cls.getGenericTypeParameters(), true);
clsCode.add(' ');
ArgType sup = cls.getSuperClass();
if (sup != null
&& !sup.equals(ArgType.OBJECT)
&& !sup.getObject().equals(ArgType.ENUM.getObject())) {
&& !cls.isEnum()) {
clsCode.add("extends ");
useClass(clsCode, sup);
clsCode.add(' ');
@@ -174,23 +195,22 @@ public class ClassGen {
}
}
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap, boolean classDeclaration) {
if (gmap == null || gmap.isEmpty()) {
public boolean addGenericTypeParameters(ICodeWriter code, List<ArgType> generics, boolean classDeclaration) {
if (generics == null || generics.isEmpty()) {
return false;
}
code.add('<');
int i = 0;
for (Entry<ArgType, List<ArgType>> e : gmap.entrySet()) {
ArgType type = e.getKey();
List<ArgType> list = e.getValue();
for (ArgType genericInfo : generics) {
if (i != 0) {
code.add(", ");
}
if (type.isGenericType()) {
code.add(type.getObject());
if (genericInfo.isGenericType()) {
code.add(genericInfo.getObject());
} else {
useClass(code, type);
useClass(code, genericInfo);
}
List<ArgType> list = genericInfo.getExtendTypes();
if (list != null && !list.isEmpty()) {
code.add(" extends ");
for (Iterator<ArgType> it = list.iterator(); it.hasNext();) {
@@ -216,26 +236,50 @@ public class ClassGen {
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('{');
clsDeclLine = clsCode.getLine();
if (printClassName && cls.checkCommentsLevel(CommentsLevel.INFO)) {
clsCode.add(" // from class: " + cls.getClassInfo().getFullName());
}
setBodyGenStarted(true);
clsDeclOffset = clsCode.getLength();
clsCode.incIndent();
addFields(clsCode);
addInnerClasses(clsCode, cls);
addMethods(clsCode);
addInnerClsAndMethods(clsCode);
clsCode.decIndent();
clsCode.startLine('}');
}
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (innerCls.contains(AFlag.DONT_GENERATE)) {
continue;
}
private void addInnerClsAndMethods(ICodeWriter clsCode) {
Stream.of(cls.getInnerClasses(), cls.getMethods())
.flatMap(Collection::stream)
.filter(node -> !node.contains(AFlag.DONT_GENERATE) || fallback)
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
.forEach(node -> {
if (node instanceof ClassNode) {
addInnerClass(clsCode, (ClassNode) node);
} else {
addMethod(clsCode, (MethodNode) node);
}
});
}
private void addInnerClass(ICodeWriter code, ClassNode innerCls) {
try {
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
code.newLine();
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
} catch (Exception e) {
innerCls.addError("Inner class code generation error", e);
}
}
@@ -248,36 +292,47 @@ public class ClassGen {
return false;
}
private void addMethods(CodeWriter code) {
List<MethodNode> methods = sortMethodsByLine(cls.getMethods());
for (MethodNode mth : methods) {
if (mth.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (code.getLine() != clsDeclLine) {
code.newLine();
}
int savedIndent = code.getIndent();
try {
addMethod(code, mth);
} catch (Exception e) {
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw new JadxRuntimeException("Method generation error", e);
}
code.newLine().add("/*");
code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e));
Utils.appendStackTrace(code, e);
code.newLine().add("*/");
code.setIndent(savedIndent);
mth.addError("Method generation error: " + e.getMessage(), e);
private void addMethod(ICodeWriter code, MethodNode mth) {
if (skipMethod(mth)) {
return;
}
if (code.getLength() != clsDeclOffset) {
code.newLine();
}
int savedIndent = code.getIndent();
try {
addMethodCode(code, mth);
} catch (Exception e) {
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw new JadxRuntimeException("Method generation error", e);
}
mth.addError("Method generation error", e);
CodeGenUtils.addErrors(code, mth);
code.setIndent(savedIndent);
}
}
private static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
List<MethodNode> out = new ArrayList<>(methods);
out.sort(Comparator.comparingInt(LineAttrNode::getSourceLine));
return out;
/**
* 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() {
@@ -289,17 +344,15 @@ public class ClassGen {
return false;
}
public void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
CodeGenUtils.addComments(code, mth);
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
public void addMethodCode(ICodeWriter code, MethodNode mth) throws CodegenException {
CodeGenUtils.addErrorsAndComments(code, mth);
if (mth.isNoCode()) {
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
code.add(';');
} else {
insertDecompilationProblems(code, mth);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode && showInconsistentCode) {
mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false;
}
MethodGen mthGen;
@@ -319,61 +372,56 @@ public class ClassGen {
}
}
public void insertDecompilationProblems(CodeWriter code, AttrNode node) {
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
if (!errors.isEmpty()) {
errors.stream().distinct().sorted().forEach(err -> {
code.startLine("/* JADX ERROR: ").add(err.getError());
Throwable cause = err.getCause();
if (cause != null) {
code.incIndent();
Utils.appendStackTrace(code, cause);
code.decIndent();
}
code.add("*/");
});
}
List<String> warns = node.getAll(AType.JADX_WARN);
if (!warns.isEmpty()) {
warns.stream().distinct().sorted()
.forEach(warn -> code.startLine("/* JADX WARNING: ").addMultiLine(warn).add(" */"));
}
}
private void addFields(CodeWriter code) throws CodegenException {
private void addFields(ICodeWriter code) throws CodegenException {
addEnumFields(code);
for (FieldNode f : cls.getFields()) {
addField(code, f);
}
}
public void addField(CodeWriter code, FieldNode f) {
public void addField(ICodeWriter code, FieldNode f) {
if (f.contains(AFlag.DONT_GENERATE)) {
return;
}
if (Consts.DEBUG_USAGE) {
addFieldUsageInfo(code, f);
}
CodeGenUtils.addComments(code, f);
annotationGen.addForField(code, f);
if (f.getFieldInfo().isRenamed()) {
boolean addInfoComments = f.checkCommentsLevel(CommentsLevel.INFO);
if (f.getFieldInfo().isRenamed() && addInfoComments) {
code.newLine();
CodeGenUtils.addRenamedComment(code, f, f.getName());
}
code.startLine(f.getAccessFlags().makeString());
code.startLine(f.getAccessFlags().makeString(addInfoComments));
useType(code, f.getType());
code.add(' ');
code.attachDefinition(f);
code.add(f.getAlias());
FieldInitAttr fv = f.get(AType.FIELD_INIT);
if (fv != null) {
FieldInitInsnAttr initInsnAttr = f.get(AType.FIELD_INIT_INSN);
if (initInsnAttr != null) {
InsnGen insnGen = makeInsnGen(initInsnAttr.getInsnMth());
code.add(" = ");
if (fv.getValue() == null) {
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
} else {
if (fv.getValueType() == InitType.CONST) {
annotationGen.encodeValue(code, fv.getValue());
} else if (fv.getValueType() == InitType.INSN) {
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
addInsnBody(insnGen, code, fv.getInsn());
addInsnBody(insnGen, code, initInsnAttr.getInsn());
} else {
EncodedValue constVal = f.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null) {
code.add(" = ");
if (constVal.getType() == EncodedType.ENCODED_NULL) {
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
} else {
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);
}
}
}
}
@@ -389,7 +437,7 @@ public class ClassGen {
return false;
}
private void addEnumFields(CodeWriter code) throws CodegenException {
private void addEnumFields(ICodeWriter code) throws CodegenException {
EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS);
if (enumFields == null) {
return;
@@ -397,14 +445,17 @@ public class ClassGen {
InsnGen igen = null;
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext();) {
EnumField f = it.next();
CodeGenUtils.addComments(code, f.getField());
code.startLine(f.getField().getAlias());
ConstructorInsn constrInsn = f.getConstrInsn();
if (constrInsn.getArgsCount() > f.getStartArg()) {
MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth());
int skipCount = getEnumCtrSkipArgsCount(callMth);
if (constrInsn.getArgsCount() > skipCount) {
if (igen == null) {
igen = makeInsnGen(enumFields.getStaticMethod());
}
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth());
igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth);
igen.generateMethodArguments(code, constrInsn, 0, callMth);
}
if (f.getCls() != null) {
code.add(' ');
@@ -425,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) {
MethodGen mthGen = new MethodGen(this, mth);
return new InsnGen(mthGen, false);
}
private void addInsnBody(InsnGen insnGen, CodeWriter code, InsnNode insn) {
private void addInsnBody(InsnGen insnGen, ICodeWriter code, InsnNode insn) {
try {
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
} catch (Exception e) {
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();
if (stype == null) {
code.add(type.toString());
@@ -456,23 +517,35 @@ 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();
if (outerType != null) {
useClass(code, outerType);
code.add('.');
// import not needed, force use short name
useClassShortName(code, type.getObject());
return;
}
useClass(code, ClassInfo.fromType(cls.root(), type));
ArgType[] generics = type.getGenericTypes();
List<ArgType> generics = type.getGenericTypes();
if (generics != null) {
code.add('<');
int len = generics.length;
int len = generics.size();
for (int i = 0; i < len; i++) {
if (i != 0) {
code.add(", ");
}
ArgType gt = generics[i];
ArgType gt = generics.get(i);
ArgType wt = gt.getWildcardType();
if (wt != null) {
code.add('?');
int bounds = gt.getWildcardBounds();
if (bounds != 0) {
code.add(bounds == -1 ? " super " : " extends ");
ArgType.WildcardBound bound = gt.getWildcardBound();
code.add(bound.getStr());
if (bound != ArgType.WildcardBound.UNBOUND) {
useType(code, wt);
}
} else {
@@ -483,8 +556,17 @@ public class ClassGen {
}
}
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo);
private void useClassShortName(ICodeWriter code, String object) {
ClassInfo classInfo = ClassInfo.fromName(cls.root(), object);
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
code.add(classInfo.getAliasShortName());
}
public void useClass(ICodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
useClass(code, classNode);
} else {
@@ -492,12 +574,12 @@ public class ClassGen {
}
}
public void useClass(CodeWriter code, ClassNode classNode) {
public void useClass(ICodeWriter code, ClassNode classNode) {
code.attachAnnotation(classNode);
addClsName(code, classNode.getClassInfo());
}
private void addClsName(CodeWriter code, ClassInfo classInfo) {
public void addClsName(ICodeWriter code, ClassInfo classInfo) {
String clsName = useClassInternal(cls.getClassInfo(), classInfo);
code.add(clsName);
}
@@ -514,6 +596,9 @@ public class ClassGen {
if (isClassInnerFor(useCls, extClsInfo)) {
return shortName;
}
if (extClsInfo.isInner()) {
return expandInnerClassName(useCls, extClsInfo);
}
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
return shortName;
}
@@ -521,12 +606,7 @@ public class ClassGen {
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
return shortName;
}
// don't add import if class not public (must be accessed using inheritance)
ClassNode classNode = cls.dex().resolveClass(extClsInfo);
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
return shortName;
}
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
if (searchCollision(cls.root(), useCls, extClsInfo)) {
return fullName;
}
// ignore classes from default package
@@ -551,6 +631,26 @@ public class ClassGen {
return shortName;
}
private String expandInnerClassName(ClassInfo useCls, ClassInfo extClsInfo) {
List<ClassInfo> clsList = new ArrayList<>();
clsList.add(extClsInfo);
ClassInfo parentCls = extClsInfo.getParentClass();
boolean addImport = true;
while (parentCls != null) {
if (parentCls == useCls || isClassInnerFor(useCls, parentCls)) {
addImport = false;
break;
}
clsList.add(parentCls);
parentCls = parentCls.getParentClass();
}
Collections.reverse(clsList);
if (addImport) {
addImport(clsList.get(0));
}
return Utils.listToString(clsList, ".", ClassInfo::getAliasShortName);
}
private void addImport(ClassInfo classInfo) {
if (parentGen != null) {
parentGen.addImport(classInfo);
@@ -580,12 +680,12 @@ public class ClassGen {
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
if (inner.isInner()) {
ClassInfo p = inner.getParentClass();
return p.equals(parent) || isClassInnerFor(p, parent);
return Objects.equals(p, parent) || isClassInnerFor(p, parent);
}
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) {
return false;
}
@@ -593,7 +693,7 @@ public class ClassGen {
if (useCls.getAliasShortName().equals(shortName)) {
return true;
}
ClassNode classNode = dex.resolveClass(useCls);
ClassNode classNode = root.resolveClass(useCls);
if (classNode != null) {
for (ClassNode inner : classNode.getInnerClasses()) {
if (inner.getShortName().equals(shortName)
@@ -602,16 +702,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();
if (classInfo.hasAlias()) {
if (classInfo.hasAlias() && cls.checkCommentsLevel(CommentsLevel.INFO)) {
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() {
return parentGen == null ? this : parentGen;
}
@@ -623,4 +757,21 @@ public class ClassGen {
public boolean isFallbackMode() {
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;
}
}
@@ -2,7 +2,9 @@ package jadx.core.codegen;
import java.util.concurrent.Callable;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.api.impl.SimpleCodeInfo;
import jadx.core.codegen.json.JsonCodeGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
@@ -10,33 +12,32 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public class CodeGen {
public static void generate(ClassNode cls) {
public static ICodeInfo generate(ClassNode cls) {
if (cls.contains(AFlag.DONT_GENERATE)) {
cls.setCode(CodeWriter.EMPTY);
} else {
JadxArgs args = cls.root().getArgs();
switch (args.getOutputFormat()) {
case JAVA:
generateJavaCode(cls, args);
break;
return ICodeInfo.EMPTY;
}
JadxArgs args = cls.root().getArgs();
switch (args.getOutputFormat()) {
case JAVA:
return generateJavaCode(cls, args);
case JSON:
generateJson(cls);
break;
}
case JSON:
return generateJson(cls);
default:
throw new JadxRuntimeException("Unknown output format");
}
}
private static void generateJavaCode(ClassNode cls, JadxArgs args) {
private static ICodeInfo generateJavaCode(ClassNode cls, JadxArgs args) {
ClassGen clsGen = new ClassGen(cls, args);
CodeWriter code = wrapCodeGen(cls, clsGen::makeClass);
cls.setCode(code);
return wrapCodeGen(cls, clsGen::makeClass);
}
private static void generateJson(ClassNode cls) {
private static ICodeInfo generateJson(ClassNode cls) {
JsonCodeGen codeGen = new JsonCodeGen(cls);
String clsJson = wrapCodeGen(cls, codeGen::process);
cls.setCode(new CodeWriter(clsJson));
return new SimpleCodeInfo(clsJson);
}
private static <R> R wrapCodeGen(ClassNode cls, Callable<R> codeGenFunc) {
@@ -1,330 +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.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 {
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
public static final String NL = System.getProperty("line.separator");
public static final String INDENT_STR = " ";
public static final CodeWriter EMPTY = new CodeWriter().finish();
private static final boolean ADD_LINE_NUMBERS = false;
private static final String[] INDENT_CACHE = {
"",
INDENT_STR,
INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
};
private StringBuilder buf;
@Nullable
private String code;
private String indentStr;
private int indent;
private int line = 1;
private int offset = 0;
private Map<CodePosition, Object> annotations = Collections.emptyMap();
private Map<Integer, Integer> lineMap = Collections.emptyMap();
public CodeWriter() {
this.buf = new StringBuilder();
this.indent = 0;
this.indentStr = "";
if (ADD_LINE_NUMBERS) {
incIndent(2);
}
}
// 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);
}
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);
}
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();
}
public String getCodeStr() {
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);
}
}
}

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