Compare commits

...

329 Commits

Author SHA1 Message Date
Skylot fdf170529f fix: use strict patterns for synthetic methods inline (#1829) 2023-04-19 17:52:37 +01:00
Skylot 50283ab543 fix: additional checks to forbid inline of null consts (#1828) 2023-04-19 15:49:06 +01:00
Skylot 3fa3e5acec fix: correct args shift for instance invoke-custom (#1816) 2023-04-16 20:10:57 +01:00
Skylot 4230cd5b5a feat(plugins): allow to load classes using input stream or byte array in jadx-input plugin (#1457) 2023-04-10 21:28:53 +01:00
nitram84 1ad6527de5 fix(xml): use parent attibute only for styles (PR #1815) 2023-04-10 18:34:17 +01:00
nitram84 0421ad80c1 fix: filter invalid chars in app name for gradle export (PR #1813) 2023-04-08 18:16:14 +01:00
nitram84 35e0201f06 fix(gradle): fix gradle build with version 7.4.2 - 7.6 2023-04-08 19:49:15 +03:00
nitram84 118eea5e77 fix(res): set empty parent for styles without a parent, remove duplicated code 2023-04-08 19:49:15 +03:00
nitram84 7f317be325 fix(res): resolve declare-styleable atrributes 2023-04-08 19:49:15 +03:00
nitram84 e1aa9f6de4 fix(res): resolve custom attributes 2023-04-08 19:49:15 +03:00
nitram84 058a5e3bb2 fix(res): resolve int hex attributes 2023-04-08 19:49:15 +03:00
JustFor 92b49ec2b5 fix(gui): update Messages_zh_CN.properties (PR #1811)
sync new text, and Some symbols are translated
2023-04-07 19:20:15 +01:00
Skylot 583a04b092 fix(gui): show skipped resources count during search (#1808) 2023-03-24 22:00:07 +00:00
Skylot 444a04e2f7 fix(gui): redirect jump from search for inlined classes 2023-03-24 15:34:46 +00:00
Skylot 157e702ffd feat: inline lambdas by instance field (#1800) 2023-03-24 15:34:45 +00:00
Jan S 77892f41ec fix(res): parsing of sparse RES_TABLE_TYPE_TYPE and RES_TABLE_TYPE_STAGED_ALIAS chunks (#1806 #1803)(PR #1807) 2023-03-23 17:30:29 +00:00
Jan S 6ba0e1dbf6 fix(res): handle RES_TABLE_TYPE_OVERLAY (#1748) (PR #1804) 2023-03-18 14:23:49 +00:00
Skylot 950fbbaa83 fix: restore missing type parameter declarations (#1800) 2023-03-17 20:28:42 +00:00
Skylot 912c431511 fix(debugger): process UI updates in correct thread (#1796) 2023-03-17 14:23:36 +00:00
Skylot 5d6b82724a fix(gui): search constant fields usage in all classes (#1801) 2023-03-16 17:41:23 +00:00
Jan S 78c976ad4f fix(res): resolve manifest decoding error Expected strings start (#1797)(PR #1798) 2023-03-10 18:46:31 +03:00
Skylot fbdfd135da fix(cli): use common enum args parser (#1787) 2023-02-27 19:35:49 +00:00
Jacob Davis-Hansson dd51783d9e fix(cli): make enum CLI arguments match documented format (PR #1787)
Currently if you do `jadx --help`, it says the `--deobf-cfg-file-mode` option accepts the value `read-or-save`. 

However, if you give it that option, it instead prints the following error message:

```
java.lang.IllegalArgumentException: 'read-or-save' is unknown, possible values are: read, read-or-save, overwrite, ignore
	at jadx.cli.JadxCLIArgs$DeobfuscationMapFileModeConverter.convert(JadxCLIArgs.java:524)
	at jadx.cli.JadxCLIArgs$DeobfuscationMapFileModeConverter.convert(JadxCLIArgs.java:516)
	at com.beust.jcommander.JCommander.convertValue(JCommander.java:1340)
	at com.beust.jcommander.ParameterDescription.addValue(ParameterDescription.java:249)
	at com.beust.jcommander.JCommander.processFixedArity(JCommander.java:920)
	at com.beust.jcommander.JCommander.processFixedArity(JCommander.java:901)
	at com.beust.jcommander.JCommander.parseValues(JCommander.java:731)
	at com.beust.jcommander.JCommander.parse(JCommander.java:363)
	at com.beust.jcommander.JCommander.parse(JCommander.java:342)
	at jadx.cli.JCommanderWrapper.parse(JCommanderWrapper.java:37)
	at jadx.cli.JadxCLIArgs.processArgs(JadxCLIArgs.java:211)
	at jadx.cli.JadxCLI.execute(JadxCLI.java:35)
	at jadx.cli.JadxCLI.main(JadxCLI.java:20)
```

This commit changes all the enum parsers to do the inverse string of `enumValuesString`, so the documented behavior works.
2023-02-27 19:08:07 +00:00
Skylot 158fc2fca3 chore: update raung version 2023-02-18 15:46:08 +00:00
Skylot 24284a6f3a fix: process manifest before other resources (#1740) 2023-02-17 17:54:07 +00:00
Skylot 85c2c63aa3 fix: output unknown invoke-custom as polymorphic call (#1760) 2023-02-11 16:06:30 +00:00
Skylot f354f7de63 fix(gui): split tabs loading to prevent ui dead lock 2023-02-11 14:01:31 +00:00
Skylot 540c0a8100 feat: support polymorphic invoke (#384)(#1777) 2023-02-03 16:28:24 +00:00
Skylot 4d00fede56 fix: resolve JavaNode caching issues (#1775) 2023-02-02 19:39:42 +00:00
Skylot b1bc5c08ff chore: update dependencies 2023-02-02 15:23:54 +00:00
Ran Naor 305d4f4fe5 fix(gui): print the renamed function name in a frida snippet log (#1772)(PR #1773)
* frida snippet log now prints the correct method name if the method was renamed
* fixed spotless check in frida snippet
* get the renamed method name from the alias proprety and changed to format string to fit frida-trace style
* fixed import order to fix gradle spotless warning
2023-01-28 17:59:42 +00:00
Ran Naor 2d149e9a5d fix(gui): allow html in JVariable to render renaming of variables correctly (#1769)(PR #1770) 2023-01-27 17:03:43 +00:00
Ran Naor 87b9ff3c35 feat(gui): added keyboard shortcut ctrl+w to close tab (#1765)(PR #1766) 2023-01-21 17:22:36 +00:00
Zach Snell 1c36b3c74c fix(gui): quick fix for duplicate/overlapping logcat windows (#1752)(PR #1761)
* Simple fix to duplicate/overlapping logcat windows. Could be improved by not re-creating UI every time
* Apply suggestions from code review
* Another try to fix trailing spaces using GitHub suggestions

Co-authored-by: Zach Snell <zach.snell@bivalogic.com>
Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
2023-01-21 17:07:27 +00:00
Skylot 068e4b8e3d fix: allow altMetafactory method in lambda call site (#1760) 2023-01-15 16:08:34 +00:00
Skylot df38a6424f fix(gui): make bytecode output closer to smali (#1739) 2022-12-25 18:53:25 +00:00
Skylot 5d186e56a5 chore: update dependencies 2022-12-25 18:53:25 +00:00
Skylot 0fafcfa006 fix(gui): improve smali disasm method param write (#1739) 2022-12-13 17:40:00 +00:00
jmlitfi e3fdbafd86 fix(gui): resolve exception in smali method writer (#1739)(PR #1745)
Co-authored-by: jmlitfi <jeffmlitfi@gmail.com>
2022-12-13 17:39:20 +00:00
bagipro 07c2b14479 fix: escape special characters in AAB resources (PR #1747)
Co-authored-by: bagipro <bugi@macbook-pro-3.local>
2022-12-13 17:34:16 +00:00
Artem Zhiganov cdc844aaf3 feat(gui): add Russian Translation (PR #1744) 2022-12-10 13:11:34 +00:00
Skylot e1b7d361b9 fix: check full signature for search method override (#1743) 2022-12-09 17:13:01 +00:00
Skylot 12ef29bebc chore: update gradle and dependencies 2022-12-09 17:13:01 +00:00
Skylot 22ed241d50 fix(gui): correct html render in comments search results 2022-11-15 13:53:48 +00:00
Shatyuka 28e5a3c5be fix(gui): hi-dpi main window initial size (#1728)(PR #1729) 2022-11-14 18:19:09 +00:00
Skylot bb4d88cc68 fix(gui): add template for constructor and void methods to Frida snippet (#1714) 2022-11-07 19:13:11 +00:00
Mathis Hesse 4aaea2b93f fix(gui): change callMethodName of constructors in Frida action (#1714)(PR #1715)
* Change callMethodName of constructors in Frida action

* Fix format violation in FridaAction

* Fix format violation in FridaAction
2022-11-07 19:11:22 +00:00
Skylot bc8d7c4fc3 feat(gui): add native libs info to summary (#1717) 2022-11-03 19:15:33 +00:00
Skylot 5ea6c46778 fix(gui): show all code sources in summary (remove dex filter) (#1716) 2022-11-03 18:25:22 +00:00
Skylot b28f8ba85b fix(gui): try to handle exception in RSTA.getPreferredSize() (#1712) 2022-10-29 21:17:17 +01:00
Skylot 4db50fb749 fix(gui): correct html disabling in search results 2022-10-27 10:29:50 +01:00
Skylot 1dd0c90a04 build: switch java version to fix jdk install issue 2022-10-26 20:34:22 +01:00
Skylot 2bace2bde2 fix(gui): disable shell folders in file open dialog (#1709) 2022-10-26 20:08:57 +01:00
Skylot 1a9cb832ab feat(gui): add alternative file open dialog (#1709) 2022-10-26 19:58:58 +01:00
Skylot 6844a46c93 fix: disable HTML rendering in labels if not needed 2022-10-20 15:58:23 +01:00
Skylot e9e45707da chore: update dependencies 2022-10-20 14:54:31 +01:00
Skylot b9d02ff4c4 refactor: remove all LinkedList usage 2022-10-12 17:05:08 +01:00
Jan S 29b64300bc fix(gui): multi-threading issue in DebugController fixed (#1701) (PR #1702) 2022-10-11 19:21:06 +01:00
zhongqingsong 777355e86e fix(gui): update Messages_zh_CN.properties (PR #1700)
Add new text about logcat.
2022-10-10 18:57:58 +01:00
Skylot 620a177ce8 fix: restore enum class with custom code in static init (#1699) 2022-10-08 21:54:06 +01:00
Skylot 683c2dfbeb fix: improve ternary inline, resolve more enum cases (#1686) 2022-10-07 15:51:11 +01:00
Skylot 266cbcc6f4 fix(gui): migrate to fixed jdwp library fork (#1471) 2022-10-06 19:47:15 +01:00
Jan S 8a45602ae6 fix: improve logging messages for zip security errors (#750)(PR #1698)
Logging error messages on invalid file-names or path traversal attacks improved
2022-10-06 19:31:42 +01:00
Skylot 711419a797 fix: correct fix for all use places of incompatible primitives (#1688) 2022-10-03 00:11:04 +03:00
Skylot 603f3057eb chore: update dependencies 2022-10-03 00:11:01 +03:00
5idereal fa6fc1f871 fix(gui): update zh-TW translations (PR #1694) 2022-10-02 15:50:11 +01:00
Skylot 49fa320989 fix: handle possible concurrent exception in method codegen (#1685) 2022-09-29 20:28:01 +01:00
Skylot 2f301bf150 fix: don't mark constructor for inline if anonymous class inline is disabled (#1680) 2022-09-25 17:47:53 +01:00
Skylot b4892ce17f build: use fixed java version to build win artifacts 2022-09-23 21:08:16 +01:00
Skylot 151c171616 fix: handle empty block at end of else-if chain (#1674) 2022-09-23 20:40:56 +01:00
Skylot 79477a2de3 fix: don't rename bridged overridden methods (#1672) 2022-09-23 19:16:34 +01:00
dependabot[bot] 78aadda931 build(deps): bump actions/checkout from 2 to 3 (PR #1669)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 18:30:35 +01:00
Jan S b50706505f fix(res): implemented parsing RES_TABLE_TYPE_LIBRARY chunks (#1663)(PR #1664)
* core: Implemented parsing RES_TABLE_TYPE_LIBRARY chunks

* skip unknown data at the end of type chunk
2022-09-10 16:58:26 +01:00
The Cobra Chicken 9114821fb1 feat(debugger): add logcat output (#1411)(PR #1666)
* Adding logcatController class and writing adb / debugger panel information to the controller.

* Finished parsing logcat binary output and writing an arraylist containing all events.

* added highlighting of logcat output based on type.  Added timestamp parsing.

* Updated code to only get new log messages.

* Added additional code for select all

* Completed Check and uncheckall options.

* Changed log highlighting to log color.  Changed from JTextArea to JTextPane. Logcat pane will now autoscroll only if it is already scrolled to the bottom.  Debugger exit will now stop logcat as well.

* Moved labels into NLS rather than using hardcoded strings.

* Implemented the ability to autoselect attached process.  Changed the formatting of logcat messages.

* Moved labels into NLS rather than using hardcoded strings.

* updating to use info getter methods rather than directly accessing variable

* Added Logcat Pause Button

* Added Clear button

* Updated clear icon

* Cleaning warnings

* cleaning

* Changed behavior to only show logcat for debugged process to start with.

* cleaning

* cleaning

* cleaning

* applying spotless

* Fixing bug with switch

* fixed formatting issue

* add missing localization strings

Co-authored-by: green9317 <38409554+green9317@users.noreply.github.com>
Co-authored-by: TheCobraChicken <jeffmlitfi@gmail.com>
Co-authored-by: Skylot <skylot@gmail.com>
2022-09-08 15:18:55 +01:00
Skylot 1195582da8 feat(gui): option for search results count per page (#1652) 2022-09-05 20:07:02 +01:00
Skylot 258987b0ff chore: update dependencies 2022-09-05 20:07:01 +01:00
Guilherme a6a734c70d fix(gui): update pt-BR translation (PR #1655) 2022-09-01 15:51:12 +01:00
Choiman1559 d6c23a2a9b fix(gui): update Korean translation (PR #1650)
* update korean
* Update Messages_ko_KR.properties
* Restore missing empty line

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
2022-08-20 17:59:37 +01:00
Skylot db028904d7 fix(gui): set legacy sort flag also for launch4j (#1628) 2022-08-20 17:37:26 +01:00
Skylot 63a571306c refactor: load resource table nodes in one change (#1648) 2022-08-19 22:15:04 +01:00
Skylot bc4db61e25 fix(gui): improve resources search (#1648) 2022-08-19 15:52:14 +01:00
Jan S c7e6e28830 fix(gui): improve log viewer dialog (#1311)(PR #1649)
* [gui]: improve log viewer dialog

* use method from UiUtils to set window icons

Co-authored-by: Skylot <skylot@gmail.com>
2022-08-18 19:55:41 +01:00
Skylot 1d7b6fdb2c fix(gui): additional checks on open search result (#1647) 2022-08-18 15:59:45 +01:00
Skylot ce5d8eeff8 fix: don't inline anonymous in self inner class (#1645) 2022-08-18 15:48:17 +01:00
Jan S 894e0e6132 fix: UnsupportedOperationException on adding a field (#1645)(PR #1646)
* fix: UnsupportedOperationException on adding a field

* changed list check and creation similar to safeAdd
2022-08-18 15:33:18 +01:00
Skylot 127f0ecf3f fix(gui): disable actions if files not loaded (#1644) 2022-08-16 21:28:57 +01:00
Skylot cf7767e702 fix(gui): handle null value in TableCellRenderer (#1642) 2022-08-16 20:48:23 +01:00
Skylot e0aedc7949 fix: improve top block search for try/catch (#1633) 2022-08-15 21:31:26 +01:00
Skylot bad78de74c perf: improve directory delete 2022-08-14 13:38:12 +01:00
Skylot 12df8a169f chore: update gradle and dependencies 2022-08-13 18:25:08 +01:00
Skylot 15c9d33339 fix(gui): handle possible classes overlap in disk cache (#1633) 2022-08-13 13:13:13 +00:00
Jiaxin Peng 7e0fafbaf1 fix(gui): fix broken FileDialog by using legacy sort (#1628)(PR #1630)
#1628 #1606 #1213 #1574 #1552
2022-08-11 13:59:46 +01:00
zhongqingsong 57b9c1dd7a fix(gui): update Messages_zh_CN.properties (PR #1627) 2022-08-11 13:29:15 +01:00
Skylot 8ba0c17259 fix: handle empty endless loop (#1611) 2022-08-10 22:07:52 +01:00
Areizen cd32151083 fix(gui): correct Frida snippet for constructor (PR #1605)
When hooking a constructor with Frida, call `$new` instead of `$init`. `$init` cannot be used to instantiate an object and is reserved for hooking.

Co-authored-by: Your Name <you@example.com>
2022-08-06 20:16:37 +01:00
Skylot 75b52d672e feat(gui): save project search history 2022-08-05 20:43:05 +01:00
Skylot 11d04508f7 feat(gui): add manual search, stop and sort actions to search dialog (#1600) 2022-08-05 20:09:33 +01:00
Skylot e641b773b5 fix(gui): improve search dialog performance 2022-08-05 14:53:48 +01:00
Skylot 6e5899c654 fix: checks for field init reorder (#1599) 2022-08-04 17:38:46 +01:00
Skylot c66ffaa7f9 feat(gui): show start page on jadx open 2022-08-03 16:44:55 +01:00
Skylot 5193c6a5d8 chore: add tool for automatically insert new i18n lines 2022-08-03 16:44:40 +01:00
Skylot e7212af547 chore: upgrade gradle wrapper to 7.5 2022-08-03 18:44:10 +03:00
FixTheBug 3ca1357af4 fix(gui): sort resources by deobfuscated name (#1595)(PR #1598)
Co-authored-by: /paul-nguyen-goldenowl <paul.nguyen.goldenowl@gmail.com>
2022-08-01 14:54:22 +01:00
Guilherme 90e95213e4 feat(gui): add Brazilian Portuguese translation (PR #1596)
* feat(translation): add pt-br
* fix: `adb_dialog` prefix deleted
2022-08-01 12:15:35 +01:00
Jan S ae2d4da585 fix(res): XML "@null" decoding (#1583)(PR #1594)
minor improvements
2022-07-31 13:50:32 +01:00
Skylot 691d5cd1e6 fix: hide unused label before exception handler in simple mode 2022-07-30 17:33:23 +01:00
Skylot 58a46c6417 fix(gui): add constructors usage into class usage (#1591) 2022-07-30 17:22:32 +01:00
Skylot d3f6160e62 feat: add option to disable finally block extraction (#1592) 2022-07-30 14:07:43 +01:00
Skylot 03e4afb12f fix: check variables before merge in finally block (#1592) 2022-07-30 13:48:53 +01:00
Hen_Ry 6802f6028e fix(gui): update german translation (PR #1554)
* german update
* Fix
* applied the latest changes as discussed

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

* Comply with JADX's import order conventions

* Only use Java 8 features

* Only use Java 8 features (2)

* Export comments to mappings file

* Method args test (doesn't work)

* Make method arg mapping exports work now

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

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

* Remove unneeded method load call

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

* Fixes regarding inner classes

* Add option to export mappings as Enigma directory

* Add option to export mappings as Enigma file/directory

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

* Fix method vars' lv-indices

* Use correct offset value for method var mappings

* Also supply lvt-index for method var mappings

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

* Remove unnecessary `public` modifier

* Make an `if` condition less complicated

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

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

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

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

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

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

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

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

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

Co-authored-by: Skylot <skylot@gmail.com>
2022-04-24 10:32:25 +01:00
Skylot 1832f2aee3 feat: allow to load custom input (#1457) 2022-04-21 13:21:13 +01:00
SiderealArt 1ec127c3cb fix(gui): update Traditional Chinese translation (PR #1452) 2022-04-19 14:05:55 +01:00
Skylot 7a3b7c55c9 build: run tests in parallel 2022-04-18 16:31:25 +01:00
Skylot b66293a2f7 fix: handle wildcard in invoke type resolver (#1238) 2022-04-18 16:27:35 +01:00
Skylot abcaafa89a chore: update gradle and dependencies 2022-04-17 19:28:18 +01:00
Skylot cf25cc4faa fix: prevent null type in code variables 2022-04-17 19:18:33 +01:00
Skylot b57001d4a7 fix: use correct reference for replaced bridge constructor (#1441) 2022-04-10 15:48:36 +01:00
Skylot 83decc2473 fix(gui): rename class while rename constructor (#1441) 2022-04-08 13:45:27 +01:00
root-intruder 92faa569be build: allow unsigned local lib builds (#1438)(PR #1439) 2022-04-06 13:41:33 +01:00
Skylot c5b731169d build: disable jitpack 2022-04-06 13:34:08 +01:00
Skylot f0a8ef81d3 fix: replace fixed memory limit with -XX:MaxRAMPercentage=70.0 (#1437) 2022-04-05 20:08:39 +01:00
Skylot 994973ac01 fix(gui): check free memory after GC attempt 2022-04-05 19:46:10 +01:00
Skylot c9622c0771 chore: update class set to Android 32 2022-04-05 20:21:41 +03:00
Jan S 8551c6c903 fix(res): ignore resource chunk entries that are located after the resource chunk end (#751)(PR #1436) 2022-04-04 18:05:07 +01:00
Bruno Oberle 9a9ac4308e fix(cli): use correct converter for "--decompilation-mode" option (#1434)(PR #1435) 2022-04-04 13:47:30 +01:00
Matt e784cbdd09 fix(deobf): fix writing method mappings as fields entries (#1432)(PR #1433) 2022-04-02 12:30:25 +01:00
Elias 2744c4bfb6 build: fix appdata.xml (#1427)(PR #1430) 2022-03-31 17:30:06 +01:00
Skylot e4f4c1b84a fix(gui): don't highlight whitespaces and special symbols (#1429) 2022-03-28 19:21:05 +01:00
Elias e5fa818b5c build: remove unsupported tags from appdata.xml (#1427)(PR #1428)
* remove <code> tags from appdata.xml because flatpak doesn't like it
* appdata.xml: remove <a> tag because flatpak doesn't like it
2022-03-27 17:28:01 +01:00
Elias b22b554a69 build: add appdata.xml for flatpak package (PR #1426) 2022-03-27 14:06:18 +01:00
Skylot e9b8060889 refactor(gui): improve node action in code area 2022-03-26 15:31:29 +00:00
Skylot 1c2b2c072c fix(gui): restore open tabs on project load (regression fix) 2022-03-25 13:40:00 +00:00
Skylot 3d451912ee fix: handle inlined classes while collecting override related methods (#1422) 2022-03-25 12:56:18 +00:00
Skylot fe91d774fa feat(gui): add split view for different decompilation modes 2022-03-23 18:16:57 +03:00
Skylot d8306cb1c0 feat: add 'simple' decompilation mode 2022-03-23 18:16:54 +03:00
Jan S 909cf0a576 fix: various minor improvements (PR #1418)
* chore: better variable naming for getInstance calls
* chore: rebalance preferences window and fix empty plugins section directly after jadx-gui start
* chore: do not ask for project save if nothing had been changed
* use parallel mode for gradle
* minor improvements for app debugging
* apply CodeQL suggestion to prevent log injection
* handle IntelliJ Idea warnings
* replace not-ASCII chars in LogUtils.escape

Co-authored-by: Skylot <skylot@gmail.com>
2022-03-23 15:13:53 +00:00
Jan S 8fe1ee11e4 fix(debugger): resolve IO read problems, proper socket closing (PR #1414)
* fix(debugger): several IO read problems fixed
* merged latest changes
* fixed read loop
* Update jadx-gui/src/main/java/jadx/gui/device/protocol/ADB.java

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
2022-03-20 17:01:01 +00:00
Skylot d2bef108f5 chore: update dependencies 2022-03-19 18:52:44 +00:00
Skylot ba8ba504b1 fix(debugger): small improve for jdwp handshake (#1412) 2022-03-19 18:43:34 +00:00
Skylot 481b5abf85 fix(debugger): handle stream end and partial reads (#1412) 2022-03-18 14:19:08 +00:00
Skylot c4e1d9445a fix(gui): reduce threads count on low memory, other tweaks (#1410) 2022-03-17 17:50:28 +00:00
Skylot cb03532b76 fix: allow implicit type cast for array operations (#1407) 2022-03-14 18:47:55 +00:00
Skylot c93e9eea14 fix: improve class names collision detection (#1406) 2022-03-13 12:08:03 +00:00
Skylot 9a67b19973 feat(gui): add zoom in/out actions (#1403) 2022-03-11 13:59:00 +00:00
Skylot 95c75bed1e chore: update gradle and dependencies 2022-03-11 11:34:51 +00:00
Skylot b008568a5c doc: add missing options to readme 2022-03-05 17:15:08 +00:00
Skylot 94fb91cec6 feat: add options for java-convert plugin 2022-03-02 15:40:32 +00:00
Skylot c54dd77f35 fix(gui): resolve NPE and fix code style in BreakpointManager 2022-03-02 12:10:14 +00:00
Jan S 17fbc99f29 feat(gui): dialog for showing exception details and creating an GitHub issue (PR #1399)
* chore(gui): Dialog for showing exception details and creating an GitHub issue
* directly throw test exception
* checkstyle
* minor
* log exception before the dialog is shown
2022-03-01 15:00:22 +00:00
Skylot 21dd17290b fix(gui): download only latest version info for jadx update (#1397) 2022-02-28 18:51:13 +00:00
Skylot dc73fc92be fix(gui): don't use hardcoded color for link component (#1398) 2022-02-28 18:39:51 +00:00
Skylot 592215db66 fix(gui): handle package version in update check (#1397) 2022-02-28 18:39:51 +00:00
Skylot fb318e3bd9 fix(gui): revert contextual keywords to identifiers (#1394) 2022-02-27 15:22:41 +00:00
Skylot 5f3c8816a3 fix: allow zero skips for restore new filled array 2022-02-26 17:29:00 +00:00
Skylot 6016b902c7 test: fix usage of Eclipse compiler 2022-02-26 17:29:00 +00:00
Skylot 5852da1e3d feat: support MethodParameters attribute (#1260) 2022-02-26 10:28:21 +00:00
Skylot 502fd069be test: for source auto check use compiled classes instead runtime 2022-02-26 10:28:20 +00:00
Jan S fad9e7b827 fix(gui): initialize project name with loaded files (shown in Jadx title) (#1386)(PR #1393) 2022-02-26 09:20:58 +00:00
Skylot 35116d0b1a fix: load files also by extension (#1391) 2022-02-25 11:38:44 +00:00
Skylot 3b781e41ad test: allow to pass additional compiler options 2022-02-24 20:52:34 +00:00
Skylot a3e9744364 chore(cli): additional debug messages for java-convert plugin 2022-02-24 20:51:31 +00:00
Skylot 7030daeccd fix(cli): resolve regression in applying '-v' and '-q' options 2022-02-24 19:52:58 +00:00
Jan S e7151ad7b2 fix(gui): IllegalArgumentException when saving project to a different directory than the APK file (#1387)(PR #1388) 2022-02-23 09:27:04 +00:00
Skylot ed2a3c8458 fix: prevent NPE on 'ignore' deobf map file mode 2022-02-22 18:06:01 +00:00
Skylot 779f75cd52 fix(gui): prevent NPE on open preferences without loaded files (#1385) 2022-02-22 18:05:51 +00:00
Skylot 54683e3198 feat: plugin options, add verify checksum option for dex input (#1385) 2022-02-21 19:44:00 +00:00
Skylot 09335395f5 doc: update option description 2022-02-20 16:51:36 +00:00
Skylot 57e3dd8f15 feat(cli): improve single file mode (#1344)(#1384) 2022-02-20 15:04:59 +00:00
Skylot a9bbadd602 feat: add option for deobfuscation map file handle mode (#1351) 2022-02-19 21:20:11 +03:00
skylot 2c570681f7 doc: add link to jadx-gui key bindings in readme 2022-02-18 20:26:39 +00:00
Skylot 25166970cc feat(gui): ctrl+c copy node string in search window (#293) 2022-02-18 19:10:56 +00:00
Skylot d3a0a56b8b feat(gui): ctrl+c copy highlighted word in code view (#1292) 2022-02-18 19:10:34 +00:00
YenKoc 3c2c198a0e feat(gui): add Xposed snippet copy action (PR #1383)
* add xposedscript
* fix code style and minor issues
* some code style changes for Xposed snippets
* some code style changes for Frida snippets + a fix for multidimensional arrays in overload params
* hide frida and xposed when right-clicking on a null node
* small style fix
* fixed formatting violations
* fix minor issues

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

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

* Fixed getting method parameters from inlined methods

* fixed generating code for constructor overloads, more cleaning

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

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

* added support for generating a frida snippet for fields

* apply spotless

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

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

* moved the overload check from NodeMethod to FridaAction

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

* fix code formatting
2022-02-07 21:50:01 +00:00
cyqw 4bed9dc358 fix(gui): results in usage search should be sorted by name (PR #1363) 2022-02-07 15:39:57 +00:00
nitram84 e229874195 fix: check if targetSdkVersion is missing in gradle export (#1367)(PR #1370) 2022-02-07 10:39:09 +00:00
Skylot 473b6e31e9 fix: support multi-entry loops (simple case) (#1320) 2022-02-06 18:36:33 +00:00
Jan S b5ce460618 feat(deobf): do not deobfuscate known top level domains with 2 or 3 characters (PR #1369) 2022-02-06 12:56:59 +00:00
Skylot 3c05b05196 fix: check names from Kotlin metadata before use (#1364) 2022-02-05 21:49:36 +00:00
Skylot bdb2efdb6b fix(res): remove static caching map for xml renames (#1364) 2022-02-05 20:23:44 +00:00
Skylot a27ba3ff4b fix(res): skip '.9.png' decode if patch data not found (#1112) 2022-02-05 17:45:08 +00:00
Skylot 4684207b54 fix: remove duplicate classes from decompilation batches (#1361) 2022-02-05 17:45:07 +00:00
Skylot dd1be3039b fix(gui): split decompile and index tasks for correct time counting (#1361) 2022-02-05 17:45:07 +00:00
Skylot 8b30b770cd fix(gui): missing icons and html decorations in usage dialog 2022-02-05 13:36:26 +00:00
Yotam 47caa91e85 fix(cli): fix and add debug log messages in initialization phase (PR #1362)
* Fix log level settings in the CLI
* Add log messages in initialization phase
2022-02-02 19:04:19 +00:00
Skylot d71f3e09df fix: prevent endless loop in path cross search (#1360) 2022-02-01 14:32:44 +00:00
Jan S 06c7415827 fix(res): improved decoding of flag attributes in binary XML files (#1156)(PR #1359) 2022-01-31 18:00:50 +00:00
Skylot bd3e62617e fix: correct inline for enums in j$.time.temporal 2022-01-31 11:49:59 +00:00
Skylot 00b48473a0 test: add internal option to disable file save 2022-01-31 10:27:20 +00:00
Skylot 84facb13d0 fix: don't inline named variables (#1338) 2022-01-28 18:33:38 +00:00
Skylot 96f90e18e8 fix: improve exception handlers attach 2022-01-26 15:43:40 +00:00
Skylot 8ff18e63ee chore: update dependencies 2022-01-25 18:51:43 +00:00
Skylot 381405ea99 fix: always use deep resolve for fields and methods (#1357) 2022-01-25 11:37:36 +00:00
Ahmet Bilal Can ae5c00397a feat(gui): add frida action to copy methods/classes as frida snippets (#1355)(PR #1356)
* add frida action to copy methods/classes as frida snippets
* bug: call toString before comparing
2022-01-24 21:37:12 +00:00
Skylot bd4509f1a7 fix: update field usage on const replace (#1348) 2022-01-24 18:22:43 +00:00
Skylot b8c84886a8 fix: correct use of class names for inner types (#1340) 2022-01-24 14:11:40 +00:00
Skylot 45021389bc fix: correct method arg name if unused 2022-01-24 13:38:49 +00:00
Yotam f674a29a64 fix(deobf): rename classes as anonymous only if they are a number (PR #1354) 2022-01-23 21:16:05 +00:00
Yotam 0c9e3227d0 fix(deobf): collect missing renames for .jobf file (#1350)(PR #1353) 2022-01-23 16:08:54 +00:00
cyqw be7e1479a1 fix(gui): find usage for overridden methods (#1349)(PR #1352) 2022-01-23 16:06:13 +00:00
Skylot 19827fca20 fix: support full class name in inner generic types (#1340) 2022-01-22 18:49:31 +00:00
Skylot 5eb7cc40ed feat: check dex checksum before parsing (#1343) 2022-01-20 19:24:49 +00:00
Skylot d22db30166 fix: use secure xml parser for process manifest 2022-01-20 11:17:12 +00:00
Skylot 6db61e7a59 chore: update dependencies 2022-01-20 10:23:49 +00:00
Skylot 86582de521 feat: use kotlin intrinsic methods for variables rename (#1207) 2022-01-19 17:30:04 +00:00
Skylot a7c63c2eb3 fix: handle method override with several bases (#1234) 2022-01-18 18:27:09 +00:00
Skylot 081a0e21ee fix: precalculate class deps for inline methods (#1339) 2022-01-17 14:38:38 +00:00
Skylot 9ac9c05265 fix: simplify cascading casts (#1336) 2022-01-15 16:31:18 +00:00
Skylot b7daf79b26 fix: add explicit type for non-int constants (#1336) 2022-01-15 14:11:44 +00:00
Skylot b67a3561a4 build: add CodeQL analysis 2022-01-13 22:37:36 +03:00
Skylot 52ac6dbbaf docs: add security.md 2022-01-13 16:45:32 +00:00
Skylot 72381ad8f3 fix: correct literal negate for double and float (#1334) 2022-01-13 14:00:53 +00:00
Skylot 6a065c46f4 chore: update dependencies 2022-01-13 12:12:15 +00:00
Skylot 092d0d7e67 fix(gui): reduce tree focus switching 2022-01-12 19:57:38 +03:00
Skylot 5ca7285558 fix(gui): correct handling for tree row click (#1324) 2022-01-12 16:57:25 +00:00
Skylot 7576f9cd5e fix: wrap negative literals before cast (#1327) 2022-01-12 17:31:40 +03:00
Skylot 46b5725d98 refactor(test): replace inputs with test profiles 2022-01-12 17:31:37 +03:00
Jan S 72542fa6f9 fix(gui): processing threads spinner initialization (#1331)(PR #1332)
* fix: processing threads spinner initialization (#1331)
* fix: processing threads spinner initialization (#1331)
2022-01-12 14:23:07 +00:00
demonlol a250d0461b fix(dbg): support multiple main <action> and <activity-alias> tags (#1322)(PR #1323)
* fix(dgb): support multiple main <action> and <activity-alias> tags in manifest
* Update jadx-gui/src/main/java/jadx/gui/device/debugger/DbgUtils.java
2022-01-02 20:09:24 +03:00
Skylot c7795bfc48 fix: improve anonymous class inline (#523) 2021-12-26 13:06:49 +00:00
Skylot 5de46b7e40 chore: update gradle and dependencies 2021-12-24 12:53:30 +00:00
Skylot 99c70872c1 fix: use debug line numbers only at fixed offsets (#1315) 2021-12-22 22:55:14 +03:00
Skylot 3566669303 chore: update lgtm config 2021-12-22 12:24:01 +00:00
Skylot 4557d05256 fix: use correct type for anonymous class instance (#597) 2021-12-21 17:47:52 +00:00
Skylot fa421d165e build: disable missing warnings from javadoc 2021-12-21 12:52:52 +00:00
Skylot ecf20020d7 chore: cache current working dir in static field, other minor changes 2021-12-20 19:25:07 +00:00
Skylot ae85af61c7 fix: skip input file name checks by zip name validator (#1310) 2021-12-20 18:55:28 +00:00
Skylot 659bbbf4fb fix: correct usage of Path.getParent() 2021-12-20 16:48:50 +00:00
Jan S 427e2dddc4 fix: use relative file paths in .jadx project file (#1312) (PR #1313)
* chore: use relative file paths in .jadx project file (#1312)
* code beautified
* requested changes
2021-12-20 13:52:51 +00:00
skylot d47483f957 docs: use jadx as a library 2021-12-19 20:36:58 +00:00
638 changed files with 27606 additions and 8241 deletions
+7
View File
@@ -0,0 +1,7 @@
version: 2
updates:
# Set update schedule for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
+88
View File
@@ -0,0 +1,88 @@
name: Build Artifacts
on:
push:
branches: [ master, build-test ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: 8
- name: Set jadx version
run: |
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- uses: burrunan/gradle-cache-action@v1
name: Build with Gradle
env:
TERM: dumb
with:
arguments: clean dist copyExe
- name: Save bundle artifact
uses: actions/upload-artifact@v3
with:
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
# Upload unpacked files for now
path: build/jadx/**/*
if-no-files-found: error
retention-days: 30
- name: Save exe artifact
uses: actions/upload-artifact@v3
with:
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
path: build/*.exe
if-no-files-found: error
retention-days: 30
build-win-bundle:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK
uses: oracle-actions/setup-java@v1
with:
release: 17
- name: Print Java version
shell: bash
run: java -version
- name: Set jadx version
shell: bash
run: |
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- uses: gradle/gradle-build-action@v2
name: Build with Gradle
env:
TERM: dumb
with:
arguments: clean dist -PbundleJRE=true
- name: Save exe bundle artifact
uses: actions/upload-artifact@v3
with:
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
path: jadx-gui/build/*-with-jre-win/*
if-no-files-found: error
retention-days: 30
+28
View File
@@ -0,0 +1,28 @@
name: Build Test
on:
push:
branches: [ master, build-test ]
pull_request:
branches: [ master ]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: 8
- uses: burrunan/gradle-cache-action@v1
name: Build with Gradle
env:
TERM: dumb
with:
arguments: clean build dist copyExe --warning-mode=all
-52
View File
@@ -1,52 +0,0 @@
name: Build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 8
- name: Set jadx version
run: |
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- uses: burrunan/gradle-cache-action@v1
name: Build with Gradle
env:
TERM: dumb
TEST_INPUT_PLUGIN: dx
with:
arguments: clean build dist copyExe --warning-mode=all
- name: Save bundle artifact
if: success() && github.event_name == 'push'
uses: actions/upload-artifact@v2
with:
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
# Upload unpacked files for now
path: build/jadx/**/*
if-no-files-found: error
- name: Save exe artifact
if: success() && github.event_name == 'push'
uses: actions/upload-artifact@v2
with:
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
path: build/*.exe
if-no-files-found: error
+41
View File
@@ -0,0 +1,41 @@
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 9 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ['java']
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
queries: +security-extended
languages: ${{ matrix.language }}
# Don't build tests in jadx-core also skip tests execution and checkstyle tasks
- run: |
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
@@ -6,5 +6,5 @@ jobs:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
+3
View File
@@ -34,3 +34,6 @@ jadx-output/
*.cfg
*.orig
quark.json
cliff.toml
jadx-gui/src/main/resources/logback.xml
+5
View File
@@ -0,0 +1,5 @@
jdk:
- openjdk11
install:
- echo "Jitpack is not supported. Use artifacts from Maven Central (https://search.maven.org/search?q=jadx), check usage help at https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library"
- ./gradlew intentional-fail
+53 -15
View File
@@ -3,15 +3,18 @@
## JADX
[![Build status](https://github.com/skylot/jadx/workflows/Build/badge.svg)](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
[![Alerts from lgtm.com](https://img.shields.io/lgtm/alerts/g/skylot/jadx.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/skylot/jadx/alerts/)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
![GitHub contributors](https://img.shields.io/github/contributors/skylot/jadx)
![GitHub all releases](https://img.shields.io/github/downloads/skylot/jadx/total)
![GitHub release (latest by SemVer)](https://img.shields.io/github/downloads/skylot/jadx/latest/total)
![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)
[![Maven Central](https://img.shields.io/maven-central/v/io.github.skylot/jadx-core)](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
**jadx** - Dex to Java decompiler
Command line and GUI tools for producing Java source code from Android Dex and Apk files
:exclamation: :exclamation: :exclamation: Please note that in most cases Jadx can't decompile all 100% of the code, so errors will occur. Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds
: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
@@ -23,15 +26,18 @@ Command line and GUI tools for producing Java source code from Android Dex and A
- jump to declaration
- find usage
- full text search
- smali debugger (thanks to [@LBJ-the-GOAT](https://github.com/LBJ-the-GOAT)), check [wiki page](https://github.com/skylot/jadx/wiki/Smali-debugger) for setup and usage
- smali debugger, check [wiki page](https://github.com/skylot/jadx/wiki/Smali-debugger) for setup and usage
Jadx-gui key bindings can be found [here](https://github.com/skylot/jadx/wiki/JADX-GUI-Key-bindings)
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
### Download
- release from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
- latest [unstable build](https://nightly.link/skylot/jadx/workflows/build/master)
- release
from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
- latest [unstable build ![GitHub commits since tagged version (branch)](https://img.shields.io/github/commits-since/skylot/jadx/latest/master)](https://nightly.link/skylot/jadx/workflows/build-artifacts/master)
After download unpack zip file go to `bin` directory and run:
- `jadx` - command line version
@@ -39,17 +45,24 @@ After download unpack zip file go to `bin` directory and run:
On Windows run `.bat` files with double-click\
**Note:** ensure you have installed Java 11 or later 64-bit version.
For windows you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
### Install
1. Arch linux
1. Arch linux ![Arch Linux package](https://img.shields.io/archlinux/v/community/any/jadx?label=)
```bash
sudo pacman -S jadx
sudo pacman -S jadx
```
2. macOS
2. macOS ![homebrew version](https://img.shields.io/homebrew/v/jadx?label=)
```bash
brew install jadx
brew install jadx
```
3. [Flathub ![Flathub](https://img.shields.io/flathub/v/com.github.skylot.jadx?label=)](https://flathub.org/apps/details/com.github.skylot.jadx)
```bash
flatpak install flathub com.github.skylot.jadx
```
### Use jadx as a library
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
### Build from source
JDK 8 or higher must be installed:
@@ -73,16 +86,23 @@ options:
-dr, --output-dir-res - output directory for resources
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--single-class - decompile a single class
--single-class - decompile a single class, full name, raw or alias
--single-class-output - file or dir for write if decompile a single class
--output-format - can be 'java' or 'json', default: java
-e, --export-gradle - save as android gradle project
-j, --threads-count - processing threads count, default: 4
-m, --decompilation-mode - code output mode:
'auto' - trying best options (default)
'restructure' - restore code structure (normal java code)
'simple' - simplified instructions (linear, with goto's)
'fallback' - raw instructions without modifications
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info
--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-finally - don't extract finally block
--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
@@ -90,9 +110,18 @@ options:
--deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64
--deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension
--deobf-rewrite-cfg - force to ignore and overwrite deobfuscation map file
--deobf-cfg-file-mode - set mode for handle deobfuscation map file:
'read' - read if found, don't save (default)
'read-or-save' - read if found, save otherwise (don't overwrite)
'overwrite' - don't read, always save
'ignore' - don't read and don't save
--deobf-use-sourcename - use source file name as class name alias
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
--deobf-res-name-source - better name source for resources:
'auto' - automatically select best name (default)
'resources' - use resources names
'code' - use R class fields 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,
@@ -102,19 +131,28 @@ options:
--fs-case-sensitive - treat filesystem as case sensitive, false by default
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
--use-dx - use dx/d8 to convert java bytecode
--comments-level - set code comments level, values: none, user_only, error, warn, info, debug, default: info
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
--version - print jadx version
-h, --help - print this help
Plugin options (-P<name>=<value>):
1) dex-input: Load .dex and .apk files
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
2) java-convert: Convert .class, .jar and .aar files to dex
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
Examples:
jadx -d out classes.dex
jadx --rename-flags "none" classes.dex
jadx --rename-flags "valid, printable" classes.dex
jadx --log-level ERROR app.apk
jadx -Pdex-input.verify-checksum=no app.apk
```
These options also worked on jadx-gui running from command line and override options from preferences dialog
+7
View File
@@ -0,0 +1,7 @@
# Security Policy
## Reporting a Vulnerability
To report a security issue, please email `skylot@gmail.com` with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
We will check and respond within 3 working days. If the issue is confirmed as a vulnerability, we will apply required mitigations at the next release.
This project follows a 90 day disclosure timeline.
+17 -18
View File
@@ -1,6 +1,6 @@
plugins {
id 'com.github.ben-manes.versions' version '0.39.0'
id 'com.diffplug.spotless' version '6.0.2'
id 'com.github.ben-manes.versions' version '0.45.0'
id 'com.diffplug.spotless' version '6.13.0'
}
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -14,7 +14,6 @@ allprojects {
version = jadxVersion
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
compileJava {
options.encoding = "UTF-8"
@@ -27,29 +26,34 @@ allprojects {
}
dependencies {
implementation 'org.slf4j:slf4j-api:1.7.32'
compileOnly 'org.jetbrains:annotations:23.0.0'
implementation 'org.slf4j:slf4j-api:2.0.6'
compileOnly 'org.jetbrains:annotations:24.0.0'
testImplementation 'ch.qos.logback:logback-classic:1.2.8'
testImplementation 'ch.qos.logback:logback-classic:1.3.5'
testImplementation 'org.hamcrest:hamcrest-library:2.2'
testImplementation 'org.mockito:mockito-core:4.1.0'
testImplementation 'org.assertj:assertj-core:3.21.0'
testImplementation 'org.mockito:mockito-core:4.10.0'
testImplementation 'org.assertj:assertj-core:3.24.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
testCompileOnly 'org.jetbrains:annotations:23.0.0'
testCompileOnly 'org.jetbrains:annotations:24.0.0'
}
test {
useJUnitPlatform()
maxParallelForks = Runtime.runtime.availableProcessors()
}
repositories {
mavenLocal()
mavenCentral()
google()
// Commented out for now since we're using a local mapping-io fork atm.
// maven {
// name 'FabricMC'
// url 'https://maven.fabricmc.net/'
// }
}
}
@@ -64,12 +68,7 @@ spotless {
importOrderFile 'config/code-formatter/eclipse.importorder'
eclipse().configFile 'config/code-formatter/eclipse.xml'
if (JavaVersion.current() < JavaVersion.VERSION_16) {
removeUnusedImports()
} else {
// google-format broken on java 16 (https://github.com/diffplug/spotless/issues/834)
println('Warning! Unused imports remove is disabled for Java 16')
}
removeUnusedImports()
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
encoding("UTF-8")
@@ -66,6 +66,7 @@ publishing {
}
signing {
required { gradle.taskGraph.hasTask("publish") }
sign publishing.publications.mavenJava
}
@@ -73,4 +74,6 @@ javadoc {
if (JavaVersion.current().isJava9Compatible()) {
options.addBooleanOption('html5', true)
}
// disable 'missing' warnings
options.addStringOption('Xdoclint:all,-missing', '-quiet')
}
+10
View File
@@ -1 +1,11 @@
org.gradle.warning.mode=all
org.gradle.parallel=true
# Flags for google-java-format (optimize imports by spotless) for Java >= 16.
# Java < 9 will ignore unsupported flags (thanks to -XX:+IgnoreUnrecognizedVMOptions)
org.gradle.jvmargs=-XX:+IgnoreUnrecognizedVMOptions \
--add-exports='jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' \
--add-exports='jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED' \
--add-exports='jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED' \
--add-exports='jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED' \
--add-exports='jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
Binary file not shown.
+2 -2
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=9afb3ca688fc12c761a0e9e4321e4d24e977a4a8916c8a768b1fe05ddb4d6b66
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip
distributionSha256Sum=7ba68c54029790ab444b39d7e293d3236b2632631fb5f2e012bb28b4ff669e4b
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+6
View File
@@ -205,6 +205,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
echo "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
Vendored
+8 -6
View File
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
+3 -3
View File
@@ -10,14 +10,14 @@ dependencies {
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
implementation 'com.beust:jcommander:1.81'
implementation 'ch.qos.logback:logback-classic:1.2.8'
implementation 'com.beust:jcommander:1.82'
implementation 'ch.qos.logback:logback-classic:1.3.5'
}
application {
applicationName = 'jadx'
mainClass.set('jadx.cli.JadxCLI')
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
applicationDefaultJvmArgs = ['-Xms128M', '-XX:MaxRAMPercentage=70.0', '-XX:+UseG1GC']
}
applicationDistribution.with {
@@ -8,6 +8,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
@@ -17,6 +18,12 @@ import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameterized;
import jadx.api.JadxDecompiler;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.options.OptionDescription;
import jadx.core.utils.Utils;
public class JCommanderWrapper<T> {
private final JCommander jc;
@@ -45,12 +52,24 @@ public class JCommanderWrapper<T> {
if (parameter.isAssigned()) {
// copy assigned field value to obj
Parameterized parameterized = parameter.getParameterized();
Object val = parameterized.get(parameter.getObject());
parameterized.set(obj, val);
Object providedValue = parameterized.get(parameter.getObject());
Object newValue = mergeValues(parameterized.getType(), providedValue, () -> parameterized.get(obj));
parameterized.set(obj, newValue);
}
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static Object mergeValues(Class<?> type, Object value, Supplier<Object> prevValueProvider) {
if (type.isAssignableFrom(Map.class)) {
// merge maps instead replacing whole map
Map prevMap = (Map) prevValueProvider.get();
return Utils.mergeMaps(prevMap, (Map) value); // value map will override keys in prevMap
}
// simple override
return value;
}
public void printUsage() {
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
@@ -70,40 +89,44 @@ public class JCommanderWrapper<T> {
maxNamesLen = len;
}
}
maxNamesLen += 3;
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
for (Field f : getFields(args.getClass())) {
String name = f.getName();
ParameterDescription p = paramsMap.get(name);
if (p == null) {
if (p == null || p.getParameter().hidden()) {
continue;
}
StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames());
String description = p.getDescription();
addSpaces(opt, maxNamesLen - opt.length() + 3);
addSpaces(opt, maxNamesLen - opt.length());
if (description.contains("\n")) {
String[] lines = description.split("\n");
opt.append("- ").append(lines[0]);
for (int i = 1; i < lines.length; i++) {
opt.append('\n');
addSpaces(opt, maxNamesLen + 5);
addSpaces(opt, maxNamesLen + 2);
opt.append(lines[i]);
}
} else {
opt.append("- ").append(description);
}
String defaultValue = getDefaultValue(args, f, opt);
if (defaultValue != null) {
if (defaultValue != null && !description.contains("(default)")) {
opt.append(", default: ").append(defaultValue);
}
out.println(opt);
}
out.println(appendPluginOptions(maxNamesLen));
out.println();
out.println("Examples:");
out.println(" jadx -d out classes.dex");
out.println(" jadx --rename-flags \"none\" classes.dex");
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
out.println(" jadx --log-level ERROR app.apk");
out.println(" jadx -Pdex-input.verify-checksum=no app.apk");
}
/**
@@ -145,4 +168,46 @@ public class JCommanderWrapper<T> {
str.append(' ');
}
}
private String appendPluginOptions(int maxNamesLen) {
StringBuilder sb = new StringBuilder();
JadxPluginManager pluginManager = new JadxPluginManager();
pluginManager.load();
int k = 1;
for (JadxPlugin plugin : pluginManager.getAllPlugins()) {
if (plugin instanceof JadxPluginOptions) {
if (appendPlugin(((JadxPluginOptions) plugin), sb, maxNamesLen, k)) {
k++;
}
}
}
if (sb.length() == 0) {
return "";
}
return "\nPlugin options (-P<name>=<value>):" + sb;
}
private boolean appendPlugin(JadxPluginOptions plugin, StringBuilder out, int maxNamesLen, int k) {
List<OptionDescription> descs = plugin.getOptionsDescriptions();
if (descs.isEmpty()) {
return false;
}
JadxPluginInfo pluginInfo = plugin.getPluginInfo();
out.append("\n ").append(k).append(") ");
out.append(pluginInfo.getPluginId()).append(": ").append(pluginInfo.getDescription());
for (OptionDescription desc : descs) {
StringBuilder opt = new StringBuilder();
opt.append(" - ").append(desc.name());
addSpaces(opt, maxNamesLen - opt.length());
opt.append("- ").append(desc.description());
if (!desc.values().isEmpty()) {
opt.append(", values: ").append(desc.values());
}
if (desc.defaultValue() != null) {
opt.append(", default: ").append(desc.defaultValue());
}
out.append("\n").append(opt);
}
return true;
}
}
+46 -11
View File
@@ -7,6 +7,7 @@ import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.impl.NoOpCodeCache;
import jadx.api.impl.SimpleCodeWriter;
import jadx.cli.LogHelper.LogLevelEnum;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.files.FileUtils;
@@ -20,8 +21,8 @@ public class JadxCLI {
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
result = 1;
} catch (Exception e) {
LOG.error("jadx error: {}", e.getMessage(), e);
} catch (Throwable e) {
LOG.error("Process error:", e);
result = 1;
} finally {
FileUtils.deleteTempRootDir();
@@ -32,23 +33,25 @@ public class JadxCLI {
public static int execute(String[] args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) {
return processAndSave(jadxArgs.toJadxArgs());
return processAndSave(jadxArgs);
}
return 0;
}
private static int processAndSave(JadxArgs jadxArgs) {
private static int processAndSave(JadxCLIArgs cliArgs) {
LogHelper.initLogLevel(cliArgs);
LogHelper.setLogLevelsForLoadingStage();
JadxArgs jadxArgs = cliArgs.toJadxArgs();
jadxArgs.setCodeCache(new NoOpCodeCache());
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load();
if (LogHelper.getLogLevel() == LogHelper.LogLevelEnum.QUIET) {
jadx.save();
} else {
jadx.save(500, (done, total) -> {
int progress = (int) (done * 100.0 / total);
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
});
if (checkForErrors(jadx)) {
return 1;
}
LogHelper.setLogLevelsForDecompileStage();
if (!SingleClassMode.process(jadx, cliArgs)) {
save(jadx);
}
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
@@ -60,4 +63,36 @@ public class JadxCLI {
}
return 0;
}
private static boolean checkForErrors(JadxDecompiler jadx) {
if (jadx.getRoot().getClasses().isEmpty()) {
if (jadx.getArgs().isSkipResources()) {
LOG.error("Load failed! No classes for decompile!");
return true;
}
if (!jadx.getArgs().isSkipSources()) {
LOG.warn("No classes to decompile; decoding resources only");
jadx.getArgs().setSkipSources(true);
}
}
if (jadx.getErrorsCount() > 0) {
LOG.error("Load with errors! Check log for details");
// continue processing
return false;
}
return false;
}
private static void save(JadxDecompiler jadx) {
if (LogHelper.getLogLevel() == LogLevelEnum.QUIET) {
jadx.save();
} else {
jadx.save(500, (done, total) -> {
int progress = (int) (done * 100.0 / total);
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
});
// dumb line clear :)
System.out.print(" \r");
}
}
}
+166 -20
View File
@@ -2,19 +2,28 @@ package jadx.cli;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.beust.jcommander.DynamicParameter;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
import jadx.api.CommentsLevel;
import jadx.api.DecompilationMode;
import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.args.ResourceNameSource;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils;
@@ -38,9 +47,12 @@ public class JadxCLIArgs {
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
protected boolean skipSources = false;
@Parameter(names = { "--single-class" }, description = "decompile a single class")
@Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
protected String singleClass = null;
@Parameter(names = { "--single-class-output" }, description = "file or dir for write if decompile a single class")
protected String singleClassOutput = null;
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
protected String outputFormat = "java";
@@ -50,6 +62,17 @@ public class JadxCLIArgs {
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
@Parameter(
names = { "-m", "--decompilation-mode" },
description = "code output mode:"
+ "\n 'auto' - trying best options (default)"
+ "\n 'restructure' - restore code structure (normal java code)"
+ "\n 'simple' - simplified instructions (linear, with goto's)"
+ "\n 'fallback' - raw instructions without modifications",
converter = DecompilationModeConverter.class
)
protected DecompilationMode decompilationMode = DecompilationMode.AUTO;
@Parameter(names = { "--show-bad-code" }, description = "show inconsistent code (incorrectly decompiled)")
protected boolean showInconsistentCode = false;
@@ -68,6 +91,12 @@ public class JadxCLIArgs {
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
protected boolean inlineMethods = true;
@Parameter(names = { "--no-inline-kotlin-lambda" }, description = "disable inline for Kotlin lambdas")
protected boolean allowInlineKotlinLambda = true;
@Parameter(names = "--no-finally", description = "don't extract finally block")
protected boolean extractFinally = true;
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
protected boolean replaceConsts = true;
@@ -92,8 +121,16 @@ public class JadxCLIArgs {
)
protected String deobfuscationMapFile;
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "force to ignore and overwrite deobfuscation map file")
protected boolean deobfuscationForceSave = false;
@Parameter(
names = { "--deobf-cfg-file-mode" },
description = "set mode for handle deobfuscation map file:"
+ "\n 'read' - read if found, don't save (default)"
+ "\n 'read-or-save' - read if found, save otherwise (don't overwrite)"
+ "\n 'overwrite' - don't read, always save"
+ "\n 'ignore' - don't read and don't save",
converter = DeobfuscationMapFileModeConverter.class
)
protected DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = false;
@@ -101,6 +138,23 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
protected boolean deobfuscationParseKotlinMetadata = false;
@Parameter(
names = { "--deobf-res-name-source" },
description = "better name source for resources:"
+ "\n 'auto' - automatically select best name (default)"
+ "\n 'resources' - use resources names"
+ "\n 'code' - use R class fields names",
converter = ResourceNameSourceConverter.class
)
protected ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
@Parameter(
names = { "--use-kotlin-methods-for-var-names" },
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
converter = UseKotlinMethodsForVarNamesConverter.class
)
protected UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
@Parameter(
names = { "--rename-flags" },
description = "fix options (comma-separated list of):"
@@ -122,7 +176,7 @@ public class JadxCLIArgs {
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
protected boolean rawCfgOutput = false;
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
@Parameter(names = { "-f", "--fallback" }, description = "set '--decompilation-mode' to 'fallback' (deprecated)")
protected boolean fallbackMode = false;
@Parameter(names = { "--use-dx" }, description = "use dx/d8 to convert java bytecode")
@@ -130,7 +184,7 @@ public class JadxCLIArgs {
@Parameter(
names = { "--comments-level" },
description = "set code comments level, values: error, warn, info, debug, user_only, none",
description = "set code comments level, values: error, warn, info, debug, user-only, none",
converter = CommentsLevelConverter.class
)
protected CommentsLevel commentsLevel = CommentsLevel.INFO;
@@ -138,7 +192,7 @@ public class JadxCLIArgs {
@Parameter(
names = { "--log-level" },
description = "set log level, values: quiet, progress, error, warn, info, debug",
converter = LogHelper.LogLevelConverter.class
converter = LogLevelConverter.class
)
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
@@ -154,6 +208,9 @@ public class JadxCLIArgs {
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
protected boolean printHelp = false;
@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
protected Map<String, String> pluginOptions = new HashMap<>();
public boolean processArgs(String[] args) {
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
return jcw.parse(args) && process(jcw);
@@ -189,7 +246,6 @@ public class JadxCLIArgs {
if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive, got: " + threadsCount);
}
LogHelper.setLogLevelFromArgs(this);
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
jcw.printUsage();
@@ -207,22 +263,25 @@ public class JadxCLIArgs {
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
args.setThreadsCount(threadsCount);
args.setSkipSources(skipSources);
if (singleClass != null) {
args.setClassFilter(className -> singleClass.equals(className));
}
args.setSkipResources(skipResources);
args.setFallbackMode(fallbackMode);
if (fallbackMode) {
args.setDecompilationMode(DecompilationMode.FALLBACK);
} else {
args.setDecompilationMode(decompilationMode);
}
args.setShowInconsistentCode(showInconsistentCode);
args.setCfgOutput(cfgOutput);
args.setRawCFGOutput(rawCfgOutput);
args.setReplaceConsts(replaceConsts);
args.setDeobfuscationOn(deobfuscationOn);
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
args.setDeobfuscationForceSave(deobfuscationForceSave);
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
args.setResourceNameSource(resourceNameSource);
args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject);
@@ -231,10 +290,13 @@ public class JadxCLIArgs {
args.setInsertDebugLines(addDebugLines);
args.setInlineAnonymousClasses(inlineAnonymousClasses);
args.setInlineMethods(inlineMethods);
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
args.setExtractFinally(extractFinally);
args.setRenameFlags(renameFlags);
args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel);
args.setUseDxInput(useDx);
args.setPluginOptions(pluginOptions);
return args;
}
@@ -254,6 +316,14 @@ public class JadxCLIArgs {
return outDirRes;
}
public String getSingleClass() {
return singleClass;
}
public String getSingleClassOutput() {
return singleClassOutput;
}
public boolean isSkipResources() {
return skipResources;
}
@@ -274,6 +344,10 @@ public class JadxCLIArgs {
return useDx;
}
public DecompilationMode getDecompilationMode() {
return decompilationMode;
}
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
@@ -298,6 +372,14 @@ public class JadxCLIArgs {
return inlineMethods;
}
public boolean isAllowInlineKotlinLambda() {
return allowInlineKotlinLambda;
}
public boolean isExtractFinally() {
return extractFinally;
}
public boolean isDeobfuscationOn() {
return deobfuscationOn;
}
@@ -314,8 +396,8 @@ public class JadxCLIArgs {
return deobfuscationMapFile;
}
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
return deobfuscationMapFileMode;
}
public boolean isDeobfuscationUseSourceNameAsAlias() {
@@ -326,6 +408,14 @@ public class JadxCLIArgs {
return deobfuscationParseKotlinMetadata;
}
public ResourceNameSource getResourceNameSource() {
return resourceNameSource;
}
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public boolean isEscapeUnicode() {
return escapeUnicode;
}
@@ -370,6 +460,14 @@ public class JadxCLIArgs {
return commentsLevel;
}
public LogHelper.LogLevelEnum getLogLevel() {
return logLevel;
}
public Map<String, String> getPluginOptions() {
return pluginOptions;
}
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
private final String paramName;
@@ -399,22 +497,70 @@ public class JadxCLIArgs {
}
}
public static class CommentsLevelConverter implements IStringConverter<CommentsLevel> {
public static class CommentsLevelConverter extends BaseEnumConverter<CommentsLevel> {
public CommentsLevelConverter() {
super(CommentsLevel::valueOf, CommentsLevel::values);
}
}
public static class UseKotlinMethodsForVarNamesConverter extends BaseEnumConverter<UseKotlinMethodsForVarNames> {
public UseKotlinMethodsForVarNamesConverter() {
super(UseKotlinMethodsForVarNames::valueOf, UseKotlinMethodsForVarNames::values);
}
}
public static class DeobfuscationMapFileModeConverter extends BaseEnumConverter<DeobfuscationMapFileMode> {
public DeobfuscationMapFileModeConverter() {
super(DeobfuscationMapFileMode::valueOf, DeobfuscationMapFileMode::values);
}
}
public static class ResourceNameSourceConverter extends BaseEnumConverter<ResourceNameSource> {
public ResourceNameSourceConverter() {
super(ResourceNameSource::valueOf, ResourceNameSource::values);
}
}
public static class DecompilationModeConverter extends BaseEnumConverter<DecompilationMode> {
public DecompilationModeConverter() {
super(DecompilationMode::valueOf, DecompilationMode::values);
}
}
public static class LogLevelConverter extends BaseEnumConverter<LogHelper.LogLevelEnum> {
public LogLevelConverter() {
super(LogHelper.LogLevelEnum::valueOf, LogHelper.LogLevelEnum::values);
}
}
public abstract static class BaseEnumConverter<E extends Enum<E>> implements IStringConverter<E> {
private final Function<String, E> parse;
private final Supplier<E[]> values;
public BaseEnumConverter(Function<String, E> parse, Supplier<E[]> values) {
this.parse = parse;
this.values = values;
}
@Override
public CommentsLevel convert(String value) {
public E convert(String value) {
try {
return CommentsLevel.valueOf(value.toUpperCase());
return parse.apply(stringAsEnumName(value));
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown comments level, possible values are: "
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
'\'' + value + "' is unknown, possible values are: " + enumValuesString(values.get()));
}
}
}
public static String enumValuesString(Enum<?>[] values) {
return Stream.of(values)
.map(v -> v.name().toLowerCase(Locale.ROOT))
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
.collect(Collectors.joining(", "));
}
private static String stringAsEnumName(String value) {
// inverse of enumValuesString conversion
return value.replace('-', '_').toUpperCase(Locale.ROOT);
}
}
+49 -36
View File
@@ -1,10 +1,9 @@
package jadx.cli;
import org.jetbrains.annotations.NotNull;
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;
@@ -32,33 +31,61 @@ public class LogHelper {
}
}
@Nullable("For disable log level control")
private static LogLevelEnum logLevelValue;
public static void setLogLevelFromArgs(JadxCLIArgs args) {
if (isCustomLogConfig()) {
return;
}
LogLevelEnum logLevel = args.logLevel;
if (args.quiet) {
logLevel = LogLevelEnum.QUIET;
} else if (args.verbose) {
logLevel = LogLevelEnum.DEBUG;
}
applyLogLevel(logLevel);
public static void initLogLevel(JadxCLIArgs args) {
logLevelValue = getLogLevelFromArgs(args);
}
public static void applyLogLevel(LogLevelEnum logLevel) {
logLevelValue = logLevel;
private static LogLevelEnum getLogLevelFromArgs(JadxCLIArgs args) {
if (isCustomLogConfig()) {
return null;
}
if (args.quiet) {
return LogLevelEnum.QUIET;
}
if (args.verbose) {
return LogLevelEnum.DEBUG;
}
return args.logLevel;
}
public static void setLogLevelsForLoadingStage() {
if (logLevelValue == null) {
return;
}
if (logLevelValue == LogLevelEnum.PROGRESS) {
// show load errors
LogHelper.applyLogLevel(LogLevelEnum.ERROR);
fixForShowProgress();
return;
}
applyLogLevel(logLevelValue);
}
public static void setLogLevelsForDecompileStage() {
if (logLevelValue == null) {
return;
}
applyLogLevel(logLevelValue);
if (logLevelValue == LogLevelEnum.PROGRESS) {
fixForShowProgress();
}
}
/**
* Show progress: change to 'INFO' for control classes
*/
private static void fixForShowProgress() {
setLevelForClass(JadxCLI.class, Level.INFO);
setLevelForClass(JadxDecompiler.class, Level.INFO);
setLevelForClass(SingleClassMode.class, Level.INFO);
}
private static void applyLogLevel(@NotNull LogLevelEnum logLevel) {
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(logLevel.getLevel());
if (logLevel != LogLevelEnum.QUIET) {
// show progress for all levels except quiet
setLevelForClass(JadxCLI.class, Level.INFO);
setLevelForClass(JadxDecompiler.class, Level.INFO);
}
}
@Nullable
@@ -90,18 +117,4 @@ public class LogHelper {
}
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,87 @@
package jadx.cli;
import java.io.File;
import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.api.JadxDecompiler;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
public class SingleClassMode {
private static final Logger LOG = LoggerFactory.getLogger(SingleClassMode.class);
public static boolean process(JadxDecompiler jadx, JadxCLIArgs cliArgs) {
String singleClass = cliArgs.getSingleClass();
String singleClassOutput = cliArgs.getSingleClassOutput();
if (singleClass == null && singleClassOutput == null) {
return false;
}
ClassNode clsForProcess;
if (singleClass != null) {
clsForProcess = jadx.getRoot().resolveClass(singleClass);
if (clsForProcess == null) {
clsForProcess = jadx.getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(singleClass))
.findFirst().orElse(null);
}
if (clsForProcess == null) {
throw new JadxRuntimeException("Input class not found: " + singleClass);
}
if (clsForProcess.contains(AFlag.DONT_GENERATE)) {
throw new JadxRuntimeException("Input class can't be saved by currect jadx settings (marked as DONT_GENERATE)");
}
if (clsForProcess.isInner()) {
clsForProcess = clsForProcess.getTopParentClass();
LOG.warn("Input class is inner, parent class will be saved: {}", clsForProcess.getFullName());
}
} else {
// singleClassOutput is set
// expect only one class to be loaded
List<ClassNode> classes = jadx.getRoot().getClasses().stream()
.filter(c -> !c.isInner() && !c.contains(AFlag.DONT_GENERATE))
.collect(Collectors.toList());
int size = classes.size();
if (size == 1) {
clsForProcess = classes.get(0);
} else {
throw new JadxRuntimeException("Found " + size + " classes, single class output can't be used");
}
}
ICodeInfo codeInfo;
try {
codeInfo = clsForProcess.decompile();
} catch (Exception e) {
throw new JadxRuntimeException("Class decompilation failed", e);
}
String fileExt = SaveCode.getFileExtension(jadx.getRoot());
File out;
if (singleClassOutput == null) {
out = new File(jadx.getArgs().getOutDirSrc(), clsForProcess.getClassInfo().getAliasFullPath() + fileExt);
} else {
if (singleClassOutput.endsWith(fileExt)) {
// treat as file name
out = new File(singleClassOutput);
} else {
// treat as directory
out = new File(singleClassOutput, clsForProcess.getShortName() + fileExt);
}
}
File resultOut = FileUtils.prepareFile(out);
if (clsForProcess.getClassInfo().hasAlias()) {
LOG.info("Saving class '{}' (alias: '{}') to file '{}'",
clsForProcess.getClassInfo().getFullName(), clsForProcess.getFullName(), resultOut.getAbsolutePath());
} else {
LOG.info("Saving class '{}' to file '{}'", clsForProcess.getFullName(), resultOut.getAbsolutePath());
}
SaveCode.save(codeInfo.getCodeStr(), resultOut);
return true;
}
}
@@ -1,9 +1,14 @@
package jadx.cli;
import java.util.Collections;
import java.util.Map;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.utils.Utils.newConstStringMap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -47,6 +52,40 @@ public class JadxCLIArgsTest {
assertThat(override(args, "").isUseImports(), is(false));
}
@Test
public void testPluginOptionsOverride() {
// add key to empty base map
checkPluginOptionsMerge(
Collections.emptyMap(),
"-Poption=otherValue",
newConstStringMap("option", "otherValue"));
// override one key
checkPluginOptionsMerge(
newConstStringMap("option", "value"),
"-Poption=otherValue",
newConstStringMap("option", "otherValue"));
// merge different keys
checkPluginOptionsMerge(
Collections.singletonMap("option1", "value1"),
"-Poption2=otherValue2",
newConstStringMap("option1", "value1", "option2", "otherValue2"));
// merge and override
checkPluginOptionsMerge(
newConstStringMap("option1", "value1", "option2", "value2"),
"-Poption2=otherValue2",
newConstStringMap("option1", "value1", "option2", "otherValue2"));
}
private void checkPluginOptionsMerge(Map<String, String> baseMap, String providedArgs, Map<String, String> expectedMap) {
JadxCLIArgs args = new JadxCLIArgs();
args.pluginOptions = baseMap;
Map<String, String> resultMap = override(args, providedArgs).getPluginOptions();
assertThat(resultMap, Matchers.equalTo(expectedMap));
}
private JadxCLIArgs parse(String... args) {
return parse(new JadxCLIArgs(), args);
}
@@ -44,6 +44,33 @@ public class TestInput {
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
}
@Test
public void testResourceOnly() throws Exception {
decode("resourceOnly", "samples/resources-only.apk");
}
private void decode(String tmpDirName, String apkSample) throws URISyntaxException, IOException {
List<String> args = new ArrayList<>();
Path tempDir = FileUtils.createTempDir(tmpDirName);
args.add("-v");
args.add("-d");
args.add(tempDir.toAbsolutePath().toString());
URL resource = getClass().getClassLoader().getResource(apkSample);
assertThat(resource).isNotNull();
String sampleFile = resource.toURI().getRawPath();
args.add(sampleFile);
int result = JadxCLI.execute(args.toArray(new String[0]));
assertThat(result).isEqualTo(0);
List<Path> files = Files.find(
tempDir,
3,
(file, attr) -> file.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"))
.collect(Collectors.toList());
assertThat(files.isEmpty()).isFalse();
}
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
List<String> args = new ArrayList<>();
Path tempDir = FileUtils.createTempDir(tmpDirName);
+8 -8
View File
@@ -5,22 +5,22 @@ plugins {
dependencies {
api(project(':jadx-plugins:jadx-plugins-api'))
implementation 'com.google.code.gson:gson:2.8.9'
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
constraints {
// Force protobuf version to prevent Java-7 issue
implementation 'com.google.protobuf:protobuf-java:3.11.4'
}
implementation 'com.google.code.gson:gson:2.10.1'
// TODO: move resources decoding to separate plugin module
implementation 'com.android.tools.build:aapt2-proto:7.3.1-8691043'
implementation 'com.google.protobuf:protobuf-java:3.21.12' // forcing latest version
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
testImplementation(project(':jadx-plugins:jadx-dex-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
testImplementation('tools.profiler:async-profiler:1.8.3')
testImplementation 'org.eclipse.jdt:ecj:3.32.0'
testImplementation 'tools.profiler:async-profiler:2.9'
}
test {
@@ -1,65 +0,0 @@
package jadx.api;
public final class CodePosition {
private final int line;
private final int offset;
private final int pos;
public CodePosition(int line, int offset, int pos) {
this.line = line;
this.offset = offset;
this.pos = pos;
}
public CodePosition(int line) {
this(line, 0, -1);
}
@Deprecated
public CodePosition(int line, int offset) {
this(line, offset, -1);
}
public int getPos() {
return pos;
}
public int getLine() {
return line;
}
public int getOffset() {
return offset;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CodePosition that = (CodePosition) o;
return line == that.line && offset == that.offset;
}
@Override
public int hashCode() {
return line + 31 * offset;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(line);
if (offset != 0) {
sb.append(':').append(offset);
}
if (pos > 0) {
sb.append('@').append(pos);
}
return sb.toString();
}
}
@@ -0,0 +1,23 @@
package jadx.api;
public enum DecompilationMode {
/**
* Trying best options (default)
*/
AUTO,
/**
* Restore code structure (normal java code)
*/
RESTRUCTURE,
/**
* Simplified instructions (linear with goto's)
*/
SIMPLE,
/**
* Raw instructions without modifications
*/
FALLBACK
}
@@ -1,13 +1,21 @@
package jadx.api;
import java.io.Closeable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface ICodeCache {
public interface ICodeCache extends Closeable {
void add(String clsFullName, ICodeInfo codeInfo);
void remove(String clsFullName);
@Nullable
@NotNull
ICodeInfo get(String clsFullName);
@Nullable
String getCode(String clsFullName);
boolean contains(String clsFullName);
}
@@ -1,8 +1,7 @@
package jadx.api;
import java.util.Map;
import jadx.api.impl.SimpleCodeInfo;
import jadx.api.metadata.ICodeMetadata;
public interface ICodeInfo {
@@ -10,7 +9,7 @@ public interface ICodeInfo {
String getCodeStr();
Map<Integer, Integer> getLineMapping();
ICodeMetadata getCodeMetadata();
Map<CodePosition, Object> getAnnotations();
boolean hasMetadata();
}
@@ -2,7 +2,10 @@ package jadx.api;
import java.util.Map;
import jadx.core.dex.attributes.ILineAttributeNode;
import org.jetbrains.annotations.ApiStatus;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
public interface ICodeWriter {
String NL = System.getProperty("line.separator");
@@ -38,13 +41,21 @@ public interface ICodeWriter {
void setIndent(int indent);
/**
* Return current line (only if metadata is supported)
*/
int getLine();
void attachDefinition(ILineAttributeNode obj);
/**
* Return start line position (only if metadata is supported)
*/
int getLineStartPos();
void attachAnnotation(Object obj);
void attachDefinition(ICodeNodeRef obj);
void attachLineAnnotation(Object obj);
void attachAnnotation(ICodeAnnotation obj);
void attachLineAnnotation(ICodeAnnotation obj);
void attachSourceLine(int sourceLine);
@@ -56,5 +67,6 @@ public interface ICodeWriter {
StringBuilder getRawBuf();
Map<CodePosition, Object> getRawAnnotations();
@ApiStatus.Internal
Map<Integer, ICodeAnnotation> getRawAnnotations();
}
+152 -11
View File
@@ -1,19 +1,29 @@
package jadx.api;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.args.ResourceNameSource;
import jadx.api.data.ICodeData;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache;
import jadx.core.utils.files.FileUtils;
public class JadxArgs {
private static final Logger LOG = LoggerFactory.getLogger(JadxArgs.class);
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
@@ -35,7 +45,6 @@ public class JadxArgs {
private boolean cfgOutput = false;
private boolean rawCFGOutput = false;
private boolean fallbackMode = false;
private boolean showInconsistentCode = false;
private boolean useImports = true;
@@ -44,6 +53,7 @@ public class JadxArgs {
private boolean extractFinally = true;
private boolean inlineAnonymousClasses = true;
private boolean inlineMethods = true;
private boolean allowInlineKotlinLambda = true;
private boolean skipResources = false;
private boolean skipSources = false;
@@ -53,12 +63,19 @@ public class JadxArgs {
*/
private Predicate<String> classFilter = null;
/**
* Save dependencies for classes accepted by {@code classFilter}
*/
private boolean includeDependencies = false;
private boolean deobfuscationOn = false;
private boolean deobfuscationForceSave = false;
private boolean useSourceNameAsClassAlias = false;
private boolean parseKotlinMetadata = false;
private File deobfuscationMapFile = null;
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
private ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
private int deobfuscationMinLength = 0;
private int deobfuscationMaxLength = Integer.MAX_VALUE;
@@ -81,12 +98,27 @@ public class JadxArgs {
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
private DecompilationMode decompilationMode = DecompilationMode.AUTO;
private ICodeData codeData;
private CommentsLevel commentsLevel = CommentsLevel.INFO;
private boolean useDxInput = false;
public enum UseKotlinMethodsForVarNames {
DISABLE, APPLY, APPLY_AND_HIDE
}
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
/**
* Don't save files (can be using for performance testing)
*/
private boolean skipFilesSave = false;
private Map<String, String> pluginOptions = new HashMap<>();
public JadxArgs() {
// use default options
}
@@ -97,6 +129,19 @@ public class JadxArgs {
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
}
public void close() {
try {
inputFiles = null;
if (codeCache != null) {
codeCache.close();
}
} catch (Exception e) {
LOG.error("Failed to close JadxArgs", e);
} finally {
codeCache = null;
}
}
public List<File> getInputFiles() {
return inputFiles;
}
@@ -138,7 +183,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() {
@@ -158,11 +203,17 @@ public class JadxArgs {
}
public boolean isFallbackMode() {
return fallbackMode;
return decompilationMode == DecompilationMode.FALLBACK;
}
/**
* Deprecated: use 'decompilation mode' property
*/
@Deprecated
public void setFallbackMode(boolean fallbackMode) {
this.fallbackMode = fallbackMode;
if (fallbackMode) {
this.decompilationMode = DecompilationMode.FALLBACK;
}
}
public boolean isShowInconsistentCode() {
@@ -213,6 +264,14 @@ public class JadxArgs {
this.inlineMethods = inlineMethods;
}
public boolean isAllowInlineKotlinLambda() {
return allowInlineKotlinLambda;
}
public void setAllowInlineKotlinLambda(boolean allowInlineKotlinLambda) {
this.allowInlineKotlinLambda = allowInlineKotlinLambda;
}
public boolean isExtractFinally() {
return extractFinally;
}
@@ -237,6 +296,14 @@ public class JadxArgs {
this.skipSources = skipSources;
}
public void setIncludeDependencies(boolean includeDependencies) {
this.includeDependencies = includeDependencies;
}
public boolean isIncludeDependencies() {
return includeDependencies;
}
public Predicate<String> getClassFilter() {
return classFilter;
}
@@ -253,12 +320,24 @@ public class JadxArgs {
this.deobfuscationOn = deobfuscationOn;
}
@Deprecated
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
return deobfuscationMapFileMode == DeobfuscationMapFileMode.OVERWRITE;
}
@Deprecated
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
this.deobfuscationForceSave = deobfuscationForceSave;
if (deobfuscationForceSave) {
this.deobfuscationMapFileMode = DeobfuscationMapFileMode.OVERWRITE;
}
}
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
return deobfuscationMapFileMode;
}
public void setDeobfuscationMapFileMode(DeobfuscationMapFileMode deobfuscationMapFileMode) {
this.deobfuscationMapFileMode = deobfuscationMapFileMode;
}
public boolean isUseSourceNameAsClassAlias() {
@@ -301,6 +380,14 @@ public class JadxArgs {
this.deobfuscationMapFile = deobfuscationMapFile;
}
public ResourceNameSource getResourceNameSource() {
return resourceNameSource;
}
public void setResourceNameSource(ResourceNameSource resourceNameSource) {
this.resourceNameSource = resourceNameSource;
}
public boolean isEscapeUnicode() {
return escapeUnicode;
}
@@ -393,6 +480,14 @@ public class JadxArgs {
this.outputFormat = outputFormat;
}
public DecompilationMode getDecompilationMode() {
return decompilationMode;
}
public void setDecompilationMode(DecompilationMode decompilationMode) {
this.decompilationMode = decompilationMode;
}
public ICodeCache getCodeCache() {
return codeCache;
}
@@ -433,6 +528,46 @@ public class JadxArgs {
this.useDxInput = useDxInput;
}
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public void setUseKotlinMethodsForVarNames(UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
}
public boolean isSkipFilesSave() {
return skipFilesSave;
}
public void setSkipFilesSave(boolean skipFilesSave) {
this.skipFilesSave = skipFilesSave;
}
public Map<String, String> getPluginOptions() {
return pluginOptions;
}
public void setPluginOptions(Map<String, String> pluginOptions) {
this.pluginOptions = pluginOptions;
}
/**
* Hash of all options that can change result code
*/
public String makeCodeArgsHash() {
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
+ inlineAnonymousClasses + inlineMethods
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
+ resourceNameSource
+ parseKotlinMetadata + useKotlinMethodsForVarNames
+ insertDebugLines + extractFinally
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
+ commentsLevel + useDxInput + pluginOptions;
return FileUtils.md5Sum(argStr.getBytes(StandardCharsets.US_ASCII));
}
@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -440,18 +575,21 @@ public class JadxArgs {
+ ", outDirSrc=" + outDirSrc
+ ", outDirRes=" + outDirRes
+ ", threadsCount=" + threadsCount
+ ", cfgOutput=" + cfgOutput
+ ", rawCFGOutput=" + rawCFGOutput
+ ", fallbackMode=" + fallbackMode
+ ", decompilationMode=" + decompilationMode
+ ", showInconsistentCode=" + showInconsistentCode
+ ", useImports=" + useImports
+ ", skipResources=" + skipResources
+ ", skipSources=" + skipSources
+ ", includeDependencies=" + includeDependencies
+ ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationMapFile=" + deobfuscationMapFile
+ ", deobfuscationForceSave=" + deobfuscationForceSave
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
+ ", resourceNameSource=" + resourceNameSource
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
+ ", insertDebugLines=" + insertDebugLines
+ ", extractFinally=" + extractFinally
+ ", deobfuscationMinLength=" + deobfuscationMinLength
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
+ ", escapeUnicode=" + escapeUnicode
@@ -465,6 +603,9 @@ public class JadxArgs {
+ ", codeCache=" + codeCache
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
+ ", useDxInput=" + useDxInput
+ ", pluginOptions=" + pluginOptions
+ ", cfgOutput=" + cfgOutput
+ ", rawCFGOutput=" + rawCFGOutput
+ '}';
}
}
@@ -13,8 +13,9 @@ public class JadxArgsValidator {
private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class);
public static void validate(JadxArgs args) {
checkInputFiles(args);
public static void validate(JadxDecompiler jadx) {
JadxArgs args = jadx.getArgs();
checkInputFiles(jadx, args);
validateOutDirs(args);
if (LOG.isDebugEnabled()) {
@@ -22,9 +23,9 @@ public class JadxArgsValidator {
}
}
private static void checkInputFiles(JadxArgs args) {
private static void checkInputFiles(JadxDecompiler jadx, JadxArgs args) {
List<File> inputFiles = args.getInputFiles();
if (inputFiles.isEmpty()) {
if (inputFiles.isEmpty() && jadx.getCustomLoads().isEmpty()) {
throw new JadxArgsValidateException("Please specify input file");
}
for (File inputFile : inputFiles) {
@@ -66,19 +67,22 @@ public class JadxArgsValidator {
@NotNull
private static File makeDirFromInput(JadxArgs args) {
File outDir;
String outDirName;
File file = args.getInputFiles().get(0);
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1) {
outDirName = name.substring(0, pos);
List<File> inputFiles = args.getInputFiles();
if (inputFiles.isEmpty()) {
outDirName = JadxArgs.DEFAULT_OUT_DIR;
} else {
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
File file = inputFiles.get(0);
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1) {
outDirName = name.substring(0, pos);
} else {
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
}
}
LOG.info("output directory: {}", outDirName);
outDir = new File(outDirName);
return outDir;
return new File(outDirName);
}
private static void checkFile(File file) {
@@ -12,7 +12,6 @@ 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;
@@ -25,14 +24,18 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.data.annotations.VarRef;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.metadata.annotations.VarRef;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
@@ -89,11 +92,9 @@ public final class JadxDecompiler implements Closeable {
private BinaryXMLParser binaryXmlParser;
private ProtoXMLParser protoXmlParser;
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
private final List<ILoadResult> customLoads = new ArrayList<>();
public JadxDecompiler() {
this(new JadxArgs());
@@ -105,7 +106,7 @@ public final class JadxDecompiler implements Closeable {
public void load() {
reset();
JadxArgsValidator.validate(args);
JadxArgsValidator.validate(this);
LOG.info("loading ...");
loadPlugins(args);
loadInputFiles();
@@ -122,12 +123,25 @@ public final class JadxDecompiler implements Closeable {
loadedInputs.clear();
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
long start = System.currentTimeMillis();
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
ILoadResult loadResult = inputPlugin.loadFiles(inputFiles);
if (loadResult != null && !loadResult.isEmpty()) {
loadedInputs.add(loadResult);
}
}
loadedInputs.addAll(customLoads);
if (LOG.isDebugEnabled()) {
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
}
}
public void addCustomLoad(ILoadResult customLoad) {
customLoads.add(customLoad);
}
public List<ILoadResult> getCustomLoads() {
return customLoads;
}
private void reset() {
@@ -136,12 +150,13 @@ public final class JadxDecompiler implements Closeable {
resources = null;
binaryXmlParser = null;
protoXmlParser = null;
}
classesMap.clear();
methodsMap.clear();
fieldsMap.clear();
@Override
public void close() {
reset();
closeInputs();
args.close();
}
private void closeInputs() {
@@ -155,11 +170,6 @@ public final class JadxDecompiler implements Closeable {
loadedInputs.clear();
}
@Override
public void close() {
reset();
}
private void loadPlugins(JadxArgs args) {
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
pluginManager.load();
@@ -167,8 +177,21 @@ public final class JadxDecompiler implements Closeable {
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
p -> p.getPluginInfo().getPluginId()));
}
Map<String, String> pluginOptions = args.getPluginOptions();
if (!pluginOptions.isEmpty()) {
LOG.debug("Applying plugin options: {}", pluginOptions);
for (JadxPluginOptions plugin : pluginManager.getPluginsWithOptions()) {
try {
plugin.setOptions(pluginOptions);
} catch (Exception e) {
String pluginId = plugin.getPluginInfo().getPluginId();
throw new JadxRuntimeException("Failed to apply options for plugin: " + pluginId, e);
}
}
}
}
@SuppressWarnings("unused")
public void registerPlugin(JadxPlugin plugin) {
pluginManager.register(plugin);
}
@@ -210,6 +233,7 @@ public final class JadxDecompiler implements Closeable {
save(false, true);
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private void save(boolean saveSources, boolean saveResources) {
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
ex.shutdown();
@@ -282,9 +306,24 @@ public final class JadxDecompiler implements Closeable {
}
private void appendResourcesSaveTasks(List<Runnable> tasks, File outDir) {
if (args.isSkipFilesSave()) {
return;
}
// process AndroidManifest.xml first to load complete resource ids table
for (ResourceFile resourceFile : getResources()) {
if (resourceFile.getType() == ResourceType.MANIFEST) {
new ResourcesSaver(outDir, resourceFile).run();
}
}
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
for (ResourceFile resourceFile : getResources()) {
if (resourceFile.getType() != ResourceType.ARSC
ResourceType resType = resourceFile.getType();
if (resType == ResourceType.MANIFEST) {
// already processed
continue;
}
if (resType != ResourceType.ARSC
&& inputFileNames.contains(resourceFile.getOriginalName())) {
// ignore resource made from input file
continue;
@@ -298,20 +337,31 @@ public final class JadxDecompiler implements Closeable {
List<JavaClass> classes = getClasses();
List<JavaClass> processQueue = new ArrayList<>(classes.size());
for (JavaClass cls : classes) {
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
ClassNode clsNode = cls.getClassNode();
if (clsNode.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (classFilter != null && !classFilter.test(cls.getFullName())) {
if (classFilter != null && !classFilter.test(clsNode.getClassInfo().getFullName())) {
if (!args.isIncludeDependencies()) {
clsNode.add(AFlag.DONT_GENERATE);
}
continue;
}
processQueue.add(cls);
}
for (List<JavaClass> decompileBatch : decompileScheduler.buildBatches(processQueue)) {
List<List<JavaClass>> batches;
try {
batches = decompileScheduler.buildBatches(processQueue);
} catch (Exception e) {
throw new JadxRuntimeException("Decompilation batches build failed", e);
}
for (List<JavaClass> decompileBatch : batches) {
tasks.add(() -> {
for (JavaClass cls : decompileBatch) {
try {
ICodeInfo code = cls.getCodeInfo();
SaveCode.save(outDir, cls.getClassNode(), code);
ClassNode clsNode = cls.getClassNode();
ICodeInfo code = clsNode.getCode();
SaveCode.save(outDir, clsNode, code);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls, e);
}
@@ -325,14 +375,14 @@ public final class JadxDecompiler implements Closeable {
return Collections.emptyList();
}
if (classes == null) {
List<ClassNode> classNodeList = root.getClasses(false);
List<ClassNode> classNodeList = root.getClasses();
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
classesMap.clear();
for (ClassNode classNode : classNodeList) {
if (!classNode.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(classNode, this);
clsList.add(javaClass);
classesMap.put(classNode, javaClass);
if (classNode.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (!classNode.getClassInfo().isInner()) {
clsList.add(convertClassNode(classNode));
}
}
classes = Collections.unmodifiableList(clsList);
@@ -340,7 +390,11 @@ public final class JadxDecompiler implements Closeable {
return classes;
}
public List<ResourceFile> getResources() {
public List<JavaClass> getClassesWithInners() {
return Utils.collectionMap(root.getClasses(), this::convertClassNode);
}
public synchronized List<ResourceFile> getResources() {
if (resources == null) {
if (root == null) {
return Collections.emptyList();
@@ -397,6 +451,7 @@ public final class JadxDecompiler implements Closeable {
/**
* Internal API. Not Stable!
*/
@ApiStatus.Internal
public RootNode getRoot() {
return root;
}
@@ -415,112 +470,40 @@ public final class JadxDecompiler implements Closeable {
return protoXmlParser;
}
private void loadJavaClass(JavaClass javaClass) {
javaClass.getMethods().forEach(mth -> methodsMap.put(mth.getMethodNode(), mth));
javaClass.getFields().forEach(fld -> fieldsMap.put(fld.getFieldNode(), fld));
for (JavaClass innerCls : javaClass.getInnerClasses()) {
classesMap.put(innerCls.getClassNode(), innerCls);
loadJavaClass(innerCls);
}
for (JavaClass inlinedCls : javaClass.getInlinedClasses()) {
classesMap.put(inlinedCls.getClassNode(), inlinedCls);
loadJavaClass(inlinedCls);
}
}
/**
* Get JavaClass by ClassNode without loading and decompilation
*/
JavaClass convertClassNode(ClassNode cls) {
return classesMap.compute(cls, (node, prevJavaCls) -> {
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) {
// keep previous variable
return prevJavaCls;
}
if (cls.isInner()) {
return new JavaClass(cls, convertClassNode(cls.getParentClass()));
}
return new JavaClass(cls, this);
});
}
@Nullable("For not generated classes")
@ApiStatus.Internal
public JavaClass getJavaClassByNode(ClassNode cls) {
JavaClass javaClass = classesMap.get(cls);
if (javaClass != null && javaClass.getClassNode() == cls) {
return javaClass;
synchronized JavaClass convertClassNode(ClassNode cls) {
JavaClass javaClass = cls.getJavaNode();
if (javaClass == null) {
javaClass = cls.isInner()
? new JavaClass(cls, convertClassNode(cls.getParentClass()))
: new JavaClass(cls, this);
cls.setJavaNode(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);
return javaClass;
}
@Nullable
private JavaMethod getJavaMethodByNode(MethodNode mth) {
JavaMethod javaMethod = methodsMap.get(mth);
if (javaMethod != null && javaMethod.getMethodNode() == mth) {
return javaMethod;
@ApiStatus.Internal
synchronized JavaField convertFieldNode(FieldNode fld) {
JavaField javaField = fld.getJavaNode();
if (javaField == null) {
JavaClass parentCls = convertClassNode(fld.getParentClass());
javaField = new JavaField(parentCls, fld);
fld.setJavaNode(javaField);
}
if (mth.contains(AFlag.DONT_GENERATE)) {
return null;
}
// parent class not loaded yet
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
if (javaClass == null) {
return null;
}
loadJavaClass(javaClass);
javaMethod = methodsMap.get(mth);
if (javaMethod != null) {
return javaMethod;
}
if (mth.getParentClass().hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
return javaField;
}
@Nullable
private JavaField getJavaFieldByNode(FieldNode fld) {
JavaField javaField = fieldsMap.get(fld);
if (javaField != null && javaField.getFieldNode() == fld) {
return javaField;
@ApiStatus.Internal
synchronized JavaMethod convertMethodNode(MethodNode mth) {
JavaMethod javaMethod = mth.getJavaNode();
if (javaMethod == null) {
javaMethod = new JavaMethod(convertClassNode(mth.getParentClass()), mth);
mth.setJavaNode(javaMethod);
}
// parent class not loaded yet
JavaClass javaClass = getJavaClassByNode(fld.getParentClass().getTopParentClass());
if (javaClass == null) {
return null;
}
loadJavaClass(javaClass);
javaField = fieldsMap.get(fld);
if (javaField != null) {
return javaField;
}
if (fld.getParentClass().hasNotGeneratedParent()) {
return null;
}
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
return javaMethod;
}
@Nullable
@@ -528,7 +511,7 @@ public final class JadxDecompiler implements Closeable {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
.findFirst()
.map(this::getJavaClassByNode)
.map(this::convertClassNode)
.orElse(null);
}
@@ -549,9 +532,9 @@ public final class JadxDecompiler implements Closeable {
.orElse(null);
if (node != null) {
if (node.contains(AFlag.DONT_GENERATE)) {
return getJavaClassByNode(node.getTopParentClass());
return convertClassNode(node.getTopParentClass());
} else {
return getJavaClassByNode(node);
return convertClassNode(node);
}
}
return null;
@@ -562,86 +545,87 @@ public final class JadxDecompiler implements Closeable {
return getRoot().getClasses().stream()
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
.findFirst()
.map(this::getJavaClassByNode)
.map(this::convertClassNode)
.orElse(null);
}
@Nullable
JavaNode convertNode(Object obj) {
if (obj instanceof VarRef) {
VarRef varRef = (VarRef) obj;
MethodNode mthNode = varRef.getMth();
JavaMethod mth = getJavaMethodByNode(mthNode);
if (mth == null) {
public JavaNode getJavaNodeByRef(ICodeNodeRef ann) {
return getJavaNodeByCodeAnnotation(null, ann);
}
@Nullable
public JavaNode getJavaNodeByCodeAnnotation(@Nullable ICodeInfo codeInfo, @Nullable ICodeAnnotation ann) {
if (ann == null) {
return null;
}
switch (ann.getAnnType()) {
case CLASS:
return convertClassNode((ClassNode) ann);
case METHOD:
return convertMethodNode((MethodNode) ann);
case FIELD:
return convertFieldNode((FieldNode) ann);
case DECLARATION:
return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode());
case VAR:
return resolveVarNode((VarNode) ann);
case VAR_REF:
return resolveVarRef(codeInfo, (VarRef) ann);
case OFFSET:
// offset annotation don't have java node object
return null;
default:
throw new JadxRuntimeException("Unknown annotation type: " + ann.getAnnType() + ", class: " + ann.getClass());
}
}
private JavaVariable resolveVarNode(VarNode varNode) {
JavaMethod javaNode = convertMethodNode(varNode.getMth());
return new JavaVariable(javaNode, varNode);
}
@Nullable
private JavaVariable resolveVarRef(ICodeInfo codeInfo, VarRef varRef) {
if (codeInfo == null) {
throw new JadxRuntimeException("Missing code info for resolve VarRef: " + varRef);
}
ICodeAnnotation varNodeAnn = codeInfo.getCodeMetadata().getAt(varRef.getRefPos());
if (varNodeAnn != null && varNodeAnn.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) {
ICodeNodeRef nodeRef = ((NodeDeclareRef) varNodeAnn).getNode();
if (nodeRef.getAnnType() == ICodeAnnotation.AnnType.VAR) {
return resolveVarNode((VarNode) nodeRef);
}
return new JavaVariable(mth, varRef);
}
if (!(obj instanceof LineAttrNode)) {
return null;
}
LineAttrNode node = (LineAttrNode) obj;
if (node.contains(AFlag.DONT_GENERATE)) {
return null;
}
if (obj instanceof ClassNode) {
return convertClassNode((ClassNode) obj);
}
if (obj instanceof MethodNode) {
return getJavaMethodByNode(((MethodNode) obj));
}
if (obj instanceof FieldNode) {
return getJavaFieldByNode((FieldNode) obj);
}
throw new JadxRuntimeException("Unexpected node type: " + obj);
return null;
}
// TODO: make interface for all nodes in code annotations and add common method instead this
Object getInternalNode(JavaNode javaNode) {
if (javaNode instanceof JavaClass) {
return ((JavaClass) javaNode).getClassNode();
}
if (javaNode instanceof JavaMethod) {
return ((JavaMethod) javaNode).getMethodNode();
}
if (javaNode instanceof JavaField) {
return ((JavaField) javaNode).getFieldNode();
}
if (javaNode instanceof JavaVariable) {
return ((JavaVariable) javaNode).getVarRef();
}
throw new JadxRuntimeException("Unexpected node type: " + javaNode);
}
List<JavaNode> convertNodes(Collection<?> nodesList) {
List<JavaNode> convertNodes(Collection<? extends ICodeNodeRef> nodesList) {
return nodesList.stream()
.map(this::convertNode)
.map(this::getJavaNodeByRef)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@Nullable
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
Map<CodePosition, Object> map = codeInfo.getAnnotations();
if (map.isEmpty()) {
return null;
}
Object obj = map.get(new CodePosition(line, offset));
if (obj == null) {
return null;
}
return convertNode(obj);
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int pos) {
ICodeAnnotation ann = codeInfo.getCodeMetadata().getAt(pos);
return getJavaNodeByCodeAnnotation(codeInfo, ann);
}
@Nullable
public CodePosition getDefinitionPosition(JavaNode javaNode) {
JavaClass jCls = javaNode.getTopParentClass();
jCls.decompile();
int defLine = javaNode.getDecompiledLine();
if (defLine == 0) {
public JavaNode getClosestJavaNode(ICodeInfo codeInfo, int pos) {
ICodeAnnotation ann = codeInfo.getCodeMetadata().getClosestUp(pos);
return getJavaNodeByCodeAnnotation(codeInfo, ann);
}
@Nullable
public JavaNode getEnclosingNode(ICodeInfo codeInfo, int pos) {
ICodeNodeRef obj = codeInfo.getCodeMetadata().getNodeAt(pos);
if (obj == null) {
return null;
}
return new CodePosition(defLine, 0, javaNode.getDefPos());
return getJavaNodeByRef(obj);
}
public void reloadCodeData() {
+121 -88
View File
@@ -8,15 +8,25 @@ import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.InlinedAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.ListUtils;
public final class JavaClass implements JavaNode {
private static final Logger LOG = LoggerFactory.getLogger(JavaClass.class);
private final JadxDecompiler decompiler;
private final ClassNode cls;
@@ -44,24 +54,24 @@ public final class JavaClass implements JavaNode {
}
public String getCode() {
ICodeInfo code = getCodeInfo();
if (code == null) {
return "";
}
return code.getCodeStr();
return getCodeInfo().getCodeStr();
}
public ICodeInfo getCodeInfo() {
public @NotNull ICodeInfo getCodeInfo() {
ICodeInfo code = load();
if (code != null) {
return code;
}
return cls.decompile();
}
public void decompile() {
cls.decompile();
load();
}
public synchronized void reload() {
public synchronized ICodeInfo reload() {
listsLoaded = false;
cls.reloadCode();
return cls.reloadCode();
}
public void unload() {
@@ -69,10 +79,26 @@ public final class JavaClass implements JavaNode {
cls.unloadCode();
}
public boolean isNoCode() {
return cls.contains(AFlag.DONT_GENERATE);
}
public boolean isInner() {
return cls.isInner();
}
public synchronized String getSmali() {
return cls.getDisassembledCode();
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
if (ann.getAnnType() == ICodeAnnotation.AnnType.CLASS) {
return ann.equals(cls);
}
return false;
}
/**
* Internal API. Not Stable!
*/
@@ -81,21 +107,34 @@ public final class JavaClass implements JavaNode {
return cls;
}
private synchronized void loadLists() {
/**
* Decompile class and loads internal lists of fields, methods, etc.
* Do nothing if already loaded.
*
* @return code info if decompilation was executed, null otherwise
*/
private synchronized @Nullable ICodeInfo load() {
if (listsLoaded) {
return;
return null;
}
listsLoaded = true;
decompile();
JadxDecompiler rootDecompiler = getRootDecompiler();
ICodeInfo code;
if (cls.getState().isProcessComplete()) {
// already decompiled -> class internals loaded
code = null;
} else {
code = cls.decompile();
}
JadxDecompiler rootDecompiler = getRootDecompiler();
int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists();
javaClass.load();
list.add(javaClass);
}
}
@@ -106,7 +145,7 @@ public final class JavaClass implements JavaNode {
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
for (ClassNode inner : cls.getInlinedClasses()) {
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists();
javaClass.load();
list.add(javaClass);
}
this.inlinedClasses = Collections.unmodifiableList(list);
@@ -117,8 +156,7 @@ public final class JavaClass implements JavaNode {
List<JavaField> flds = new ArrayList<>(fieldsCount);
for (FieldNode f : cls.getFields()) {
if (!f.contains(AFlag.DONT_GENERATE)) {
JavaField javaField = new JavaField(this, f);
flds.add(javaField);
flds.add(rootDecompiler.convertFieldNode(f));
}
}
this.fields = Collections.unmodifiableList(flds);
@@ -129,65 +167,56 @@ public final class JavaClass implements JavaNode {
List<JavaMethod> mths = new ArrayList<>(methodsCount);
for (MethodNode m : cls.getMethods()) {
if (!m.contains(AFlag.DONT_GENERATE)) {
JavaMethod javaMethod = new JavaMethod(this, m);
mths.add(javaMethod);
mths.add(rootDecompiler.convertMethodNode(m));
}
}
mths.sort(Comparator.comparing(JavaMethod::getName));
this.methods = Collections.unmodifiableList(mths);
}
return code;
}
protected JadxDecompiler getRootDecompiler() {
JadxDecompiler getRootDecompiler() {
if (parent != null) {
return parent.getRootDecompiler();
}
return decompiler;
}
public Map<CodePosition, Object> getCodeAnnotations() {
ICodeInfo code = getCodeInfo();
if (code == null) {
return Collections.emptyMap();
}
return code.getAnnotations();
public ICodeAnnotation getAnnotationAt(int pos) {
return getCodeInfo().getCodeMetadata().getAt(pos);
}
public Object getAnnotationAt(CodePosition pos) {
return getCodeAnnotations().get(pos);
}
public Map<CodePosition, JavaNode> getUsageMap() {
Map<CodePosition, Object> map = getCodeAnnotations();
public Map<Integer, JavaNode> getUsageMap() {
Map<Integer, ICodeAnnotation> map = getCodeInfo().getCodeMetadata().getAsMap();
if (map.isEmpty() || decompiler == null) {
return Collections.emptyMap();
}
Map<CodePosition, JavaNode> resultMap = new HashMap<>(map.size());
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
JavaNode node = getRootDecompiler().convertNode(obj);
if (node != null) {
resultMap.put(codePosition, node);
Map<Integer, JavaNode> resultMap = new HashMap<>(map.size());
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
int codePosition = entry.getKey();
ICodeAnnotation obj = entry.getValue();
if (obj instanceof ICodeNodeRef) {
JavaNode node = getRootDecompiler().getJavaNodeByRef((ICodeNodeRef) obj);
if (node != null) {
resultMap.put(codePosition, node);
}
}
}
return resultMap;
}
public List<CodePosition> getUsageFor(JavaNode javaNode) {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty() || decompiler == null) {
public List<Integer> getUsePlacesFor(ICodeInfo codeInfo, JavaNode javaNode) {
if (!codeInfo.hasMetadata()) {
return Collections.emptyList();
}
Object internalNode = getRootDecompiler().getInternalNode(javaNode);
List<CodePosition> result = new ArrayList<>();
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
if (internalNode.equals(obj)) {
result.add(codePosition);
List<Integer> result = new ArrayList<>();
codeInfo.getCodeMetadata().searchDown(0, (pos, ann) -> {
if (javaNode.isOwnCodeAnnotation(ann)) {
result.add(pos);
}
}
return null;
});
return result;
}
@@ -196,20 +225,8 @@ public final class JavaClass implements JavaNode {
return getRootDecompiler().convertNodes(cls.getUseIn());
}
@Nullable
@Deprecated
public JavaNode getJavaNodeAtPosition(int line, int offset) {
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
}
@Nullable
@Deprecated
public CodePosition getDefinitionPosition() {
return getRootDecompiler().getDefinitionPosition(this);
}
public Integer getSourceLine(int decompiledLine) {
return getCodeInfo().getLineMapping().get(decompiledLine);
return getCodeInfo().getCodeMetadata().getLineMapping().get(decompiledLine);
}
@Override
@@ -235,25 +252,37 @@ public final class JavaClass implements JavaNode {
return parent;
}
@Override
public JavaClass getTopParentClass() {
if (cls.contains(AFlag.ANONYMOUS_CLASS)) {
// moved to usage class
return getParentForAnonymousClass();
}
return parent == null ? this : parent.getTopParentClass();
public JavaClass getOriginalTopParentClass() {
return parent == null ? this : parent.getOriginalTopParentClass();
}
private JavaClass getParentForAnonymousClass() {
List<JavaNode> useIn = getUseIn();
if (useIn.isEmpty()) {
return this;
/**
* Return top parent class which contains code of this class.
* Code parent can be different from original parent after move or inline
*
* @return this if already a top class
*/
@Override
public JavaClass getTopParentClass() {
JavaClass codeParent = getCodeParent();
return codeParent == null ? this : codeParent.getTopParentClass();
}
/**
* Return parent class which contains code of this class.
* Code parent can be different for original parent after move or inline
*/
public @Nullable JavaClass getCodeParent() {
AnonymousClassAttr anonymousClsAttr = cls.get(AType.ANONYMOUS_CLASS);
if (anonymousClsAttr != null) {
// moved to usage class
return getRootDecompiler().convertClassNode(anonymousClsAttr.getOuterCls());
}
JavaNode useNode = useIn.get(0);
if (useNode.equals(this)) {
return this;
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
if (inlinedAttr != null) {
return getRootDecompiler().convertClassNode(inlinedAttr.getInlineCls());
}
return useNode.getTopParentClass();
return parent;
}
public AccessInfo getAccessInfo() {
@@ -261,22 +290,22 @@ public final class JavaClass implements JavaNode {
}
public List<JavaClass> getInnerClasses() {
loadLists();
load();
return innerClasses;
}
public List<JavaClass> getInlinedClasses() {
loadLists();
load();
return inlinedClasses;
}
public List<JavaField> getFields() {
loadLists();
load();
return fields;
}
public List<JavaMethod> getMethods() {
loadLists();
load();
return methods;
}
@@ -286,7 +315,16 @@ public final class JavaClass implements JavaNode {
if (methodNode == null) {
return null;
}
return new JavaMethod(this, methodNode);
return getRootDecompiler().convertMethodNode(methodNode);
}
public List<JavaClass> getDependencies() {
JadxDecompiler d = getRootDecompiler();
return ListUtils.map(cls.getDependencies(), d::convertClassNode);
}
public int getTotalDepsCount() {
return cls.getTotalDepsCount();
}
@Override
@@ -294,11 +332,6 @@ public final class JavaClass implements JavaNode {
this.cls.getClassInfo().removeAlias();
}
@Override
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
@Override
public int getDefPos() {
return cls.getDefPosition();
@@ -4,6 +4,7 @@ import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import jadx.api.metadata.ICodeAnnotation;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
@@ -28,6 +29,10 @@ public final class JavaField implements JavaNode {
return parent.getFullName() + '.' + getName();
}
public String getRawName() {
return field.getName();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
@@ -46,11 +51,6 @@ public final class JavaField implements JavaNode {
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
}
@Override
public int getDecompiledLine() {
return field.getDecompiledLine();
}
@Override
public int getDefPos() {
return field.getDefPosition();
@@ -66,6 +66,14 @@ public final class JavaField implements JavaNode {
this.field.getFieldInfo().removeAlias();
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
if (ann.getAnnType() == ICodeAnnotation.AnnType.FIELD) {
return ann.equals(field);
}
return false;
}
/**
* Internal API. Not Stable!
*/
@@ -2,10 +2,14 @@ package jadx.api;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.jetbrains.annotations.ApiStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.metadata.ICodeAnnotation;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.AccessInfo;
@@ -14,6 +18,8 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils;
public final class JavaMethod implements JavaNode {
private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class);
private final MethodNode mth;
private final JavaClass parent;
@@ -73,7 +79,14 @@ public final class JavaMethod implements JavaNode {
}
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
return ovrdAttr.getRelatedMthNodes().stream()
.map(m -> ((JavaMethod) decompiler.convertNode(m)))
.map(m -> {
JavaMethod javaMth = decompiler.convertMethodNode(m);
if (javaMth == null) {
LOG.warn("Failed convert to java method: {}", m);
}
return javaMth;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@@ -85,11 +98,6 @@ public final class JavaMethod implements JavaNode {
return mth.getMethodInfo().isClassInit();
}
@Override
public int getDecompiledLine() {
return mth.getDecompiledLine();
}
@Override
public int getDefPos() {
return mth.getDefPosition();
@@ -100,6 +108,14 @@ public final class JavaMethod implements JavaNode {
this.mth.getMethodInfo().removeAlias();
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
if (ann.getAnnType() == ICodeAnnotation.AnnType.METHOD) {
return ann.equals(mth);
}
return false;
}
/**
* Internal API. Not Stable!
*/
@@ -2,6 +2,8 @@ package jadx.api;
import java.util.List;
import jadx.api.metadata.ICodeAnnotation;
public interface JavaNode {
String getName();
@@ -12,12 +14,12 @@ public interface JavaNode {
JavaClass getTopParentClass();
int getDecompiledLine();
int getDefPos();
List<JavaNode> getUseIn();
default void removeAlias() {
}
boolean isOwnCodeAnnotation(ICodeAnnotation ann);
}
@@ -5,6 +5,8 @@ import java.util.List;
import org.jetbrains.annotations.NotNull;
import jadx.api.metadata.ICodeAnnotation;
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
private final String name;
private final List<JavaClass> classes;
@@ -39,11 +41,6 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return null;
}
@Override
public int getDecompiledLine() {
return 0;
}
@Override
public int getDefPos() {
return 0;
@@ -54,6 +51,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return Collections.emptyList();
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
return false;
}
@Override
public int compareTo(@NotNull JavaPackage o) {
return name.compareTo(o.name);
@@ -4,17 +4,20 @@ import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.data.annotations.VarRef;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.metadata.annotations.VarRef;
import jadx.core.dex.instructions.args.ArgType;
public class JavaVariable implements JavaNode {
private final JavaMethod mth;
private final VarRef varRef;
private final VarNode varNode;
public JavaVariable(JavaMethod mth, VarRef varRef) {
public JavaVariable(JavaMethod mth, VarNode varNode) {
this.mth = mth;
this.varRef = varRef;
this.varNode = varNode;
}
public JavaMethod getMth() {
@@ -22,26 +25,30 @@ public class JavaVariable implements JavaNode {
}
public int getReg() {
return varRef.getReg();
return varNode.getReg();
}
public int getSsa() {
return varRef.getSsa();
return varNode.getSsa();
}
@Override
public String getName() {
return varRef.getName();
public @Nullable String getName() {
return varNode.getName();
}
@ApiStatus.Internal
public VarRef getVarRef() {
return varRef;
public VarNode getVarNode() {
return varNode;
}
@Override
public String getFullName() {
return varRef.getType() + " " + varRef.getName() + " (r" + varRef.getReg() + "v" + varRef.getSsa() + ")";
return varNode.getType() + " " + varNode.getName() + " (r" + varNode.getReg() + "v" + varNode.getSsa() + ")";
}
public ArgType getType() {
return ArgType.tryToResolveClassAlias(mth.getMethodNode().root(), varNode.getType());
}
@Override
@@ -54,20 +61,9 @@ public class JavaVariable implements JavaNode {
return mth.getTopParentClass();
}
@Override
public int getDecompiledLine() {
if (varRef instanceof VarDeclareRef) {
return ((VarDeclareRef) varRef).getDecompiledLine();
}
return 0;
}
@Override
public int getDefPos() {
if (varRef instanceof VarDeclareRef) {
return ((VarDeclareRef) varRef).getDefPosition();
}
return 0;
return varNode.getDefPosition();
}
@Override
@@ -75,9 +71,18 @@ public class JavaVariable implements JavaNode {
return Collections.singletonList(mth);
}
@Override
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
if (ann.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
VarRef varRef = (VarRef) ann;
return varRef.getRefPos() == getDefPos();
}
return false;
}
@Override
public int hashCode() {
return varRef.hashCode();
return varNode.hashCode();
}
@Override
@@ -88,6 +93,6 @@ public class JavaVariable implements JavaNode {
if (!(o instanceof JavaVariable)) {
return false;
}
return varRef.equals(((JavaVariable) o).varRef);
return varNode.equals(((JavaVariable) o).varNode);
}
}
@@ -37,6 +37,10 @@ public class ResourceFile {
private ZipRef zipRef;
private String deobfName;
public static ResourceFile createResourceFile(JadxDecompiler decompiler, File file, ResourceType type) {
return new ResourceFile(decompiler, file.getAbsolutePath(), type);
}
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) {
return null;
@@ -41,7 +41,7 @@ public enum ResourceType {
}
public static ResourceType getFileType(String fileName) {
if (fileName.matches("[^/]+/resources.pb")) {
if (fileName.endsWith("/resources.pb")) {
return ARSC;
}
int dot = fileName.lastIndexOf('.');
@@ -126,8 +126,9 @@ public final class ResourcesLoader {
if (name.endsWith(".9.png")) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
decoder.decode(inputStream, os);
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
if (decoder.decode(inputStream, os)) {
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
}
} catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
}
@@ -145,16 +146,8 @@ public final class ResourcesLoader {
return null;
});
} else {
addResourceFile(list, file);
}
}
private void addResourceFile(List<ResourceFile> list, File file) {
String name = file.getAbsolutePath();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
list.add(rf);
ResourceType type = ResourceType.getFileType(file.getAbsolutePath());
list.add(ResourceFile.createResourceFile(jadxRef, file, type));
}
}
@@ -0,0 +1,32 @@
package jadx.api.args;
public enum DeobfuscationMapFileMode {
/**
* Load if found, don't save (default)
*/
READ,
/**
* Load if found, save only if new (don't overwrite)
*/
READ_OR_SAVE,
/**
* Don't load, always save
*/
OVERWRITE,
/**
* Don't load and don't save
*/
IGNORE;
public boolean shouldRead() {
return this == READ || this == READ_OR_SAVE;
}
public boolean shouldWrite() {
return this == READ_OR_SAVE || this == OVERWRITE;
}
}
@@ -0,0 +1,22 @@
package jadx.api.args;
/**
* Resources original name source (for deobfuscation)
*/
public enum ResourceNameSource {
/**
* Automatically select best name (default)
*/
AUTO,
/**
* Force use resources provided names
*/
RESOURCES,
/**
* Force use resources names from R class
*/
CODE,
}
@@ -1,5 +0,0 @@
package jadx.api.data.annotations;
public interface ICodeRawOffset {
int getOffset();
}
@@ -1,57 +0,0 @@
package jadx.api.data.annotations;
import jadx.core.dex.attributes.ILineAttributeNode;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.nodes.MethodNode;
public class VarDeclareRef extends VarRef implements ILineAttributeNode {
public static VarDeclareRef get(MethodNode mth, CodeVar codeVar) {
VarDeclareRef ref = new VarDeclareRef(mth, codeVar);
codeVar.setCachedVarRef(ref);
return ref;
}
private int sourceLine;
private int decompiledLine;
private int defPosition;
private VarDeclareRef(MethodNode mth, CodeVar codeVar) {
super(mth, codeVar.getAnySsaVar());
}
@Override
public int getSourceLine() {
return sourceLine;
}
@Override
public void setSourceLine(int sourceLine) {
this.sourceLine = sourceLine;
}
@Override
public int getDecompiledLine() {
return decompiledLine;
}
@Override
public void setDecompiledLine(int decompiledLine) {
this.decompiledLine = decompiledLine;
}
@Override
public int getDefPosition() {
return defPosition;
}
@Override
public void setDefPosition(int pos) {
this.defPosition = pos;
}
@Override
public String toString() {
return "VarDeclareRef{r" + getReg() + 'v' + getSsa() + '}';
}
}
@@ -1,98 +0,0 @@
package jadx.api.data.annotations;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.MethodNode;
public class VarRef {
@Nullable
public static VarRef get(MethodNode mth, RegisterArg reg) {
SSAVar ssaVar = reg.getSVar();
if (ssaVar == null) {
return null;
}
CodeVar codeVar = ssaVar.getCodeVar();
VarRef cachedVarRef = codeVar.getCachedVarRef();
if (cachedVarRef != null) {
if (cachedVarRef.getName() == null) {
cachedVarRef.setName(codeVar.getName());
}
return cachedVarRef;
}
VarRef newVarRef = new VarRef(mth, ssaVar);
codeVar.setCachedVarRef(newVarRef);
return newVarRef;
}
private final MethodNode mth;
private final int reg;
private final int ssa;
private final ArgType type;
private String name;
protected VarRef(MethodNode mth, SSAVar ssaVar) {
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
}
private VarRef(MethodNode mth, int reg, int ssa, ArgType type, String name) {
this.mth = mth;
this.reg = reg;
this.ssa = ssa;
this.type = type;
this.name = name;
}
public MethodNode getMth() {
return mth;
}
public int getReg() {
return reg;
}
public int getSsa() {
return ssa;
}
public ArgType getType() {
return type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof VarRef)) {
return false;
}
VarRef other = (VarRef) o;
return getReg() == other.getReg()
&& getSsa() == other.getSsa()
&& getMth().equals(other.getMth());
}
@Override
public int hashCode() {
return 31 * getReg() + getSsa();
}
@Override
public String toString() {
return "VarUseRef{r" + reg + 'v' + ssa + '}';
}
}
@@ -3,7 +3,7 @@ package jadx.api.data.impl;
import jadx.api.JavaVariable;
import jadx.api.data.CodeRefType;
import jadx.api.data.IJavaCodeRef;
import jadx.api.data.annotations.VarRef;
import jadx.api.metadata.annotations.VarNode;
public class JadxCodeRef implements IJavaCodeRef {
@@ -23,8 +23,8 @@ public class JadxCodeRef implements IJavaCodeRef {
return forVar(javaVariable.getReg(), javaVariable.getSsa());
}
public static JadxCodeRef forVar(VarRef varRef) {
return forVar(varRef.getReg(), varRef.getSsa());
public static JadxCodeRef forVar(VarNode varNode) {
return forVar(varNode.getReg(), varNode.getSsa());
}
public static JadxCodeRef forCatch(int handlerOffset) {
@@ -2,23 +2,19 @@ package jadx.api.impl;
import java.util.Map;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeMetadata;
import jadx.api.metadata.impl.CodeMetadataStorage;
public class AnnotatedCodeInfo implements ICodeInfo {
private final String code;
private final Map<Integer, Integer> lineMapping;
private final Map<CodePosition, Object> annotations;
private final ICodeMetadata metadata;
public AnnotatedCodeInfo(ICodeInfo codeInfo) {
this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations());
}
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<CodePosition, Object> annotations) {
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<Integer, ICodeAnnotation> annotations) {
this.code = code;
this.lineMapping = lineMapping;
this.annotations = annotations;
this.metadata = CodeMetadataStorage.build(lineMapping, annotations);
}
@Override
@@ -27,13 +23,13 @@ public class AnnotatedCodeInfo implements ICodeInfo {
}
@Override
public Map<Integer, Integer> getLineMapping() {
return lineMapping;
public ICodeMetadata getCodeMetadata() {
return metadata;
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return annotations;
public boolean hasMetadata() {
return metadata != ICodeMetadata.EMPTY;
}
@Override
@@ -5,18 +5,20 @@ import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.ILineAttributeNode;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.api.metadata.annotations.VarRef;
import jadx.core.utils.StringUtils;
public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter {
private int line = 1;
private int offset;
private Map<CodePosition, Object> annotations = Collections.emptyMap();
private Map<Integer, ICodeAnnotation> annotations = Collections.emptyMap();
private Map<Integer, Integer> lineMap = Collections.emptyMap();
public AnnotatedCodeWriter() {
@@ -59,19 +61,17 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
@Override
public ICodeWriter add(ICodeWriter cw) {
if ((!(cw instanceof AnnotatedCodeWriter))) {
if (!cw.isMetadataSupported()) {
buf.append(cw.getCodeStr());
return this;
}
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
line--;
int startLine = line;
int startPos = getLength();
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
CodePosition codePos = entry.getKey();
int newLine = startLine + codePos.getLine();
int newPos = startPos + codePos.getPos();
attachAnnotation(entry.getValue(), new CodePosition(newLine, codePos.getOffset(), newPos));
for (Map.Entry<Integer, ICodeAnnotation> entry : code.annotations.entrySet()) {
int pos = entry.getKey();
int newPos = startPos + pos;
attachAnnotation(entry.getValue(), newPos);
}
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
attachSourceLine(line + entry.getKey(), entry.getValue());
@@ -101,44 +101,36 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
return line;
}
private static final class DefinitionWrapper {
private final ILineAttributeNode node;
private DefinitionWrapper(ILineAttributeNode node) {
this.node = node;
}
public ILineAttributeNode getNode() {
return node;
}
@Override
public int getLineStartPos() {
return getLength() - offset;
}
@Override
public void attachDefinition(ILineAttributeNode obj) {
public void attachDefinition(ICodeNodeRef obj) {
if (obj == null) {
return;
}
attachAnnotation(obj);
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset, getLength()));
attachAnnotation(new NodeDeclareRef(obj));
}
@Override
public void attachAnnotation(Object obj) {
public void attachAnnotation(ICodeAnnotation obj) {
if (obj == null) {
return;
}
attachAnnotation(obj, new CodePosition(line, offset + 1, getLength()));
attachAnnotation(obj, getLength());
}
@Override
public void attachLineAnnotation(Object obj) {
public void attachLineAnnotation(ICodeAnnotation obj) {
if (obj == null) {
return;
}
attachAnnotation(obj, new CodePosition(line, 0, getLength() - offset));
attachAnnotation(obj, getLineStartPos());
}
private void attachAnnotation(Object obj, CodePosition pos) {
private void attachAnnotation(ICodeAnnotation obj, int pos) {
if (annotations.isEmpty()) {
annotations = new HashMap<>();
}
@@ -164,29 +156,39 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
public ICodeInfo finish() {
removeFirstEmptyLine();
processDefinitionAnnotations();
validateAnnotations();
String code = buf.toString();
buf = null;
return new AnnotatedCodeInfo(code, lineMap, annotations);
}
@Override
public Map<CodePosition, Object> getRawAnnotations() {
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
return annotations;
}
private void processDefinitionAnnotations() {
if (!annotations.isEmpty()) {
annotations.entrySet().removeIf(entry -> {
Object v = entry.getValue();
if (v instanceof DefinitionWrapper) {
ILineAttributeNode l = ((DefinitionWrapper) v).getNode();
CodePosition codePos = entry.getKey();
l.setDecompiledLine(codePos.getLine());
l.setDefPosition(codePos.getPos());
return true;
annotations.forEach((k, v) -> {
if (v instanceof NodeDeclareRef) {
NodeDeclareRef declareRef = (NodeDeclareRef) v;
declareRef.setDefPos(k);
declareRef.getNode().setDefPosition(k);
}
return false;
});
}
}
private void validateAnnotations() {
if (annotations.isEmpty()) {
return;
}
annotations.values().removeIf(v -> {
if (v.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
VarRef varRef = (VarRef) v;
return varRef.getRefPos() == 0;
}
return false;
});
}
}
@@ -0,0 +1,49 @@
package jadx.api.impl;
import java.io.IOException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
public abstract class DelegateCodeCache implements ICodeCache {
protected final ICodeCache backCache;
public DelegateCodeCache(ICodeCache backCache) {
this.backCache = backCache;
}
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
backCache.add(clsFullName, codeInfo);
}
@Override
public void remove(String clsFullName) {
backCache.remove(clsFullName);
}
@Override
public @NotNull ICodeInfo get(String clsFullName) {
return backCache.get(clsFullName);
}
@Override
@Nullable
public String getCode(String clsFullName) {
return backCache.getCode(clsFullName);
}
@Override
public boolean contains(String clsFullName) {
return backCache.contains(clsFullName);
}
@Override
public void close() throws IOException {
backCache.close();
}
}
@@ -1,8 +1,10 @@
package jadx.api.impl;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
@@ -22,9 +24,33 @@ public class InMemoryCodeCache implements ICodeCache {
storage.remove(clsFullName);
}
@NotNull
@Override
public @Nullable ICodeInfo get(String clsFullName) {
return storage.get(clsFullName);
public ICodeInfo get(String clsFullName) {
ICodeInfo codeInfo = storage.get(clsFullName);
if (codeInfo == null) {
return ICodeInfo.EMPTY;
}
return codeInfo;
}
@Override
public @Nullable String getCode(String clsFullName) {
ICodeInfo codeInfo = storage.get(clsFullName);
if (codeInfo == null) {
return null;
}
return codeInfo.getCodeStr();
}
@Override
public boolean contains(String clsFullName) {
return storage.containsKey(clsFullName);
}
@Override
public void close() throws IOException {
storage.clear();
}
@Override
@@ -1,5 +1,6 @@
package jadx.api.impl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeCache;
@@ -7,6 +8,8 @@ import jadx.api.ICodeInfo;
public class NoOpCodeCache implements ICodeCache {
public static final NoOpCodeCache INSTANCE = new NoOpCodeCache();
@Override
public void add(String clsFullName, ICodeInfo codeInfo) {
// do nothing
@@ -18,10 +21,26 @@ public class NoOpCodeCache implements ICodeCache {
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
@NotNull
public ICodeInfo get(String clsFullName) {
return ICodeInfo.EMPTY;
}
@Override
public @Nullable String getCode(String clsFullName) {
return null;
}
@Override
public boolean contains(String clsFullName) {
return false;
}
@Override
public void close() {
// do nothing
}
@Override
public String toString() {
return "NoOpCodeCache";
@@ -1,10 +1,7 @@
package jadx.api.impl;
import java.util.Collections;
import java.util.Map;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.metadata.ICodeMetadata;
public class SimpleCodeInfo implements ICodeInfo {
@@ -20,13 +17,13 @@ public class SimpleCodeInfo implements ICodeInfo {
}
@Override
public Map<Integer, Integer> getLineMapping() {
return Collections.emptyMap();
public ICodeMetadata getCodeMetadata() {
return ICodeMetadata.EMPTY;
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return Collections.emptyMap();
public boolean hasMetadata() {
return false;
}
@Override
@@ -6,11 +6,11 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.ILineAttributeNode;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.utils.Utils;
/**
@@ -193,17 +193,22 @@ public class SimpleCodeWriter implements ICodeWriter {
}
@Override
public void attachDefinition(ILineAttributeNode obj) {
public int getLineStartPos() {
return 0;
}
@Override
public void attachDefinition(ICodeNodeRef obj) {
// no op
}
@Override
public void attachAnnotation(Object obj) {
public void attachAnnotation(ICodeAnnotation obj) {
// no op
}
@Override
public void attachLineAnnotation(Object obj) {
public void attachLineAnnotation(ICodeAnnotation obj) {
// no op
}
@@ -238,7 +243,7 @@ public class SimpleCodeWriter implements ICodeWriter {
}
@Override
public Map<CodePosition, Object> getRawAnnotations() {
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
return Collections.emptyMap();
}
@@ -0,0 +1,17 @@
package jadx.api.metadata;
public interface ICodeAnnotation {
enum AnnType {
CLASS,
FIELD,
METHOD,
VAR,
VAR_REF,
DECLARATION,
OFFSET,
END // class or method body end
}
AnnType getAnnType();
}
@@ -0,0 +1,59 @@
package jadx.api.metadata;
import java.util.Map;
import java.util.function.BiFunction;
import org.jetbrains.annotations.Nullable;
import jadx.api.metadata.impl.CodeMetadataStorage;
public interface ICodeMetadata {
ICodeMetadata EMPTY = CodeMetadataStorage.empty();
@Nullable
ICodeAnnotation getAt(int position);
@Nullable
ICodeAnnotation getClosestUp(int position);
@Nullable
ICodeAnnotation searchUp(int position, ICodeAnnotation.AnnType annType);
@Nullable
ICodeAnnotation searchUp(int position, int limitPos, ICodeAnnotation.AnnType annType);
/**
* Iterate code annotations from {@code startPos} to smaller positions.
*
* @param visitor
* return not null value to stop iterations
*/
@Nullable
<T> T searchUp(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor);
/**
* Iterate code annotations from {@code startPos} to higher positions.
*
* @param visitor
* return not null value to stop iterations
*/
@Nullable
<T> T searchDown(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor);
/**
* Get current node at position (can be enclosing class or method)
*/
@Nullable
ICodeNodeRef getNodeAt(int position);
/**
* Any definition of class or method below position
*/
@Nullable
ICodeNodeRef getNodeBelow(int position);
Map<Integer, ICodeAnnotation> getAsMap();
Map<Integer, Integer> getLineMapping();
}
@@ -0,0 +1,7 @@
package jadx.api.metadata;
public interface ICodeNodeRef extends ICodeAnnotation {
int getDefPosition();
void setDefPosition(int pos);
}
@@ -1,11 +1,12 @@
package jadx.api.data.annotations;
package jadx.api.metadata.annotations;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
import jadx.api.metadata.ICodeAnnotation;
import jadx.core.dex.nodes.InsnNode;
public class InsnCodeOffset implements ICodeRawOffset {
public class InsnCodeOffset implements ICodeAnnotation {
public static void attach(ICodeWriter code, InsnNode insn) {
if (insn == null) {
@@ -40,11 +41,15 @@ public class InsnCodeOffset implements ICodeRawOffset {
this.offset = offset;
}
@Override
public int getOffset() {
return offset;
}
@Override
public AnnType getAnnType() {
return AnnType.OFFSET;
}
@Override
public String toString() {
return "offset=" + offset;
@@ -0,0 +1,55 @@
package jadx.api.metadata.annotations;
import java.util.Objects;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
public class NodeDeclareRef implements ICodeAnnotation {
private final ICodeNodeRef node;
private int defPos;
public NodeDeclareRef(ICodeNodeRef node) {
this.node = Objects.requireNonNull(node);
}
public ICodeNodeRef getNode() {
return node;
}
public int getDefPos() {
return defPos;
}
public void setDefPos(int defPos) {
this.defPos = defPos;
}
@Override
public AnnType getAnnType() {
return AnnType.DECLARATION;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof NodeDeclareRef)) {
return false;
}
return node.equals(((NodeDeclareRef) o).node);
}
@Override
public int hashCode() {
return node.hashCode();
}
@Override
public String toString() {
return "NodeDeclareRef{" + node + '}';
}
}
@@ -0,0 +1,21 @@
package jadx.api.metadata.annotations;
import jadx.api.metadata.ICodeAnnotation;
public class NodeEnd implements ICodeAnnotation {
public static final NodeEnd VALUE = new NodeEnd();
private NodeEnd() {
}
@Override
public AnnType getAnnType() {
return AnnType.END;
}
@Override
public String toString() {
return "END";
}
}
@@ -0,0 +1,147 @@
package jadx.api.metadata.annotations;
import org.jetbrains.annotations.Nullable;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.MethodNode;
/**
* Variable info
*/
public class VarNode implements ICodeNodeRef {
@Nullable
public static VarNode get(MethodNode mth, RegisterArg reg) {
SSAVar ssaVar = reg.getSVar();
if (ssaVar == null) {
return null;
}
return get(mth, ssaVar);
}
@Nullable
public static VarNode get(MethodNode mth, CodeVar codeVar) {
return get(mth, codeVar.getAnySsaVar());
}
@Nullable
public static VarNode get(MethodNode mth, SSAVar ssaVar) {
CodeVar codeVar = ssaVar.getCodeVar();
if (codeVar.isThis()) {
return null;
}
VarNode cachedVarNode = codeVar.getCachedVarNode();
if (cachedVarNode != null) {
return cachedVarNode;
}
VarNode newVarNode = new VarNode(mth, ssaVar);
codeVar.setCachedVarNode(newVarNode);
return newVarNode;
}
@Nullable
public static ICodeAnnotation getRef(MethodNode mth, RegisterArg reg) {
VarNode varNode = get(mth, reg);
if (varNode == null) {
return null;
}
return varNode.getVarRef();
}
private final MethodNode mth;
private final int reg;
private final int ssa;
private final ArgType type;
private @Nullable String name;
private int defPos;
private final VarRef varRef;
protected VarNode(MethodNode mth, SSAVar ssaVar) {
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
}
public VarNode(MethodNode mth, int reg, int ssa, ArgType type, String name) {
this.mth = mth;
this.reg = reg;
this.ssa = ssa;
this.type = type;
this.name = name;
this.varRef = VarRef.fromVarNode(this);
}
public MethodNode getMth() {
return mth;
}
public int getReg() {
return reg;
}
public int getSsa() {
return ssa;
}
public ArgType getType() {
return type;
}
@Nullable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public VarRef getVarRef() {
return varRef;
}
@Override
public int getDefPosition() {
return defPos;
}
@Override
public void setDefPosition(int pos) {
this.defPos = pos;
}
@Override
public AnnType getAnnType() {
return AnnType.VAR;
}
@Override
public int hashCode() {
int h = 31 * getReg() + getSsa();
return 31 * h + mth.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof VarNode)) {
return false;
}
VarNode other = (VarNode) o;
return getReg() == other.getReg()
&& getSsa() == other.getSsa()
&& getMth().equals(other.getMth());
}
@Override
public String toString() {
return "VarNode{r" + reg + 'v' + ssa + '}';
}
}
@@ -0,0 +1,68 @@
package jadx.api.metadata.annotations;
import jadx.api.metadata.ICodeAnnotation;
/**
* Variable reference by position of VarNode in code metadata.
* <br>
* Because on creation position not yet known,
* VarRef created using VarNode as a source of ref pos during serialization.
* <br>
* On metadata deserialization created with ref pos directly.
*/
public abstract class VarRef implements ICodeAnnotation {
public static VarRef fromPos(int refPos) {
if (refPos == 0) {
throw new IllegalArgumentException("Zero refPos");
}
return new FixedVarRef(refPos);
}
public static VarRef fromVarNode(VarNode varNode) {
return new RelatedVarRef(varNode);
}
public abstract int getRefPos();
@Override
public AnnType getAnnType() {
return AnnType.VAR_REF;
}
public static final class FixedVarRef extends VarRef {
private final int refPos;
public FixedVarRef(int refPos) {
this.refPos = refPos;
}
@Override
public int getRefPos() {
return refPos;
}
}
public static final class RelatedVarRef extends VarRef {
private final VarNode varNode;
public RelatedVarRef(VarNode varNode) {
this.varNode = varNode;
}
@Override
public int getRefPos() {
return varNode.getDefPosition();
}
@Override
public String toString() {
return "VarRef{" + varNode + ", name=" + varNode.getName() + ", mth=" + varNode.getMth() + '}';
}
}
@Override
public String toString() {
return "VarRef{" + getRefPos() + '}';
}
}
@@ -0,0 +1,151 @@
package jadx.api.metadata.impl;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.function.BiFunction;
import org.jetbrains.annotations.Nullable;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeAnnotation.AnnType;
import jadx.api.metadata.ICodeMetadata;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.core.utils.Utils;
public class CodeMetadataStorage implements ICodeMetadata {
public static ICodeMetadata build(Map<Integer, Integer> lines, Map<Integer, ICodeAnnotation> map) {
if (map.isEmpty() && lines.isEmpty()) {
return ICodeMetadata.EMPTY;
}
Comparator<Integer> reverseCmp = Comparator.comparingInt(Integer::intValue).reversed();
NavigableMap<Integer, ICodeAnnotation> navMap = new TreeMap<>(reverseCmp);
navMap.putAll(map);
return new CodeMetadataStorage(lines, navMap);
}
public static ICodeMetadata empty() {
return new CodeMetadataStorage(Collections.emptyMap(), Collections.emptyNavigableMap());
}
private final Map<Integer, Integer> lines;
private final NavigableMap<Integer, ICodeAnnotation> navMap;
private CodeMetadataStorage(Map<Integer, Integer> lines, NavigableMap<Integer, ICodeAnnotation> navMap) {
this.lines = lines;
this.navMap = navMap;
}
@Override
public ICodeAnnotation getAt(int position) {
return navMap.get(position);
}
@Override
public @Nullable ICodeAnnotation getClosestUp(int position) {
Map.Entry<Integer, ICodeAnnotation> entryBefore = navMap.higherEntry(position);
return entryBefore != null ? entryBefore.getValue() : null;
}
@Override
public @Nullable ICodeAnnotation searchUp(int position, AnnType annType) {
for (ICodeAnnotation v : navMap.tailMap(position, true).values()) {
if (v.getAnnType() == annType) {
return v;
}
}
return null;
}
@Override
public @Nullable ICodeAnnotation searchUp(int position, int limitPos, AnnType annType) {
for (ICodeAnnotation v : navMap.subMap(position, true, limitPos, true).values()) {
if (v.getAnnType() == annType) {
return v;
}
}
return null;
}
@Override
public <T> @Nullable T searchUp(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor) {
for (Map.Entry<Integer, ICodeAnnotation> entry : navMap.tailMap(startPos, true).entrySet()) {
T value = visitor.apply(entry.getKey(), entry.getValue());
if (value != null) {
return value;
}
}
return null;
}
@Override
public <T> @Nullable T searchDown(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor) {
NavigableMap<Integer, ICodeAnnotation> map = navMap.headMap(startPos, true).descendingMap();
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
T value = visitor.apply(entry.getKey(), entry.getValue());
if (value != null) {
return value;
}
}
return null;
}
@Override
public ICodeNodeRef getNodeAt(int position) {
int nesting = 0;
for (ICodeAnnotation ann : navMap.tailMap(position, true).values()) {
switch (ann.getAnnType()) {
case END:
nesting++;
break;
case DECLARATION:
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
AnnType nodeType = node.getAnnType();
if (nodeType == AnnType.CLASS || nodeType == AnnType.METHOD) {
if (nesting == 0) {
return node;
}
nesting--;
}
break;
}
}
return null;
}
@Override
public ICodeNodeRef getNodeBelow(int position) {
for (ICodeAnnotation ann : navMap.headMap(position, true).descendingMap().values()) {
if (ann.getAnnType() == AnnType.DECLARATION) {
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
AnnType nodeType = node.getAnnType();
if (nodeType == AnnType.CLASS || nodeType == AnnType.METHOD) {
return node;
}
}
}
return null;
}
@Override
public NavigableMap<Integer, ICodeAnnotation> getAsMap() {
return navMap;
}
@Override
public Map<Integer, Integer> getLineMapping() {
return lines;
}
@Override
public String toString() {
return "CodeMetadata{\nlines=" + lines
+ "\nannotations=\n " + Utils.listToString(navMap.descendingMap().entrySet(), "\n ") + "\n}";
}
}
@@ -0,0 +1,38 @@
package jadx.api.utils;
import jadx.api.ICodeWriter;
public class CodeUtils {
public static String getLineForPos(String code, int pos) {
int start = getLineStartForPos(code, pos);
int end = getLineEndForPos(code, pos);
return code.substring(start, end);
}
public static int getLineStartForPos(String code, int pos) {
String newLine = ICodeWriter.NL;
int start = code.lastIndexOf(newLine, pos);
return start == -1 ? 0 : start + newLine.length();
}
public static int getLineEndForPos(String code, int pos) {
int end = code.indexOf(ICodeWriter.NL, pos);
return end == -1 ? code.length() : end;
}
public static int getLineNumForPos(String code, int pos) {
String newLine = ICodeWriter.NL;
int newLineLen = newLine.length();
int line = 1;
int prev = 0;
while (true) {
int next = code.indexOf(newLine, prev);
if (next >= pos) {
return line;
}
prev = next + newLineLen;
line++;
}
}
}
@@ -7,6 +7,7 @@ public class Consts {
public static final boolean DEBUG_TYPE_INFERENCE = false;
public static final boolean DEBUG_OVERLOADED_CASTS = false;
public static final boolean DEBUG_EXC_HANDLERS = false;
public static final boolean DEBUG_FINALLY = false;
public static final String CLASS_OBJECT = "java.lang.Object";
public static final String CLASS_STRING = "java.lang.String";
+92 -18
View File
@@ -12,6 +12,8 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.visitors.AnonymousClassVisitor;
import jadx.core.dex.visitors.AttachCommentsVisitor;
import jadx.core.dex.visitors.AttachMethodDetails;
import jadx.core.dex.visitors.AttachTryCatchVisitor;
@@ -31,12 +33,14 @@ import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.InlineMethods;
import jadx.core.dex.visitors.MarkMethodsForInline;
import jadx.core.dex.visitors.MethodInvokeVisitor;
import jadx.core.dex.visitors.MethodVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.MoveInlineVisitor;
import jadx.core.dex.visitors.OverrideMethodVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ProcessAnonymous;
import jadx.core.dex.visitors.ProcessInstructionsVisitor;
import jadx.core.dex.visitors.ProcessMethodsForInline;
import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.ShadowFieldVisitor;
import jadx.core.dex.visitors.SignatureProcessor;
@@ -46,6 +50,7 @@ import jadx.core.dex.visitors.blocks.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
@@ -57,8 +62,10 @@ import jadx.core.dex.visitors.rename.CodeRenameVisitor;
import jadx.core.dex.visitors.rename.RenameVisitor;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
@@ -66,21 +73,20 @@ public class Jadx {
private Jadx() {
}
static {
if (Consts.DEBUG) {
LOG.info("debug enabled");
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
switch (args.getDecompilationMode()) {
case AUTO:
case RESTRUCTURE:
return getRegionsModePasses(args);
case SIMPLE:
return getSimpleModePasses(args);
case FALLBACK:
return getFallbackPassesList();
default:
throw new JadxRuntimeException("Unknown decompilation mode: " + args.getDecompilationMode());
}
}
public static List<IDexTreeVisitor> getFallbackPassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new AttachTryCatchVisitor());
passes.add(new AttachCommentsVisitor());
passes.add(new ProcessInstructionsVisitor());
passes.add(new FallbackModeVisitor());
return passes;
}
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new SignatureProcessor());
@@ -88,15 +94,12 @@ public class Jadx {
passes.add(new RenameVisitor());
passes.add(new UsageInfoVisitor());
passes.add(new ProcessAnonymous());
passes.add(new ProcessMethodsForInline());
return passes;
}
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
if (args.isFallbackMode()) {
return getFallbackPassesList();
}
public static List<IDexTreeVisitor> getRegionsModePasses(JadxArgs args) {
List<IDexTreeVisitor> passes = new ArrayList<>();
// instructions IR
passes.add(new CheckCode());
if (args.isDebugInfo()) {
@@ -128,6 +131,10 @@ public class Jadx {
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new FinishTypeInference());
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
passes.add(new ProcessKotlinInternals());
}
passes.add(new CodeRenameVisitor());
if (args.isInlineMethods()) {
passes.add(new InlineMethods());
@@ -135,6 +142,7 @@ public class Jadx {
passes.add(new GenericTypesVisitor());
passes.add(new ShadowFieldVisitor());
passes.add(new DeboxingVisitor());
passes.add(new AnonymousClassVisitor());
passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode());
@@ -170,7 +178,73 @@ public class Jadx {
return passes;
}
public static List<IDexTreeVisitor> getSimpleModePasses(JadxArgs args) {
List<IDexTreeVisitor> passes = new ArrayList<>();
if (args.isDebugInfo()) {
passes.add(new DebugInfoAttachVisitor());
}
passes.add(new AttachTryCatchVisitor());
if (args.getCommentsLevel() != CommentsLevel.NONE) {
passes.add(new AttachCommentsVisitor());
}
passes.add(new AttachMethodDetails());
passes.add(new ProcessInstructionsVisitor());
passes.add(new BlockSplitter());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK)));
passes.add(new BlockProcessor());
passes.add(new SSATransform());
passes.add(new MoveInlineVisitor());
passes.add(new ConstructorVisitor());
passes.add(new InitCodeVariables());
passes.add(new ConstInlineVisitor());
passes.add(new TypeInferenceVisitor());
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new FinishTypeInference());
passes.add(new CodeRenameVisitor());
passes.add(new DeboxingVisitor());
passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode());
passes.add(new CodeShrinkVisitor());
passes.add(new SimplifyVisitor());
passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE)));
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump());
}
return passes;
}
public static List<IDexTreeVisitor> getFallbackPassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new AttachTryCatchVisitor());
passes.add(new AttachCommentsVisitor());
passes.add(new ProcessInstructionsVisitor());
passes.add(new FallbackModeVisitor());
return passes;
}
public static final String VERSION_DEV = "dev";
private static String version;
public static String getVersion() {
if (version == null) {
version = searchJadxVersion();
}
return version;
}
public static boolean isDevVersion() {
return getVersion().equals(VERSION_DEV);
}
private static String searchJadxVersion() {
try {
ClassLoader classLoader = Jadx.class.getClassLoader();
if (classLoader != null) {
@@ -188,6 +262,6 @@ public class Jadx {
} catch (Exception e) {
LOG.error("Can't get manifest file", e);
}
return "dev";
return VERSION_DEV;
}
}
@@ -1,18 +1,19 @@
package jadx.core;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.LoadStage;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -23,13 +24,17 @@ import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
public final class ProcessClass {
public class ProcessClass {
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
private ProcessClass() {
private final List<IDexTreeVisitor> passes;
public ProcessClass(JadxArgs args) {
this.passes = Jadx.getPassesList(args);
}
@Nullable
private static ICodeInfo process(ClassNode cls, boolean codegen) {
private ICodeInfo process(ClassNode cls, boolean codegen) {
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
// nothing to do
return null;
@@ -39,17 +44,17 @@ public final class ProcessClass {
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
cls.remove(AFlag.CLASS_DEEP_RELOAD);
cls.deepUnload();
cls.root().runPreDecompileStageForClass(cls);
cls.add(AFlag.CLASS_UNLOADED);
}
if (cls.contains(AFlag.CLASS_UNLOADED)) {
cls.remove(AFlag.CLASS_UNLOADED);
cls.root().runPreDecompileStageForClass(cls);
cls.remove(AFlag.CLASS_UNLOADED);
}
if (cls.getState() == GENERATED_AND_UNLOADED) {
// force loading code again
cls.setState(NOT_LOADED);
}
if (codegen) {
if (cls.getState() == GENERATED_AND_UNLOADED) {
// allow to run code generation again
cls.setState(NOT_LOADED);
}
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
@@ -63,7 +68,7 @@ public final class ProcessClass {
}
if (cls.getState() == LOADED) {
cls.setState(PROCESS_STARTED);
for (IDexTreeVisitor visitor : cls.root().getPasses()) {
for (IDexTreeVisitor visitor : passes) {
DepthTraversal.visit(visitor, cls);
}
cls.setState(PROCESS_COMPLETE);
@@ -88,27 +93,23 @@ public final class ProcessClass {
}
@NotNull
public static ICodeInfo generateCode(ClassNode cls) {
public ICodeInfo generateCode(ClassNode cls) {
ClassNode topParentClass = cls.getTopParentClass();
if (topParentClass != cls) {
return generateCode(topParentClass);
}
try {
Set<ClassNode> useIn = new HashSet<>(cls.getUseIn());
List<ClassNode> usedInDeps = new ArrayList<>();
for (ClassNode depCls : cls.getDependencies()) {
if (useIn.contains(depCls)) {
// postpone to resolve cross dependencies
usedInDeps.add(depCls);
} else {
process(depCls, false);
}
}
if (!usedInDeps.isEmpty()) {
// process current class before its usage
if (cls.contains(AFlag.DONT_GENERATE)) {
process(cls, false);
for (ClassNode depCls : usedInDeps) {
process(depCls, false);
return ICodeInfo.EMPTY;
}
for (ClassNode depCls : cls.getDependencies()) {
process(depCls, false);
}
if (!cls.getCodegenDeps().isEmpty()) {
process(cls, false);
for (ClassNode codegenDep : cls.getCodegenDeps()) {
process(codegenDep, false);
}
}
ICodeInfo code = process(cls, true);
@@ -120,4 +121,19 @@ public final class ProcessClass {
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
}
}
public void initPasses(RootNode root) {
for (IDexTreeVisitor pass : passes) {
try {
pass.init(root);
} catch (Exception e) {
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
}
}
}
// TODO: make passes list private and not visible
public List<IDexTreeVisitor> getPasses() {
return passes;
}
}
@@ -203,6 +203,9 @@ public class ClspGraph {
if (isNew) {
addSuperTypes(parentCls, result);
}
} else {
// parent type is unknown
result.add(parentType.getObject());
}
}
}
@@ -13,8 +13,8 @@ import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr;
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
import jadx.core.Consts;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.info.FieldInfo;
@@ -48,7 +48,7 @@ public class AnnotationGen {
add(field, code);
}
public void addForParameter(ICodeWriter code, MethodParamsAttr paramsAnnotations, int n) {
public void addForParameter(ICodeWriter code, AnnotationMethodParamsAttr paramsAnnotations, int n) {
List<AnnotationsAttr> paramList = paramsAnnotations.getParamList();
if (n >= paramList.size()) {
return;
@@ -19,6 +19,7 @@ import jadx.api.CommentsLevel;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.metadata.annotations.NodeEnd;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
@@ -170,7 +171,7 @@ public class ClassGen {
ArgType sup = cls.getSuperClass();
if (sup != null
&& !sup.equals(ArgType.OBJECT)
&& !cls.isEnum()) {
&& !cls.contains(AFlag.REMOVE_SUPER_CLASS)) {
clsCode.add("extends ");
useClass(clsCode, sup);
clsCode.add(' ');
@@ -256,6 +257,7 @@ public class ClassGen {
addInnerClsAndMethods(clsCode);
clsCode.decIndent();
clsCode.startLine('}');
clsCode.attachAnnotation(NodeEnd.VALUE);
}
private void addInnerClsAndMethods(ICodeWriter clsCode) {
@@ -285,7 +287,7 @@ public class ClassGen {
private boolean isInnerClassesPresents() {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
if (!innerCls.contains(AType.ANONYMOUS_CLASS)) {
return true;
}
}
@@ -320,19 +322,25 @@ public class ClassGen {
if (inlineAttr == null || inlineAttr.notNeeded()) {
return false;
}
if (mth.getUseIn().isEmpty()) {
mth.add(AFlag.DONT_GENERATE);
return true;
try {
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;
} catch (Exception e) {
// check failed => keep method
mth.addWarnComment("Failed to check method usage", e);
return false;
}
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() {
@@ -356,7 +364,7 @@ public class ClassGen {
badCode = false;
}
MethodGen mthGen;
if (badCode || fallback || mth.contains(AType.JADX_ERROR) || mth.getRegion() == null) {
if (badCode || fallback || mth.contains(AType.JADX_ERROR)) {
mthGen = MethodGen.getFallbackMethodGen(mth);
} else {
mthGen = new MethodGen(this, mth);
@@ -369,6 +377,7 @@ public class ClassGen {
mthGen.addInstructions(code);
code.decIndent();
code.startLine('}');
code.attachAnnotation(NodeEnd.VALUE);
}
}
@@ -459,7 +468,7 @@ public class ClassGen {
}
if (f.getCls() != null) {
code.add(' ');
new ClassGen(f.getCls(), this).addClassBody(code);
new ClassGen(f.getCls(), this).addClassBody(code, true);
}
if (it.hasNext()) {
code.add(',');
@@ -526,12 +535,42 @@ public class ClassGen {
if (outerType != null) {
useClass(code, outerType);
code.add('.');
// import not needed, force use short name
useClassShortName(code, type.getObject());
addInnerType(code, type);
return;
}
useClass(code, ClassInfo.fromType(cls.root(), type));
addGenerics(code, type);
}
private void addInnerType(ICodeWriter code, ArgType baseType) {
ArgType innerType = baseType.getInnerType();
ArgType outerType = innerType.getOuterType();
if (outerType != null) {
useClassWithShortName(code, baseType, outerType);
code.add('.');
addInnerType(code, innerType);
return;
}
useClassWithShortName(code, baseType, innerType);
}
private void useClassWithShortName(ICodeWriter code, ArgType baseType, ArgType type) {
String fullNameObj;
if (type.getObject().contains(".")) {
fullNameObj = type.getObject();
} else {
fullNameObj = baseType.getObject();
}
ClassInfo classInfo = ClassInfo.fromName(cls.root(), fullNameObj);
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
code.add(classInfo.getAliasShortName());
addGenerics(code, type);
}
private void addGenerics(ICodeWriter code, ArgType type) {
List<ArgType> generics = type.getGenericTypes();
if (generics != null) {
code.add('<');
@@ -556,15 +595,6 @@ public class ClassGen {
}
}
private void useClassShortName(ICodeWriter code, String object) {
ClassInfo classInfo = ClassInfo.fromName(cls.root(), object);
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
code.add(classInfo.getAliasShortName());
}
public void useClass(ICodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
@@ -590,7 +620,7 @@ public class ClassGen {
return fullName;
}
String shortName = extClsInfo.getAliasShortName();
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
if (useCls.equals(extClsInfo)) {
return shortName;
}
if (isClassInnerFor(useCls, extClsInfo)) {
@@ -599,16 +629,21 @@ public class ClassGen {
if (extClsInfo.isInner()) {
return expandInnerClassName(useCls, extClsInfo);
}
if (checkInnerCollision(cls.root(), useCls, extClsInfo)
|| checkInPackageCollision(cls.root(), useCls, extClsInfo)) {
return fullName;
}
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
return shortName;
}
// don't add import for top classes from 'java.lang' package (subpackages excluded)
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
return shortName;
}
// don't add import if this class from same package
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
return shortName;
}
if (searchCollision(cls.root(), useCls, extClsInfo)) {
return fullName;
}
// ignore classes from default package
if (extClsInfo.isDefaultPackage()) {
return shortName;
@@ -685,7 +720,7 @@ public class ClassGen {
return false;
}
private static boolean searchCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
private static boolean checkInnerCollision(RootNode root, @Nullable ClassInfo useCls, ClassInfo searchCls) {
if (useCls == null) {
return false;
}
@@ -702,7 +737,20 @@ public class ClassGen {
}
}
}
return searchCollision(root, useCls.getParentClass(), searchCls);
return checkInnerCollision(root, useCls.getParentClass(), searchCls);
}
/**
* Check if class with same name exists in current package
*/
private static boolean checkInPackageCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
String currentPkg = useCls.getAliasPkg();
if (currentPkg.equals(searchCls.getAliasPkg())) {
// search class already from current package
return false;
}
String shortName = searchCls.getAliasShortName();
return root.getClsp().isClsKnown(currentPkg + '.' + shortName);
}
private void insertRenameInfo(ICodeWriter code, ClassNode cls) {
@@ -1,7 +1,7 @@
package jadx.core.codegen;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import jadx.api.ICodeWriter;
@@ -23,7 +23,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public class ConditionGen extends InsnGen {
private static class CondStack {
private final Queue<IfCondition> stack = new LinkedList<>();
private final Queue<IfCondition> stack = new ArrayDeque<>();
public Queue<IfCondition> getStack() {
return stack;
@@ -11,16 +11,17 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.data.annotations.VarRef;
import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.plugins.input.data.MethodHandleType;
import jadx.core.deobf.NameMapper;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitInsnAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
@@ -37,6 +38,7 @@ import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeCustomNode;
import jadx.core.dex.instructions.InvokeCustomRawNode;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.NewArrayNode;
@@ -51,6 +53,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
@@ -108,11 +111,11 @@ public class InsnGen {
if (arg.isRegister()) {
RegisterArg reg = (RegisterArg) arg;
if (code.isMetadataSupported()) {
code.attachAnnotation(VarRef.get(mth, reg));
code.attachAnnotation(VarNode.getRef(mth, reg));
}
code.add(mgen.getNameGen().useArg(reg));
} else if (arg.isLiteral()) {
code.add(lit((LiteralArg) arg));
addLiteralArg(code, (LiteralArg) arg, flags);
} else if (arg.isInsnWrap()) {
addWrappedArg(code, (InsnWrapArg) arg, flags);
} else if (arg.isNamed()) {
@@ -122,6 +125,15 @@ public class InsnGen {
}
}
private void addLiteralArg(ICodeWriter code, LiteralArg litArg, Set<Flags> flags) {
String literalStr = lit(litArg);
if (!flags.contains(Flags.BODY_ONLY_NOWRAP) && literalStr.startsWith("-")) {
code.add('(').add(literalStr).add(')');
} else {
code.add(literalStr);
}
}
private void addWrappedArg(ICodeWriter code, InsnWrapArg arg, Set<Flags> flags) throws CodegenException {
InsnNode wrapInsn = arg.getWrapInsn();
if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
@@ -152,10 +164,18 @@ public class InsnGen {
}
useType(code, codeVar.getType());
code.add(' ');
defVar(code, codeVar);
}
/**
* Variable definition without type, only var name
*/
private void defVar(ICodeWriter code, CodeVar codeVar) {
String varName = mgen.getNameGen().assignArg(codeVar);
if (code.isMetadataSupported()) {
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
code.attachDefinition(VarNode.get(mth, codeVar));
}
code.add(mgen.getNameGen().assignArg(codeVar));
code.add(varName);
}
private String lit(LiteralArg arg) {
@@ -164,7 +184,7 @@ public class InsnGen {
private void instanceField(ICodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
ClassNode pCls = mth.getParentClass();
FieldNode fieldNode = pCls.root().deepResolveField(field);
FieldNode fieldNode = pCls.root().resolveField(field);
if (fieldNode != null) {
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
if (replace != null) {
@@ -191,7 +211,31 @@ public class InsnGen {
}
}
protected void staticField(ICodeWriter code, FieldInfo field) throws CodegenException {
FieldNode fieldNode = root.resolveField(field);
if (fieldNode != null
&& fieldNode.contains(AFlag.INLINE_INSTANCE_FIELD)
&& fieldNode.getParentClass().contains(AType.ANONYMOUS_CLASS)) {
FieldInitInsnAttr initInsnAttr = fieldNode.get(AType.FIELD_INIT_INSN);
if (initInsnAttr != null) {
InsnNode insn = initInsnAttr.getInsn();
if (insn instanceof ConstructorInsn) {
fieldNode.add(AFlag.DONT_GENERATE);
inlineAnonymousConstructor(code, fieldNode.getParentClass(), (ConstructorInsn) insn);
return;
}
}
}
makeStaticFieldAccess(code, field, fieldNode, mgen.getClassGen());
}
public static void makeStaticFieldAccess(ICodeWriter code, FieldInfo field, ClassGen clsGen) {
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
makeStaticFieldAccess(code, field, fieldNode, clsGen);
}
private static void makeStaticFieldAccess(ICodeWriter code,
FieldInfo field, @Nullable FieldNode fieldNode, ClassGen clsGen) {
ClassInfo declClass = field.getDeclClass();
// TODO
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
@@ -202,7 +246,6 @@ public class InsnGen {
}
code.add('.');
}
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field);
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
@@ -213,10 +256,6 @@ public class InsnGen {
}
}
protected void staticField(ICodeWriter code, FieldInfo field) {
makeStaticFieldAccess(code, field, mgen.getClassGen());
}
public void useClass(ICodeWriter code, ArgType type) {
mgen.getClassGen().useClass(code, type);
}
@@ -509,7 +548,7 @@ public class InsnGen {
code.add(' ');
code.add(ifInsn.getOp().getSymbol()).add(' ');
addArg(code, insn.getArg(1));
code.add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget()));
code.add(") goto ").add(MethodGen.getLabelName(ifInsn));
break;
case GOTO:
@@ -530,13 +569,24 @@ public class InsnGen {
code.add(") {");
code.incIndent();
int[] keys = sw.getKeys();
int[] targets = sw.getTargets();
for (int i = 0; i < keys.length; i++) {
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
code.add(MethodGen.getLabelName(targets[i])).add(';');
int size = keys.length;
BlockNode[] targetBlocks = sw.getTargetBlocks();
if (targetBlocks != null) {
for (int i = 0; i < size; i++) {
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
code.add(MethodGen.getLabelName(targetBlocks[i])).add(';');
}
code.startLine("default: goto ");
code.add(MethodGen.getLabelName(sw.getDefTargetBlock())).add(';');
} else {
int[] targets = sw.getTargets();
for (int i = 0; i < size; i++) {
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
code.add(MethodGen.getLabelName(targets[i])).add(';');
}
code.startLine("default: goto ");
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
}
code.startLine("default: goto ");
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
code.decIndent();
code.startLine('}');
break;
@@ -665,28 +715,34 @@ public class InsnGen {
private void makeConstructor(ConstructorInsn insn, ICodeWriter code) throws CodegenException {
ClassNode cls = mth.root().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous() && !fallback) {
cls.ensureProcessed();
inlineAnonymousConstructor(code, cls, insn);
mth.getParentClass().addInlinedClass(cls);
return;
}
if (insn.isSelf()) {
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
}
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
MethodNode refMth = callMth;
if (callMth != null) {
MethodReplaceAttr replaceAttr = callMth.get(AType.METHOD_REPLACE);
if (replaceAttr != null) {
refMth = replaceAttr.getReplaceMth();
}
}
if (insn.isSuper()) {
code.attachAnnotation(callMth);
code.attachAnnotation(refMth);
code.add("super");
} else if (insn.isThis()) {
code.attachAnnotation(callMth);
code.attachAnnotation(refMth);
code.add("this");
} else {
code.add("new ");
if (callMth == null || callMth.contains(AFlag.DONT_GENERATE)) {
if (refMth == null || refMth.contains(AFlag.DONT_GENERATE)) {
// use class reference if constructor method is missing (default constructor)
code.attachAnnotation(mth.root().resolveClass(insn.getCallMth().getDeclClass()));
} else {
code.attachAnnotation(callMth);
code.attachAnnotation(refMth);
}
mgen.getClassGen().addClsName(code, insn.getClassType());
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
@@ -710,21 +766,15 @@ public class InsnGen {
}
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
cls.ensureProcessed();
if (this.mth.getParentClass() == cls) {
cls.remove(AFlag.ANONYMOUS_CLASS);
cls.remove(AType.ANONYMOUS_CLASS);
cls.remove(AFlag.DONT_GENERATE);
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
throw new CodegenException("Anonymous inner class unlimited recursion detected."
+ " Convert class to inner: " + cls.getClassInfo().getFullName());
}
cls.add(AFlag.DONT_GENERATE);
ArgType parent;
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
} else {
parent = cls.getSuperClass();
}
ArgType parent = cls.get(AType.ANONYMOUS_CLASS).getBaseType();
// hide empty anonymous constructors
for (MethodNode ctor : cls.getMethods()) {
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
@@ -732,20 +782,31 @@ public class InsnGen {
ctor.add(AFlag.DONT_GENERATE);
}
}
code.attachDefinition(cls);
code.add("new ");
if (parent == null) {
code.add("Object");
} else {
useClass(code, parent);
}
useClass(code, parent);
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
if (callMth != null) {
// copy var names
List<RegisterArg> mthArgs = callMth.getArgRegs();
int argsCount = Math.min(insn.getArgsCount(), mthArgs.size());
for (int i = 0; i < argsCount; i++) {
InsnArg arg = insn.getArg(i);
if (arg.isRegister()) {
RegisterArg mthArg = mthArgs.get(i);
RegisterArg insnArg = (RegisterArg) arg;
mthArg.getSVar().setCodeVar(insnArg.getSVar().getCodeVar());
}
}
}
generateMethodArguments(code, insn, 0, callMth);
code.add(' ');
ClassGen classGen = new ClassGen(cls, mgen.getClassGen().getParentGen());
classGen.setOuterNameGen(mgen.getNameGen());
classGen.addClassBody(code, true);
mth.getParentClass().addInlinedClass(cls);
}
private void makeInvoke(InvokeNode insn, ICodeWriter code) throws CodegenException {
@@ -755,13 +816,25 @@ public class InsnGen {
return;
}
MethodInfo callMth = insn.getCallMth();
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
MethodNode callMthNode = mth.root().resolveMethod(callMth);
if (type == InvokeType.CUSTOM_RAW) {
makeInvokeCustomRaw((InvokeCustomRawNode) insn, callMthNode, code);
return;
}
if (insn.isPolymorphicCall()) {
// add missing cast
code.add('(');
useType(code, callMth.getReturnType());
code.add(") ");
}
int k = 0;
switch (type) {
case DIRECT:
case VIRTUAL:
case INTERFACE:
case POLYMORPHIC:
InsnArg arg = insn.getArg(0);
if (needInvokeArg(arg)) {
addArgDot(code, arg);
@@ -770,14 +843,9 @@ public class InsnGen {
break;
case SUPER:
ClassInfo superCallCls = getClassForSuperCall(code, callMth);
if (superCallCls != null) {
useClass(code, superCallCls);
code.add('.');
}
// use 'super' instead 'this' in 0 arg
code.add("super").add('.');
k++;
callSuper(code, callMth);
k++; // use 'super' instead 'this' in 0 arg
code.add('.');
break;
case STATIC:
@@ -791,13 +859,44 @@ public class InsnGen {
}
if (callMthNode != null) {
code.attachAnnotation(callMthNode);
code.add(callMthNode.getAlias());
}
if (insn.contains(AFlag.FORCE_RAW_NAME)) {
code.add(callMth.getName());
} else {
code.add(callMth.getAlias());
if (callMthNode != null) {
code.add(callMthNode.getAlias());
} else {
code.add(callMth.getAlias());
}
}
generateMethodArguments(code, insn, k, callMthNode);
}
private void makeInvokeCustomRaw(InvokeCustomRawNode insn,
@Nullable MethodNode callMthNode, ICodeWriter code) throws CodegenException {
if (isFallback()) {
code.add("call_site(");
code.incIndent();
for (EncodedValue value : insn.getCallSiteValues()) {
code.startLine(value.toString());
}
code.decIndent();
code.startLine(").invoke");
generateMethodArguments(code, insn, 0, callMthNode);
} else {
ArgType returnType = insn.getCallMth().getReturnType();
if (!returnType.isVoid()) {
code.add('(');
useType(code, returnType);
code.add(") ");
}
makeInvoke(insn.getResolveInvoke(), code);
code.add(".dynamicInvoker().invoke");
generateMethodArguments(code, insn, 0, callMthNode);
code.add(" /* invoke-custom */");
}
}
// FIXME: add 'this' for equals methods in scope
private boolean needInvokeArg(InsnArg arg) {
if (arg.isAnyThis()) {
@@ -909,15 +1008,17 @@ public class InsnGen {
code.add(", ");
}
CodeVar argCodeVar = callArgs.get(i).getSVar().getCodeVar();
code.add(nameGen.assignArg(argCodeVar));
defVar(code, argCodeVar);
}
}
// force set external arg names into call method args
int extArgsCount = customNode.getArgsCount();
int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg
int callArg = 0;
for (int i = startArg; i < extArgsCount; i++) {
RegisterArg extArg = (RegisterArg) customNode.getArg(i);
callArgs.get(i).setName(extArg.getName());
RegisterArg callRegArg = callArgs.get(callArg++);
callRegArg.getSVar().setCodeVar(extArg.getSVar().getCodeVar());
}
code.add(" -> {");
code.incIndent();
@@ -927,34 +1028,43 @@ public class InsnGen {
code.startLine('}');
}
@Nullable
private ClassInfo getClassForSuperCall(ICodeWriter code, MethodInfo callMth) {
ClassNode useCls = mth.getParentClass();
ClassInfo insnCls = useCls.getClassInfo();
ClassInfo declClass = callMth.getDeclClass();
if (insnCls.equals(declClass)) {
return null;
private void callSuper(ICodeWriter code, MethodInfo callMth) {
ClassInfo superCallCls = getClassForSuperCall(callMth);
if (superCallCls == null) {
// unknown class, add comment to keep that info
code.add("super/*").add(callMth.getDeclClass().getFullName()).add("*/");
return;
}
ClassNode topClass = useCls.getTopParentClass();
if (topClass.getClassInfo().equals(declClass)) {
return declClass;
ClassInfo curClass = mth.getParentClass().getClassInfo();
if (superCallCls.equals(curClass)) {
code.add("super");
return;
}
// search call class
ClassNode nextParent = useCls;
do {
ClassInfo nextClsInfo = nextParent.getClassInfo();
if (nextClsInfo.equals(declClass)
|| ArgType.isInstanceOf(mth.root(), nextClsInfo.getType(), declClass.getType())) {
if (nextParent == useCls) {
return null;
}
return nextClsInfo;
}
nextParent = nextParent.getParentClass();
} while (nextParent != null && nextParent != topClass);
// use custom class
useClass(code, superCallCls);
code.add(".super");
}
// search failed, just return parent class
return useCls.getParentClass().getClassInfo();
/**
* Search call class in super types of this
* and all parent classes (needed for inlined synthetic calls)
*/
@Nullable
private ClassInfo getClassForSuperCall(MethodInfo callMth) {
ArgType declClsType = callMth.getDeclClass().getType();
ClassNode parentNode = mth.getParentClass();
while (true) {
ClassInfo parentCls = parentNode.getClassInfo();
if (ArgType.isInstanceOf(root, parentCls.getType(), declClsType)) {
return parentCls;
}
ClassNode nextParent = parentNode.getParentClass();
if (nextParent == parentNode) {
// no parent, class not found
return null;
}
parentNode = nextParent;
}
}
void generateMethodArguments(ICodeWriter code, BaseInvokeNode insn, int startArgNum,
@@ -1029,7 +1139,6 @@ public class InsnGen {
} else {
condGen.wrap(code, insn.getCondition());
code.add(" ? ");
addCastIfNeeded(code, first, second);
addArg(code, first, false);
code.add(" : ");
addArg(code, second, false);
@@ -1039,33 +1148,6 @@ public class InsnGen {
}
}
private void addCastIfNeeded(ICodeWriter code, InsnArg first, InsnArg second) {
if (first.isLiteral() && second.isLiteral()) {
if (first.getType() == ArgType.BYTE) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (lit1 != Byte.MAX_VALUE && lit1 != Byte.MIN_VALUE
&& lit2 != Byte.MAX_VALUE && lit2 != Byte.MIN_VALUE) {
code.add("(byte) ");
}
} else if (first.getType() == ArgType.SHORT) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (lit1 != Short.MAX_VALUE && lit1 != Short.MIN_VALUE
&& lit2 != Short.MAX_VALUE && lit2 != Short.MIN_VALUE) {
code.add("(short) ");
}
} else if (first.getType() == ArgType.CHAR) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (!NameMapper.isPrintableChar((char) (lit1))
&& !NameMapper.isPrintableChar((char) (lit2))) {
code.add("(char) ");
}
}
}
}
private void makeArith(ArithNode insn, ICodeWriter code, Set<Flags> state) throws CodegenException {
if (insn.contains(AFlag.ARITH_ONEARG)) {
makeArithOneArg(insn, code);
@@ -6,17 +6,19 @@ import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.JadxArgs;
import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
import jadx.core.Consts;
import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag;
@@ -24,6 +26,7 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IfNode;
@@ -32,13 +35,14 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxOverflowException;
@@ -141,8 +145,9 @@ public class MethodGen {
} else {
classGen.useType(code, mth.getReturnType());
code.add(' ');
code.attachDefinition(mth);
code.add(mth.getAlias());
MethodNode defMth = getMethodForDefinition();
code.attachDefinition(defMth);
code.add(defMth.getAlias());
}
code.add('(');
@@ -175,12 +180,20 @@ public class MethodGen {
return true;
}
private MethodNode getMethodForDefinition() {
MethodReplaceAttr replaceAttr = mth.get(AType.METHOD_REPLACE);
if (replaceAttr != null) {
return replaceAttr.getReplaceMth();
}
return mth;
}
private void addOverrideAnnotation(ICodeWriter code, MethodNode mth) {
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr == null) {
return;
}
if (!overrideAttr.isAtBaseMth()) {
if (!overrideAttr.getBaseMethods().contains(mth)) {
code.startLine("@Override");
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
code.add(" // ");
@@ -195,7 +208,7 @@ public class MethodGen {
}
private void addMethodArguments(ICodeWriter code, List<RegisterArg> args) {
MethodParamsAttr paramsAnnotation = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS);
AnnotationMethodParamsAttr paramsAnnotation = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS);
int i = 0;
Iterator<RegisterArg> it = args.iterator();
while (it.hasNext()) {
@@ -238,10 +251,11 @@ public class MethodGen {
classGen.useType(code, argType);
}
code.add(' ');
if (code.isMetadataSupported() && ssaVar != null) {
code.attachDefinition(VarDeclareRef.get(mth, var));
String varName = nameGen.assignArg(var);
if (code.isMetadataSupported() && ssaVar != null /* for fallback mode */) {
code.attachDefinition(VarNode.get(mth, var));
}
code.add(nameGen.assignArg(var));
code.add(varName);
i++;
if (it.hasNext()) {
@@ -251,12 +265,28 @@ public class MethodGen {
}
public void addInstructions(ICodeWriter code) throws CodegenException {
if (mth.root().getArgs().isFallbackMode()) {
addFallbackMethodCode(code, FALLBACK_MODE);
} else if (classGen.isFallbackMode()) {
dumpInstructions(code);
} else {
addRegionInsns(code);
JadxArgs args = mth.root().getArgs();
switch (args.getDecompilationMode()) {
case AUTO:
if (classGen.isFallbackMode()) {
// TODO: try simple mode first
dumpInstructions(code);
} else {
addRegionInsns(code);
}
break;
case RESTRUCTURE:
addRegionInsns(code);
break;
case SIMPLE:
addSimpleMethodCode(code);
break;
case FALLBACK:
addFallbackMethodCode(code, FALLBACK_MODE);
break;
}
}
@@ -278,6 +308,59 @@ public class MethodGen {
}
}
private void addSimpleMethodCode(ICodeWriter code) {
if (mth.getBasicBlocks() == null) {
code.startLine("// Blocks not ready for simple mode, using fallback");
addFallbackMethodCode(code, FALLBACK_MODE);
return;
}
JadxArgs args = mth.root().getArgs();
ICodeWriter tmpCode = args.getCodeWriterProvider().apply(args);
try {
tmpCode.setIndent(code.getIndent());
generateSimpleCode(tmpCode);
code.add(tmpCode);
} catch (Exception e) {
mth.addError("Simple mode code generation failed", e);
CodeGenUtils.addError(code, "Simple mode code generation failed", e);
dumpInstructions(code);
}
}
private void generateSimpleCode(ICodeWriter code) throws CodegenException {
SimpleModeHelper helper = new SimpleModeHelper(mth);
List<BlockNode> blocks = helper.prepareBlocks();
InsnGen insnGen = new InsnGen(this, true);
for (BlockNode block : blocks) {
if (block.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (helper.isNeedStartLabel(block)) {
code.decIndent();
code.startLine(getLabelName(block)).add(':');
code.incIndent();
}
for (InsnNode insn : block.getInstructions()) {
if (!insn.contains(AFlag.DONT_GENERATE)) {
if (insn.getResult() != null) {
CodeVar codeVar = insn.getResult().getSVar().getCodeVar();
if (!codeVar.isDeclared()) {
insn.add(AFlag.DECLARE_VAR);
codeVar.setDeclared(true);
}
}
InsnCodeOffset.attach(code, insn);
insnGen.makeInsn(insn, code);
addCatchComment(code, insn, false);
CodeGenUtils.addCodeComments(code, mth, insn);
}
}
if (helper.isNeedEndGoto(block)) {
code.startLine("goto ").add(getLabelName(block.getSuccessors().get(0)));
}
}
}
public void dumpInstructions(ICodeWriter code) {
if (mth.checkCommentsLevel(CommentsLevel.ERROR)) {
code.startLine("/*");
@@ -352,60 +435,81 @@ public class MethodGen {
public static void addFallbackInsns(ICodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
int startIndent = code.getIndent();
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
MethodGen methodGen = getFallbackMethodGen(mth);
InsnGen insnGen = new InsnGen(methodGen, true);
InsnNode prevInsn = null;
for (InsnNode insn : insnArr) {
if (insn == null) {
continue;
}
if (insn.contains(AType.JADX_ERROR)) {
for (JadxError error : insn.getAll(AType.JADX_ERROR)) {
code.startLine("// ").add(error.getError());
}
continue;
methodGen.dumpInsn(code, insnGen, option, startIndent, prevInsn, insn);
prevInsn = insn;
}
}
private boolean dumpInsn(ICodeWriter code, InsnGen insnGen, FallbackOption option, int startIndent,
@Nullable InsnNode prevInsn, InsnNode insn) {
if (insn.contains(AType.JADX_ERROR)) {
for (JadxError error : insn.getAll(AType.JADX_ERROR)) {
code.startLine("// ").add(error.getError());
}
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
return true;
}
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ':');
code.incIndent();
}
if (insn.getType() == InsnType.NOP) {
return true;
}
try {
boolean escapeComment = isCommentEscapeNeeded(insn, option);
if (escapeComment) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ':');
code.startLine("*/");
code.startLine("// ");
} else {
code.startLineWithNum(insn.getSourceLine());
}
InsnCodeOffset.attach(code, insn);
RegisterArg resArg = insn.getResult();
if (resArg != null) {
ArgType varType = resArg.getInitType();
if (varType.isTypeKnown()) {
code.add(varType.toString()).add(' ');
}
}
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
if (escapeComment) {
code.startLine("/*");
code.incIndent();
}
if (insn.getType() == InsnType.NOP) {
continue;
}
try {
boolean escapeComment = isCommentEscapeNeeded(insn, option);
if (escapeComment) {
code.decIndent();
code.startLine("*/");
code.startLine("// ");
} else {
code.startLineWithNum(insn.getSourceLine());
}
InsnCodeOffset.attach(code, insn);
RegisterArg resArg = insn.getResult();
if (resArg != null) {
ArgType varType = resArg.getInitType();
if (varType.isTypeKnown()) {
code.add(varType.toString()).add(' ');
}
}
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
if (escapeComment) {
code.startLine("/*");
code.incIndent();
}
addCatchComment(code, insn, true);
CodeGenUtils.addCodeComments(code, mth, insn);
} catch (Exception e) {
LOG.debug("Error generate fallback instruction: ", e.getCause());
code.setIndent(startIndent);
code.startLine("// error: " + insn);
}
return false;
}
CatchAttr catchAttr = insn.get(AType.EXC_CATCH);
if (catchAttr != null) {
code.add(" // " + catchAttr);
}
CodeGenUtils.addCodeComments(code, mth, insn);
} catch (Exception e) {
LOG.debug("Error generate fallback instruction: ", e.getCause());
code.setIndent(startIndent);
code.startLine("// error: " + insn);
private void addCatchComment(ICodeWriter code, InsnNode insn, boolean raw) {
CatchAttr catchAttr = insn.get(AType.EXC_CATCH);
if (catchAttr == null) {
return;
}
code.add(" // Catch:");
for (ExceptionHandler handler : catchAttr.getHandlers()) {
code.add(' ');
classGen.useClass(code, handler.getArgType());
code.add(" -> ");
if (raw) {
code.add(getLabelName(handler.getHandlerOffset()));
} else {
code.add(getLabelName(handler.getHandlerBlock()));
}
prevInsn = insn;
}
}
@@ -448,7 +552,22 @@ public class MethodGen {
return new MethodGen(clsGen, mth);
}
public static String getLabelName(BlockNode block) {
return String.format("L%d", block.getId());
}
public static String getLabelName(IfNode insn) {
BlockNode thenBlock = insn.getThenBlock();
if (thenBlock != null) {
return getLabelName(thenBlock);
}
return getLabelName(insn.getTarget());
}
public static String getLabelName(int offset) {
return "L_" + InsnUtils.formatOffset(offset);
if (offset < 0) {
return String.format("LB_%x", -offset);
}
return String.format("L%x", offset);
}
}
@@ -7,6 +7,7 @@ import java.util.Set;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
@@ -162,8 +163,7 @@ public class NameGen {
InsnNode assignInsn = assignArg.getParentInsn();
if (assignInsn != null) {
String name = makeNameFromInsn(assignInsn);
if (name != null && !NameMapper.isReserved(name)) {
assignArg.setName(name);
if (name != null && NameMapper.isValidAndPrintable(name)) {
return name;
}
}
@@ -174,7 +174,7 @@ public class NameGen {
private String makeNameForType(ArgType type) {
if (type.isPrimitive()) {
return makeNameForPrimitive(type);
return type.getPrimitiveType().getShortName().toLowerCase();
}
if (type.isArray()) {
return makeNameForType(type.getArrayRootElement()) + "Arr";
@@ -182,10 +182,6 @@ public class NameGen {
return makeNameForObject(type);
}
private static String makeNameForPrimitive(ArgType type) {
return type.getPrimitiveType().getShortName().toLowerCase();
}
private String makeNameForObject(ArgType type) {
if (type.isGenericType()) {
return StringUtils.escape(type.getObject().toLowerCase());
@@ -195,19 +191,32 @@ public class NameGen {
if (alias != null) {
return alias;
}
ClassInfo extClsInfo = ClassInfo.fromType(mth.root(), type);
String shortName = extClsInfo.getShortName();
String vName = fromName(shortName);
if (vName != null) {
return vName;
}
if (shortName != null) {
return StringUtils.escape(shortName.toLowerCase());
}
return makeNameForCheckedClass(ClassInfo.fromType(mth.root(), type));
}
return StringUtils.escape(type.toString());
}
private String makeNameForCheckedClass(ClassInfo classInfo) {
String shortName = classInfo.getAliasShortName();
String vName = fromName(shortName);
if (vName != null) {
return vName;
}
String lower = StringUtils.escape(shortName.toLowerCase());
if (shortName.equals(lower)) {
return lower + "Var";
}
return lower;
}
private String makeNameForClass(ClassInfo classInfo) {
String alias = getAliasForObject(classInfo.getFullName());
if (alias != null) {
return alias;
}
return makeNameForCheckedClass(classInfo);
}
private static String fromName(String name) {
if (name == null || name.isEmpty()) {
return null;
@@ -238,7 +247,12 @@ public class NameGen {
case CONSTRUCTOR:
ConstructorInsn co = (ConstructorInsn) insn;
return makeNameForObject(co.getClassType().getType());
MethodNode callMth = mth.root().getMethodUtils().resolveMethod(co);
if (callMth != null && callMth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
// don't use name of anonymous class
return null;
}
return makeNameForClass(co.getClassType());
case ARRAY_LENGTH:
return "length";
@@ -264,18 +278,22 @@ public class NameGen {
}
private String makeNameFromInvoke(MethodInfo callMth) {
String name = callMth.getName();
String name = callMth.getAlias();
ClassInfo declClass = callMth.getDeclClass();
if ("getInstance".equals(name)) {
// e.g. Cipher.getInstance
return makeNameForClass(declClass);
}
if (name.startsWith("get") || name.startsWith("set")) {
return fromName(name.substring(3));
}
if ("iterator".equals(name)) {
return "it";
}
ArgType declType = callMth.getDeclClass().getType();
if ("toString".equals(name)) {
return makeNameForType(declType);
return makeNameForClass(declClass);
}
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
if ("forName".equals(name) && declClass.getType().equals(ArgType.CLASS)) {
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
}
if (name.startsWith("to")) {
@@ -10,8 +10,8 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.dex.attributes.AFlag;
@@ -26,6 +26,7 @@ import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IBlock;
@@ -269,7 +270,7 @@ public class RegionGen extends InsnGen {
code.startLine('}');
}
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) {
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) throws CodegenException {
if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
@@ -346,11 +347,11 @@ public class RegionGen extends InsnGen {
if (arg == null) {
code.add("unknown"); // throwing exception is too late at this point
} else if (arg instanceof RegisterArg) {
CodeVar codeVar = ((RegisterArg) arg).getSVar().getCodeVar();
SSAVar ssaVar = ((RegisterArg) arg).getSVar();
if (code.isMetadataSupported()) {
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
code.attachDefinition(VarNode.get(mth, ssaVar));
}
code.add(mgen.getNameGen().assignArg(codeVar));
code.add(mgen.getNameGen().assignArg(ssaVar.getCodeVar()));
} else if (arg instanceof NamedArg) {
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
} else {
@@ -0,0 +1,151 @@
package jadx.core.codegen;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.TargetInsnNode;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.visitors.blocks.BlockProcessor;
import jadx.core.dex.visitors.blocks.BlockSplitter;
import jadx.core.utils.BlockUtils;
public class SimpleModeHelper {
private final MethodNode mth;
private final BitSet startLabel;
private final BitSet endGoto;
public SimpleModeHelper(MethodNode mth) {
this.mth = mth;
this.startLabel = BlockUtils.newBlocksBitSet(mth);
this.endGoto = BlockUtils.newBlocksBitSet(mth);
}
public List<BlockNode> prepareBlocks() {
removeEmptyBlocks();
List<BlockNode> blocksList = getSortedBlocks();
blocksList.removeIf(b -> b.equals(mth.getEnterBlock()) || b.equals(mth.getExitBlock()));
unbindExceptionHandlers();
if (blocksList.isEmpty()) {
return Collections.emptyList();
}
@Nullable
BlockNode prev = null;
int blocksCount = blocksList.size();
for (int i = 0; i < blocksCount; i++) {
BlockNode block = blocksList.get(i);
BlockNode nextBlock = i + 1 == blocksCount ? null : blocksList.get(i + 1);
List<BlockNode> preds = block.getPredecessors();
int predsCount = preds.size();
if (predsCount > 1) {
startLabel.set(block.getId());
} else if (predsCount == 1 && prev != null) {
if (!prev.equals(preds.get(0))) {
if (!block.contains(AFlag.EXC_BOTTOM_SPLITTER)) {
startLabel.set(block.getId());
}
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) {
endGoto.set(prev.getId());
}
}
}
InsnNode lastInsn = BlockUtils.getLastInsn(block);
if (lastInsn instanceof TargetInsnNode) {
processTargetInsn(block, lastInsn, nextBlock);
}
if (block.contains(AType.EXC_HANDLER)) {
startLabel.set(block.getId());
}
if (nextBlock == null && !mth.isPreExitBlocks(block)) {
endGoto.set(block.getId());
}
prev = block;
}
if (mth.isVoidReturn()) {
int last = blocksList.size() - 1;
if (blocksList.get(last).contains(AFlag.RETURN)) {
// remove trailing return
blocksList.remove(last);
}
}
return blocksList;
}
private void removeEmptyBlocks() {
for (BlockNode block : mth.getBasicBlocks()) {
if (block.getInstructions().isEmpty()
&& block.getPredecessors().size() > 0
&& block.getSuccessors().size() == 1) {
BlockNode successor = block.getSuccessors().get(0);
List<BlockNode> predecessors = block.getPredecessors();
BlockSplitter.removeConnection(block, successor);
if (predecessors.size() == 1) {
BlockSplitter.replaceConnection(predecessors.get(0), block, successor);
} else {
for (BlockNode pred : new ArrayList<>(predecessors)) {
BlockSplitter.replaceConnection(pred, block, successor);
}
}
block.add(AFlag.REMOVE);
}
}
BlockProcessor.removeMarkedBlocks(mth);
}
private void unbindExceptionHandlers() {
if (mth.isNoExceptionHandlers()) {
return;
}
for (ExceptionHandler handler : mth.getExceptionHandlers()) {
BlockNode handlerBlock = handler.getHandlerBlock();
if (handlerBlock != null) {
BlockSplitter.removePredecessors(handlerBlock);
}
}
}
private void processTargetInsn(BlockNode block, InsnNode lastInsn, @Nullable BlockNode next) {
if (lastInsn instanceof IfNode) {
IfNode ifInsn = (IfNode) lastInsn;
BlockNode thenBlock = ifInsn.getThenBlock();
if (Objects.equals(next, thenBlock)) {
ifInsn.invertCondition();
startLabel.set(ifInsn.getThenBlock().getId());
} else {
startLabel.set(thenBlock.getId());
}
ifInsn.normalize();
} else {
for (BlockNode successor : block.getSuccessors()) {
startLabel.set(successor.getId());
}
}
}
public boolean isNeedStartLabel(BlockNode block) {
return startLabel.get(block.getId());
}
public boolean isNeedEndGoto(BlockNode block) {
return endGoto.get(block.getId());
}
// DFS sort blocks to reduce goto count
private List<BlockNode> getSortedBlocks() {
List<BlockNode> list = new ArrayList<>(mth.getBasicBlocks().size());
BlockUtils.dfsVisit(mth, list::add);
return list;
}
}
@@ -12,13 +12,13 @@ import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.SimpleCodeWriter;
import jadx.api.metadata.ICodeMetadata;
import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.core.codegen.ClassGen;
import jadx.core.codegen.MethodGen;
import jadx.core.codegen.json.cls.JsonClass;
@@ -180,24 +180,27 @@ public class JsonCodeGen {
}
String[] lines = codeStr.split(ICodeWriter.NL);
Map<Integer, Integer> lineMapping = code.getLineMapping();
Map<CodePosition, Object> annotations = code.getAnnotations();
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
ICodeMetadata metadata = code.getCodeMetadata();
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
int linesCount = lines.length;
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
int lineStartPos = 0;
int newLineLen = ICodeWriter.NL.length();
for (int i = 0; i < linesCount; i++) {
String codeLine = lines[i];
int line = i + 2;
JsonCodeLine jsonCodeLine = new JsonCodeLine();
jsonCodeLine.setCode(codeLine);
jsonCodeLine.setSourceLine(lineMapping.get(line));
Object obj = annotations.get(new CodePosition(line));
Object obj = metadata.getAt(lineStartPos);
if (obj instanceof InsnCodeOffset) {
long offset = ((InsnCodeOffset) obj).getOffset();
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
}
codeLines.add(jsonCodeLine);
lineStartPos += codeLine.length() + newLineLen;
}
return codeLines;
}
@@ -0,0 +1,24 @@
package jadx.core.deobf;
public class ClsAliasPair {
private final String pkg;
private final String name;
public ClsAliasPair(String pkg, String name) {
this.pkg = pkg;
this.name = name;
}
public String getPkg() {
return pkg;
}
public String getName() {
return name;
}
@Override
public String toString() {
return pkg + '.' + name;
}
}
@@ -12,11 +12,11 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
@@ -37,28 +37,21 @@ public class DeobfPresets {
private final Map<String, String> fldPresetMap = new HashMap<>();
private final Map<String, String> mthPresetMap = new HashMap<>();
@Nullable
public static DeobfPresets build(RootNode root) {
Path deobfMapPath = getPathDeobfMapPath(root);
if (deobfMapPath == null) {
return null;
if (root.getArgs().getDeobfuscationMapFileMode() != DeobfuscationMapFileMode.IGNORE) {
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
}
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
return new DeobfPresets(deobfMapPath);
}
@Nullable
private static Path getPathDeobfMapPath(RootNode root) {
JadxArgs jadxArgs = root.getArgs();
File deobfMapFile = jadxArgs.getDeobfuscationMapFile();
if (deobfMapFile != null) {
return deobfMapFile.toPath();
}
List<File> inputFiles = jadxArgs.getInputFiles();
if (inputFiles.isEmpty()) {
return null;
}
Path inputFilePath = inputFiles.get(0).getAbsoluteFile().toPath();
Path inputFilePath = jadxArgs.getInputFiles().get(0).toPath().toAbsolutePath();
String baseName = FileUtils.getPathBaseName(inputFilePath);
return inputFilePath.getParent().resolve(baseName + ".jobf");
}
@@ -70,9 +63,9 @@ public class DeobfPresets {
/**
* Loads deobfuscator presets
*/
public void load() {
public boolean load() {
if (!Files.exists(deobfMapFile)) {
return;
return false;
}
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
try {
@@ -106,8 +99,10 @@ public class DeobfPresets {
break;
}
}
return true;
} catch (Exception e) {
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
return false;
}
}
@@ -142,9 +137,7 @@ public class DeobfPresets {
}
Files.write(deobfMapFile, list, MAP_FILE_CHARSET,
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
if (LOG.isDebugEnabled()) {
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
}
LOG.info("Deobfuscation map file saved as: {}", deobfMapFile);
}
public String getForCls(ClassInfo cls) {
@@ -16,6 +16,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.core.dex.attributes.AFlag;
@@ -76,22 +77,25 @@ public class Deobfuscator {
}
public void execute() {
if (!args.isDeobfuscationForceSave()) {
deobfPresets.load();
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
if (args.getDeobfuscationMapFileMode().shouldRead()) {
if (deobfPresets.load()) {
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
}
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
initIndexes();
}
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
initIndexes();
}
process();
}
public void savePresets() {
DeobfuscationMapFileMode mode = args.getDeobfuscationMapFileMode();
if (!mode.shouldWrite()) {
return;
}
Path deobfMapFile = deobfPresets.getDeobfMapFile();
if (Files.exists(deobfMapFile) && !args.isDeobfuscationForceSave()) {
LOG.info("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
deobfMapFile.toAbsolutePath());
if (mode == DeobfuscationMapFileMode.READ_OR_SAVE && Files.exists(deobfMapFile)) {
return;
}
try {
@@ -112,16 +116,25 @@ public class Deobfuscator {
deobfPresets.getPkgPresetMap().put(p.getName(), p.getAlias());
}
}
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
if (deobfClsInfo.getAlias() != null) {
deobfPresets.getClsPresetMap().put(deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias());
for (ClassNode cls : root.getClasses()) {
ClassInfo classInfo = cls.getClassInfo();
if (classInfo.hasAlias()) {
deobfPresets.getClsPresetMap().put(classInfo.makeRawFullName(), classInfo.getAliasShortName());
}
for (FieldNode fld : cls.getFields()) {
FieldInfo fieldInfo = fld.getFieldInfo();
if (fieldInfo.hasAlias()) {
deobfPresets.getFldPresetMap().put(fieldInfo.getRawFullId(), fld.getAlias());
}
}
for (MethodNode mth : cls.getMethods()) {
MethodInfo methodInfo = mth.getMethodInfo();
if (methodInfo.hasAlias()) {
deobfPresets.getMthPresetMap().put(methodInfo.getRawFullId(), methodInfo.getAlias());
}
}
}
for (FieldInfo fld : fldMap.keySet()) {
deobfPresets.getFldPresetMap().put(fld.getRawFullId(), fld.getAlias());
}
for (MethodInfo mth : mthMap.keySet()) {
deobfPresets.getMthPresetMap().put(mth.getRawFullId(), mth.getAlias());
}
}
@@ -377,10 +390,10 @@ public class Deobfuscator {
String alias = null;
String pkgName = null;
if (this.parseKotlinMetadata) {
ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls);
ClsAliasPair kotlinCls = KotlinMetadataUtils.getClassAlias(cls);
if (kotlinCls != null) {
alias = prepareNameFull(kotlinCls.getShortName(), "C");
pkgName = kotlinCls.getPackage();
alias = kotlinCls.getName();
pkgName = kotlinCls.getPkg();
}
}
if (alias == null && this.useSourceNameAsAlias) {
@@ -415,7 +428,9 @@ public class Deobfuscator {
return "Enum";
}
String result = "";
if (cls.getAccessFlags().isAbstract()) {
if (cls.getAccessFlags().isInterface()) {
result += "Interface";
} else if (cls.getAccessFlags().isAbstract()) {
result += "Abstract";
}
@@ -572,6 +587,7 @@ public class Deobfuscator {
if (!pkg.hasAlias()) {
String pkgName = pkg.getName();
if ((args.isDeobfuscationOn() && shouldRename(pkgName))
&& (pkg.getParentPackage() != rootPackage || !TldHelper.contains(pkgName)) // check if first level is a valid tld
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName))
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
@@ -592,20 +608,6 @@ public class Deobfuscator {
return NameMapper.removeInvalidCharsMiddle(name);
}
private String prepareNameFull(String name, String prefix) {
if (name.length() > maxLength) {
return makeHashName(name, prefix);
}
String result = NameMapper.removeInvalidChars(name, prefix);
if (result.isEmpty()) {
return makeHashName(name, prefix);
}
if (NameMapper.isReserved(result)) {
return prefix + result;
}
return result;
}
private static String makeHashName(String name, String invalidPrefix) {
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
}
@@ -143,19 +143,15 @@ public class NameMapper {
/**
* Return modified string with removed:
* <p>
* <ul>
* <li>not printable chars (including unicode)
* <li>chars not valid for java identifier part
* </ul>
* <p>
* Note: this 'middle' method must be used with prefixed string:
* <p>
* <ul>
* <li>can leave invalid chars for java identifier start (i.e numbers)
* <li>result not checked for reserved words
* </ul>
* <p>
*/
public static String removeInvalidCharsMiddle(String name) {
if (isValidIdentifier(name) && isAllCharsPrintable(name)) {
@@ -0,0 +1,38 @@
package jadx.core.deobf;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Provides a list of all top level domains with 3 characters and less,
* so we can exclude them from deobfuscation.
*/
public class TldHelper {
private static final Set<String> TLD_SET = loadTldFile();
private static Set<String> loadTldFile() {
Set<String> tldNames = new HashSet<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(Deobfuscator.class.getResourceAsStream("tld_3.txt")))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (!line.startsWith("#") && !line.isEmpty()) {
tldNames.add(line);
}
}
return tldNames;
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load top level domain list tld_3.txt", e);
}
}
public static boolean contains(String name) {
return TLD_SET.contains(name);
}
}
@@ -21,10 +21,13 @@ public enum AFlag {
DONT_GENERATE, // process as usual, but don't output to generated code
COMMENT_OUT, // process as usual, but comment insn in generated code
REMOVE, // can be completely removed
REMOVE_SUPER_CLASS, // don't add super class
HIDDEN, // instruction used inside other instruction but not listed in args
DONT_RENAME, // do not rename during deobfuscation
FORCE_RAW_NAME, // force use of raw name instead alias
ADDED_TO_REGION,
EXC_TOP_SPLITTER,
@@ -33,8 +36,10 @@ public enum AFlag {
SKIP_FIRST_ARG,
SKIP_ARG, // skip argument in invoke call
NO_SKIP_ARGS,
ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS,
INLINE_INSTANCE_FIELD,
THIS,
SUPER,
@@ -76,8 +81,14 @@ public enum AFlag {
INCONSISTENT_CODE, // warning about incorrect decompilation
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
REQUEST_CODE_SHRINK,
RERUN_SSA_TRANSFORM,
METHOD_CANDIDATE_FOR_INLINE,
USE_LINES_HINTS, // source lines info in methods can be trusted
DISABLE_BLOCKS_LOCK,
// Class processing flags
RESTART_CODEGEN, // codegen must be executed again
RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage
@@ -2,6 +2,7 @@ package jadx.core.dex.attributes;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
@@ -10,19 +11,23 @@ import jadx.core.dex.attributes.nodes.EnumMapAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
import jadx.core.dex.attributes.nodes.InlinedAttr;
import jadx.core.dex.attributes.nodes.JadxCommentsAttr;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.attributes.nodes.SpecialEdgeAttr;
import jadx.core.dex.attributes.nodes.TmpEdgeAttr;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.trycatch.CatchAttr;
@@ -51,6 +56,8 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
public static final AType<AnonymousClassAttr> ANONYMOUS_CLASS = new AType<>();
public static final AType<InlinedAttr> INLINED = new AType<>();
// field
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
@@ -59,6 +66,8 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
// method
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
public static final AType<MethodReplaceAttr> METHOD_REPLACE = new AType<>();
public static final AType<MethodBridgeAttr> BRIDGED_BY = new AType<>();
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
@@ -72,6 +81,7 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
public static final AType<AttrList<SpecialEdgeAttr>> SPECIAL_EDGE = new AType<>();
public static final AType<TmpEdgeAttr> TMP_EDGE = new AType<>();
public static final AType<TryCatchBlockAttr> TRY_BLOCK = new AType<>();
@@ -5,10 +5,6 @@ public interface ILineAttributeNode {
void setSourceLine(int sourceLine);
int getDecompiledLine();
void setDecompiledLine(int line);
int getDefPosition();
void setDefPosition(int pos);
@@ -0,0 +1,46 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
public class AnonymousClassAttr extends PinnedAttribute {
public enum InlineType {
CONSTRUCTOR,
INSTANCE_FIELD,
}
private final ClassNode outerCls;
private final ArgType baseType;
private final InlineType inlineType;
public AnonymousClassAttr(ClassNode outerCls, ArgType baseType, InlineType inlineType) {
this.outerCls = outerCls;
this.baseType = baseType;
this.inlineType = inlineType;
}
public ClassNode getOuterCls() {
return outerCls;
}
public ArgType getBaseType() {
return baseType;
}
public InlineType getInlineType() {
return inlineType;
}
@Override
public AType<AnonymousClassAttr> getAttrType() {
return AType.ANONYMOUS_CLASS;
}
@Override
public String toString() {
return "AnonymousClass{" + outerCls + ", base: " + baseType + ", inline type: " + inlineType + '}';
}
}
@@ -0,0 +1,29 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.ClassNode;
public class InlinedAttr implements IJadxAttribute {
private final ClassNode inlineCls;
public InlinedAttr(ClassNode inlineCls) {
this.inlineCls = inlineCls;
}
public ClassNode getInlineCls() {
return inlineCls;
}
@Override
public IJadxAttrType<InlinedAttr> getAttrType() {
return AType.INLINED;
}
@Override
public String toString() {
return "INLINED: " + inlineCls;
}
}
@@ -50,11 +50,7 @@ public class JadxError implements Comparable<JadxError> {
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("JadxError: ");
if (error != null) {
str.append(error);
str.append(' ');
}
str.append("JadxError: ").append(error).append(' ');
if (cause != null) {
str.append(cause.getClass());
str.append(':');
@@ -7,21 +7,11 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
private int sourceLine;
private int decompiledLine;
// the position exactly where a node declared at in decompiled java code.
/**
* Position where a node declared at in decompiled code
*/
private int defPosition;
@Override
public int getDefPosition() {
return this.defPosition;
}
@Override
public void setDefPosition(int defPosition) {
this.defPosition = defPosition;
}
@Override
public int getSourceLine() {
return sourceLine;
@@ -33,13 +23,13 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
}
@Override
public int getDecompiledLine() {
return decompiledLine;
public int getDefPosition() {
return this.defPosition;
}
@Override
public void setDecompiledLine(int decompiledLine) {
this.decompiledLine = decompiledLine;
public void setDefPosition(int defPosition) {
this.defPosition = defPosition;
}
public void addSourceLineFrom(LineAttrNode lineAttrNode) {
@@ -50,6 +40,6 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
public void copyLines(LineAttrNode lineAttrNode) {
setSourceLine(lineAttrNode.getSourceLine());
setDecompiledLine(lineAttrNode.getDecompiledLine());
setDefPosition(lineAttrNode.getDefPosition());
}
}
@@ -1,7 +1,6 @@
package jadx.core.dex.attributes.nodes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -20,10 +19,10 @@ public class LoopInfo {
private int id;
private LoopInfo parentLoop;
public LoopInfo(BlockNode start, BlockNode end) {
public LoopInfo(BlockNode start, BlockNode end, Set<BlockNode> loopBlocks) {
this.start = start;
this.end = end;
this.loopBlocks = Collections.unmodifiableSet(BlockUtils.getAllPathsBlocks(start, end));
this.loopBlocks = loopBlocks;
}
public BlockNode getStart() {
@@ -92,6 +91,19 @@ public class LoopInfo {
this.parentLoop = parentLoop;
}
public boolean hasParent(LoopInfo searchLoop) {
LoopInfo parent = parentLoop;
while (true) {
if (parent == null) {
return false;
}
if (parent == searchLoop) {
return true;
}
parent = parent.getParentLoop();
}
}
@Override
public String toString() {
return "LOOP:" + id + ": " + start + "->" + end;
@@ -0,0 +1,28 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.MethodNode;
public class MethodBridgeAttr extends PinnedAttribute {
private final MethodNode bridgeMth;
public MethodBridgeAttr(MethodNode bridgeMth) {
this.bridgeMth = bridgeMth;
}
public MethodNode getBridgeMth() {
return bridgeMth;
}
@Override
public AType<MethodBridgeAttr> getAttrType() {
return AType.BRIDGED_BY;
}
@Override
public String toString() {
return "BRIDGED_BY: " + bridgeMth;
}
}
@@ -1,6 +1,7 @@
package jadx.core.dex.attributes.nodes;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
@@ -20,27 +21,26 @@ public class MethodOverrideAttr extends PinnedAttribute {
*/
private SortedSet<MethodNode> relatedMthNodes;
public MethodOverrideAttr(List<IMethodDetails> overrideList, SortedSet<MethodNode> relatedMthNodes) {
private Set<IMethodDetails> baseMethods;
public MethodOverrideAttr(List<IMethodDetails> overrideList, SortedSet<MethodNode> relatedMthNodes, Set<IMethodDetails> baseMethods) {
this.overrideList = overrideList;
this.relatedMthNodes = relatedMthNodes;
}
public boolean isAtBaseMth() {
return overrideList.isEmpty();
this.baseMethods = baseMethods;
}
public List<IMethodDetails> getOverrideList() {
return overrideList;
}
public void setOverrideList(List<IMethodDetails> overrideList) {
this.overrideList = overrideList;
}
public SortedSet<MethodNode> getRelatedMthNodes() {
return relatedMthNodes;
}
public Set<IMethodDetails> getBaseMethods() {
return baseMethods;
}
public void setRelatedMthNodes(SortedSet<MethodNode> relatedMthNodes) {
this.relatedMthNodes = relatedMthNodes;
}
@@ -52,6 +52,6 @@ public class MethodOverrideAttr extends PinnedAttribute {
@Override
public String toString() {
return "METHOD_OVERRIDE: " + overrideList;
return "METHOD_OVERRIDE: " + getBaseMethods();
}
}
@@ -0,0 +1,31 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.MethodNode;
/**
* Calls of method should be replaced by provided method (used for synthetic methods redirect)
*/
public class MethodReplaceAttr extends PinnedAttribute {
private final MethodNode replaceMth;
public MethodReplaceAttr(MethodNode replaceMth) {
this.replaceMth = replaceMth;
}
public MethodNode getReplaceMth() {
return replaceMth;
}
@Override
public AType<MethodReplaceAttr> getAttrType() {
return AType.METHOD_REPLACE;
}
@Override
public String toString() {
return "REPLACED_BY: " + replaceMth;
}
}
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes.nodes;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import jadx.api.ICodeWriter;
@@ -10,7 +10,7 @@ import jadx.core.dex.instructions.PhiInsn;
public class PhiListAttr implements IJadxAttribute {
private final List<PhiInsn> list = new LinkedList<>();
private final List<PhiInsn> list = new ArrayList<>();
@Override
public AType<PhiListAttr> getAttrType() {
@@ -6,6 +6,16 @@ import jadx.core.dex.attributes.AttrNode;
public class RenameReasonAttr implements IJadxAttribute {
public static RenameReasonAttr forNode(AttrNode node) {
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
if (renameReasonAttr != null) {
return renameReasonAttr;
}
RenameReasonAttr newAttr = new RenameReasonAttr();
node.addAttr(newAttr);
return newAttr;
}
private String description;
public RenameReasonAttr() {
@@ -0,0 +1,46 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrList;
import jadx.core.dex.nodes.BlockNode;
public class SpecialEdgeAttr implements IJadxAttribute {
public enum SpecialEdgeType {
BACK_EDGE,
CROSS_EDGE
}
private final SpecialEdgeType type;
private final BlockNode start;
private final BlockNode end;
public SpecialEdgeAttr(SpecialEdgeType type, BlockNode start, BlockNode end) {
this.type = type;
this.start = start;
this.end = end;
}
public SpecialEdgeType getType() {
return type;
}
public BlockNode getStart() {
return start;
}
public BlockNode getEnd() {
return end;
}
@Override
public AType<AttrList<SpecialEdgeAttr>> getAttrType() {
return AType.SPECIAL_EDGE;
}
@Override
public String toString() {
return type + ": " + start + " -> " + end;
}
}
@@ -1,5 +1,7 @@
package jadx.core.dex.info;
import org.intellij.lang.annotations.MagicConstant;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.Consts;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -20,10 +22,21 @@ public class AccessInfo {
this.type = type;
}
@MagicConstant(valuesFromClass = AccessFlags.class)
public boolean containsFlag(int flag) {
return (accFlags & flag) != 0;
}
@MagicConstant(valuesFromClass = AccessFlags.class)
public boolean containsFlags(int... flags) {
for (int flag : flags) {
if ((accFlags & flag) == 0) {
return false;
}
}
return true;
}
public AccessInfo remove(int flag) {
if (containsFlag(flag)) {
return new AccessInfo(accFlags & ~flag, type);

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