Compare commits

...

137 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
276 changed files with 10055 additions and 2412 deletions
+3 -1
View File
@@ -57,7 +57,9 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Set up JDK - name: Set up JDK
uses: oracle-actions/setup-java@v1 # set latest java version by default uses: oracle-actions/setup-java@v1
with:
release: 17
- name: Print Java version - name: Print Java version
shell: bash shell: bash
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v2
@@ -6,5 +6,5 @@ jobs:
name: "Validation" name: "Validation"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1 - uses: gradle/wrapper-validation-action@v1
+5
View File
@@ -102,6 +102,7 @@ options:
--add-debug-lines - add comments with debug line numbers if available --add-debug-lines - add comments with debug line numbers if available
--no-inline-anonymous - disable anonymous classes inline --no-inline-anonymous - disable anonymous classes inline
--no-inline-methods - disable methods inline --no-inline-methods - disable methods inline
--no-finally - don't extract finally block
--no-replace-consts - don't replace constant value with matching constant field --no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u) --escape-unicode - escape non latin characters in strings (with \u)
--respect-bytecode-access-modifiers - don't change original access modifiers --respect-bytecode-access-modifiers - don't change original access modifiers
@@ -116,6 +117,10 @@ options:
'ignore' - don't read and don't save 'ignore' - don't read and don't save
--deobf-use-sourcename - use source file name as class name alias --deobf-use-sourcename - use source file name as class name alias
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names --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 --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): --rename-flags - fix options (comma-separated list of):
'case' - fix case sensitivity issues (according to --fs-case-sensitive option), 'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
+10 -10
View File
@@ -1,6 +1,6 @@
plugins { plugins {
id 'com.github.ben-manes.versions' version '0.42.0' id 'com.github.ben-manes.versions' version '0.45.0'
id 'com.diffplug.spotless' version '6.6.1' id 'com.diffplug.spotless' version '6.13.0'
} }
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev" ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -26,18 +26,18 @@ allprojects {
} }
dependencies { dependencies {
implementation 'org.slf4j:slf4j-api:1.7.36' implementation 'org.slf4j:slf4j-api:2.0.6'
compileOnly 'org.jetbrains:annotations:23.0.0' compileOnly 'org.jetbrains:annotations:24.0.0'
testImplementation 'ch.qos.logback:logback-classic:1.2.11' testImplementation 'ch.qos.logback:logback-classic:1.3.5'
testImplementation 'org.hamcrest:hamcrest-library:2.2' testImplementation 'org.hamcrest:hamcrest-library:2.2'
testImplementation 'org.mockito:mockito-core:4.6.0' testImplementation 'org.mockito:mockito-core:4.10.0'
testImplementation 'org.assertj:assertj-core:3.22.0' testImplementation 'org.assertj:assertj-core:3.24.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
testCompileOnly 'org.jetbrains:annotations:23.0.0' testCompileOnly 'org.jetbrains:annotations:24.0.0'
} }
test { test {
Binary file not shown.
+2 -2
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda distributionSha256Sum=7ba68c54029790ab444b39d7e293d3236b2632631fb5f2e012bb28b4ff669e4b
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
Vendored
+6
View File
@@ -205,6 +205,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \ 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. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # 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 limitations under the License.
@rem @rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@@ -25,7 +25,7 @@
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 set EXIT_CODE=%ERRORLEVEL%
exit /b 1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
+1 -1
View File
@@ -11,7 +11,7 @@ dependencies {
runtimeOnly(project(':jadx-plugins:jadx-smali-input')) runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
implementation 'com.beust:jcommander:1.82' implementation 'com.beust:jcommander:1.82'
implementation 'ch.qos.logback:logback-classic:1.2.11' implementation 'ch.qos.logback:logback-classic:1.3.5'
} }
application { application {
@@ -7,6 +7,8 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -21,6 +23,7 @@ import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames; import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.args.DeobfuscationMapFileMode; import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.args.ResourceNameSource;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
@@ -88,6 +91,12 @@ public class JadxCLIArgs {
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline") @Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
protected boolean inlineMethods = true; 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") @Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
protected boolean replaceConsts = true; protected boolean replaceConsts = true;
@@ -129,6 +138,16 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names") @Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
protected boolean deobfuscationParseKotlinMetadata = false; protected boolean deobfuscationParseKotlinMetadata = false;
@Parameter(
names = { "--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( @Parameter(
names = { "--use-kotlin-methods-for-var-names" }, names = { "--use-kotlin-methods-for-var-names" },
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide", description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
@@ -173,7 +192,7 @@ public class JadxCLIArgs {
@Parameter( @Parameter(
names = { "--log-level" }, names = { "--log-level" },
description = "set log level, values: quiet, progress, error, warn, info, debug", 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; protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
@@ -262,6 +281,7 @@ public class JadxCLIArgs {
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias); args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata); args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames); args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
args.setResourceNameSource(resourceNameSource);
args.setEscapeUnicode(escapeUnicode); args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers); args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject); args.setExportAsGradleProject(exportAsGradleProject);
@@ -270,6 +290,8 @@ public class JadxCLIArgs {
args.setInsertDebugLines(addDebugLines); args.setInsertDebugLines(addDebugLines);
args.setInlineAnonymousClasses(inlineAnonymousClasses); args.setInlineAnonymousClasses(inlineAnonymousClasses);
args.setInlineMethods(inlineMethods); args.setInlineMethods(inlineMethods);
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
args.setExtractFinally(extractFinally);
args.setRenameFlags(renameFlags); args.setRenameFlags(renameFlags);
args.setFsCaseSensitive(fsCaseSensitive); args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel); args.setCommentsLevel(commentsLevel);
@@ -350,6 +372,14 @@ public class JadxCLIArgs {
return inlineMethods; return inlineMethods;
} }
public boolean isAllowInlineKotlinLambda() {
return allowInlineKotlinLambda;
}
public boolean isExtractFinally() {
return extractFinally;
}
public boolean isDeobfuscationOn() { public boolean isDeobfuscationOn() {
return deobfuscationOn; return deobfuscationOn;
} }
@@ -378,6 +408,10 @@ public class JadxCLIArgs {
return deobfuscationParseKotlinMetadata; return deobfuscationParseKotlinMetadata;
} }
public ResourceNameSource getResourceNameSource() {
return resourceNameSource;
}
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() { public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames; return useKotlinMethodsForVarNames;
} }
@@ -463,54 +497,58 @@ public class JadxCLIArgs {
} }
} }
public static class CommentsLevelConverter implements IStringConverter<CommentsLevel> { public static class CommentsLevelConverter extends BaseEnumConverter<CommentsLevel> {
@Override public CommentsLevelConverter() {
public CommentsLevel convert(String value) { super(CommentsLevel::valueOf, CommentsLevel::values);
try {
return CommentsLevel.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown comments level, possible values are: "
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
}
} }
} }
public static class UseKotlinMethodsForVarNamesConverter implements IStringConverter<UseKotlinMethodsForVarNames> { public static class UseKotlinMethodsForVarNamesConverter extends BaseEnumConverter<UseKotlinMethodsForVarNames> {
@Override public UseKotlinMethodsForVarNamesConverter() {
public UseKotlinMethodsForVarNames convert(String value) { super(UseKotlinMethodsForVarNames::valueOf, UseKotlinMethodsForVarNames::values);
try {
return UseKotlinMethodsForVarNames.valueOf(value.replace('-', '_').toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: "
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
}
} }
} }
public static class DeobfuscationMapFileModeConverter implements IStringConverter<DeobfuscationMapFileMode> { public static class DeobfuscationMapFileModeConverter extends BaseEnumConverter<DeobfuscationMapFileMode> {
@Override public DeobfuscationMapFileModeConverter() {
public DeobfuscationMapFileMode convert(String value) { super(DeobfuscationMapFileMode::valueOf, DeobfuscationMapFileMode::values);
try {
return DeobfuscationMapFileMode.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: "
+ JadxCLIArgs.enumValuesString(DeobfuscationMapFileMode.values()));
}
} }
} }
public static class DecompilationModeConverter implements IStringConverter<DecompilationMode> { 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 @Override
public DecompilationMode convert(String value) { public E convert(String value) {
try { try {
return DecompilationMode.valueOf(value.toUpperCase()); return parse.apply(stringAsEnumName(value));
} catch (Exception e) { } catch (Exception e) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: " '\'' + value + "' is unknown, possible values are: " + enumValuesString(values.get()));
+ JadxCLIArgs.enumValuesString(DecompilationMode.values()));
} }
} }
} }
@@ -520,4 +558,9 @@ public class JadxCLIArgs {
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT)) .map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
.collect(Collectors.joining(", ")); .collect(Collectors.joining(", "));
} }
private static String stringAsEnumName(String value) {
// inverse of enumValuesString conversion
return value.replace('-', '_').toUpperCase(Locale.ROOT);
}
} }
@@ -4,8 +4,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter;
import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.Logger;
@@ -119,18 +117,4 @@ public class LogHelper {
} }
return false; 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()));
}
}
}
} }
+7 -8
View File
@@ -5,12 +5,11 @@ plugins {
dependencies { dependencies {
api(project(':jadx-plugins:jadx-plugins-api')) api(project(':jadx-plugins:jadx-plugins-api'))
implementation 'com.google.code.gson:gson:2.9.0' implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
constraints { // TODO: move resources decoding to separate plugin module
// Force protobuf version to prevent Java-7 issue implementation 'com.android.tools.build:aapt2-proto:7.3.1-8691043'
implementation 'com.google.protobuf:protobuf-java:3.11.4' implementation 'com.google.protobuf:protobuf-java:3.21.12' // forcing latest version
}
testImplementation 'org.apache.commons:commons-lang3:3.12.0' testImplementation 'org.apache.commons:commons-lang3:3.12.0'
@@ -20,8 +19,8 @@ dependencies {
testRuntimeOnly(project(':jadx-plugins:jadx-java-input')) testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input')) testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
testImplementation 'org.eclipse.jdt:ecj:3.29.0' testImplementation 'org.eclipse.jdt:ecj:3.32.0'
testImplementation 'tools.profiler:async-profiler:1.8.3' testImplementation 'tools.profiler:async-profiler:2.9'
} }
test { test {
@@ -16,6 +16,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.args.DeobfuscationMapFileMode; import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.args.ResourceNameSource;
import jadx.api.data.ICodeData; import jadx.api.data.ICodeData;
import jadx.api.impl.AnnotatedCodeWriter; import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache; import jadx.api.impl.InMemoryCodeCache;
@@ -52,6 +53,7 @@ public class JadxArgs {
private boolean extractFinally = true; private boolean extractFinally = true;
private boolean inlineAnonymousClasses = true; private boolean inlineAnonymousClasses = true;
private boolean inlineMethods = true; private boolean inlineMethods = true;
private boolean allowInlineKotlinLambda = true;
private boolean skipResources = false; private boolean skipResources = false;
private boolean skipSources = false; private boolean skipSources = false;
@@ -72,6 +74,7 @@ public class JadxArgs {
private File deobfuscationMapFile = null; private File deobfuscationMapFile = null;
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ; private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
private ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
private int deobfuscationMinLength = 0; private int deobfuscationMinLength = 0;
private int deobfuscationMaxLength = Integer.MAX_VALUE; private int deobfuscationMaxLength = Integer.MAX_VALUE;
@@ -261,6 +264,14 @@ public class JadxArgs {
this.inlineMethods = inlineMethods; this.inlineMethods = inlineMethods;
} }
public boolean isAllowInlineKotlinLambda() {
return allowInlineKotlinLambda;
}
public void setAllowInlineKotlinLambda(boolean allowInlineKotlinLambda) {
this.allowInlineKotlinLambda = allowInlineKotlinLambda;
}
public boolean isExtractFinally() { public boolean isExtractFinally() {
return extractFinally; return extractFinally;
} }
@@ -369,6 +380,14 @@ public class JadxArgs {
this.deobfuscationMapFile = deobfuscationMapFile; this.deobfuscationMapFile = deobfuscationMapFile;
} }
public ResourceNameSource getResourceNameSource() {
return resourceNameSource;
}
public void setResourceNameSource(ResourceNameSource resourceNameSource) {
this.resourceNameSource = resourceNameSource;
}
public boolean isEscapeUnicode() { public boolean isEscapeUnicode() {
return escapeUnicode; return escapeUnicode;
} }
@@ -540,6 +559,7 @@ public class JadxArgs {
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
+ inlineAnonymousClasses + inlineMethods + inlineAnonymousClasses + inlineMethods
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength + deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
+ resourceNameSource
+ parseKotlinMetadata + useKotlinMethodsForVarNames + parseKotlinMetadata + useKotlinMethodsForVarNames
+ insertDebugLines + extractFinally + insertDebugLines + extractFinally
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts + debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
@@ -564,6 +584,7 @@ public class JadxArgs {
+ ", deobfuscationOn=" + deobfuscationOn + ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationMapFile=" + deobfuscationMapFile + ", deobfuscationMapFile=" + deobfuscationMapFile
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode + ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
+ ", resourceNameSource=" + resourceNameSource
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias + ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata + ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames + ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
@@ -12,7 +12,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
@@ -37,8 +36,6 @@ import jadx.api.plugins.input.data.ILoadResult;
import jadx.api.plugins.options.JadxPluginOptions; import jadx.api.plugins.options.JadxPluginOptions;
import jadx.core.Jadx; import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.InlinedAttr;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
@@ -95,10 +92,6 @@ public final class JadxDecompiler implements Closeable {
private BinaryXMLParser binaryXmlParser; private BinaryXMLParser binaryXmlParser;
private ProtoXMLParser protoXmlParser; 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();
private final List<ILoadResult> customLoads = new ArrayList<>(); private final List<ILoadResult> customLoads = new ArrayList<>();
@@ -157,10 +150,6 @@ public final class JadxDecompiler implements Closeable {
resources = null; resources = null;
binaryXmlParser = null; binaryXmlParser = null;
protoXmlParser = null; protoXmlParser = null;
classesMap.clear();
methodsMap.clear();
fieldsMap.clear();
} }
@Override @Override
@@ -320,9 +309,21 @@ public final class JadxDecompiler implements Closeable {
if (args.isSkipFilesSave()) { if (args.isSkipFilesSave()) {
return; 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()); Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
for (ResourceFile resourceFile : getResources()) { for (ResourceFile resourceFile : getResources()) {
if (resourceFile.getType() != ResourceType.ARSC ResourceType resType = resourceFile.getType();
if (resType == ResourceType.MANIFEST) {
// already processed
continue;
}
if (resType != ResourceType.ARSC
&& inputFileNames.contains(resourceFile.getOriginalName())) { && inputFileNames.contains(resourceFile.getOriginalName())) {
// ignore resource made from input file // ignore resource made from input file
continue; continue;
@@ -358,8 +359,9 @@ public final class JadxDecompiler implements Closeable {
tasks.add(() -> { tasks.add(() -> {
for (JavaClass cls : decompileBatch) { for (JavaClass cls : decompileBatch) {
try { try {
ICodeInfo code = cls.getCodeInfo(); ClassNode clsNode = cls.getClassNode();
SaveCode.save(outDir, cls.getClassNode(), code); ICodeInfo code = clsNode.getCode();
SaveCode.save(outDir, clsNode, code);
} catch (Exception e) { } catch (Exception e) {
LOG.error("Error saving class: {}", cls, e); LOG.error("Error saving class: {}", cls, e);
} }
@@ -392,7 +394,7 @@ public final class JadxDecompiler implements Closeable {
return Utils.collectionMap(root.getClasses(), this::convertClassNode); return Utils.collectionMap(root.getClasses(), this::convertClassNode);
} }
public List<ResourceFile> getResources() { public synchronized List<ResourceFile> getResources() {
if (resources == null) { if (resources == null) {
if (root == null) { if (root == null) {
return Collections.emptyList(); return Collections.emptyList();
@@ -472,47 +474,36 @@ public final class JadxDecompiler implements Closeable {
* Get JavaClass by ClassNode without loading and decompilation * Get JavaClass by ClassNode without loading and decompilation
*/ */
@ApiStatus.Internal @ApiStatus.Internal
JavaClass convertClassNode(ClassNode cls) { synchronized JavaClass convertClassNode(ClassNode cls) {
return classesMap.compute(cls, (node, prevJavaCls) -> { JavaClass javaClass = cls.getJavaNode();
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) { if (javaClass == null) {
// keep previous variable javaClass = cls.isInner()
return prevJavaCls; ? new JavaClass(cls, convertClassNode(cls.getParentClass()))
} : new JavaClass(cls, this);
if (cls.isInner()) { cls.setJavaNode(javaClass);
return new JavaClass(cls, convertClassNode(cls.getParentClass())); }
} return javaClass;
return new JavaClass(cls, this);
});
} }
@ApiStatus.Internal @ApiStatus.Internal
JavaField convertFieldNode(FieldNode field) { synchronized JavaField convertFieldNode(FieldNode fld) {
return fieldsMap.computeIfAbsent(field, fldNode -> { JavaField javaField = fld.getJavaNode();
JavaClass parentCls = convertClassNode(fldNode.getParentClass()); if (javaField == null) {
return new JavaField(parentCls, fldNode); JavaClass parentCls = convertClassNode(fld.getParentClass());
}); javaField = new JavaField(parentCls, fld);
fld.setJavaNode(javaField);
}
return javaField;
} }
@ApiStatus.Internal @ApiStatus.Internal
JavaMethod convertMethodNode(MethodNode method) { synchronized JavaMethod convertMethodNode(MethodNode mth) {
return methodsMap.computeIfAbsent(method, mthNode -> { JavaMethod javaMethod = mth.getJavaNode();
ClassNode codeCls = getCodeParentClass(mthNode.getParentClass()); if (javaMethod == null) {
return new JavaMethod(convertClassNode(codeCls), mthNode); javaMethod = new JavaMethod(convertClassNode(mth.getParentClass()), mth);
}); mth.setJavaNode(javaMethod);
}
private static ClassNode getCodeParentClass(ClassNode cls) {
ClassNode codeCls;
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
if (inlinedAttr != null) {
codeCls = inlinedAttr.getInlineCls().getTopParentClass();
} else {
codeCls = cls.getTopParentClass();
} }
if (codeCls == cls) { return javaMethod;
return codeCls;
}
return getCodeParentClass(codeCls);
} }
@Nullable @Nullable
@@ -589,14 +580,9 @@ public final class JadxDecompiler implements Closeable {
} }
} }
@Nullable
private JavaVariable resolveVarNode(VarNode varNode) { private JavaVariable resolveVarNode(VarNode varNode) {
MethodNode mthNode = varNode.getMth(); JavaMethod javaNode = convertMethodNode(varNode.getMth());
JavaMethod mth = convertMethodNode(mthNode); return new JavaVariable(javaNode, varNode);
if (mth == null) {
return null;
}
return new JavaVariable(mth, varNode);
} }
@Nullable @Nullable
+47 -19
View File
@@ -18,6 +18,7 @@ import jadx.api.metadata.ICodeNodeRef;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr; import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.InlinedAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
@@ -57,7 +58,10 @@ public final class JavaClass implements JavaNode {
} }
public @NotNull ICodeInfo getCodeInfo() { public @NotNull ICodeInfo getCodeInfo() {
load(); ICodeInfo code = load();
if (code != null) {
return code;
}
return cls.decompile(); return cls.decompile();
} }
@@ -106,19 +110,24 @@ public final class JavaClass implements JavaNode {
/** /**
* Decompile class and loads internal lists of fields, methods, etc. * Decompile class and loads internal lists of fields, methods, etc.
* Do nothing if already loaded. * Do nothing if already loaded.
*
* @return code info if decompilation was executed, null otherwise
*/ */
@Nullable private synchronized @Nullable ICodeInfo load() {
private synchronized void load() {
if (listsLoaded) { if (listsLoaded) {
return; return null;
} }
listsLoaded = true; listsLoaded = true;
JadxDecompiler rootDecompiler = getRootDecompiler();
ICodeCache codeCache = rootDecompiler.getArgs().getCodeCache(); ICodeInfo code;
if (!codeCache.contains(cls.getRawName())) { if (cls.getState().isProcessComplete()) {
cls.decompile(); // already decompiled -> class internals loaded
code = null;
} else {
code = cls.decompile();
} }
JadxDecompiler rootDecompiler = getRootDecompiler();
int inClsCount = cls.getInnerClasses().size(); int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) { if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inClsCount); List<JavaClass> list = new ArrayList<>(inClsCount);
@@ -164,6 +173,7 @@ public final class JavaClass implements JavaNode {
mths.sort(Comparator.comparing(JavaMethod::getName)); mths.sort(Comparator.comparing(JavaMethod::getName));
this.methods = Collections.unmodifiableList(mths); this.methods = Collections.unmodifiableList(mths);
} }
return code;
} }
JadxDecompiler getRootDecompiler() { JadxDecompiler getRootDecompiler() {
@@ -242,19 +252,37 @@ public final class JavaClass implements JavaNode {
return parent; return parent;
} }
@Override public JavaClass getOriginalTopParentClass() {
public JavaClass getTopParentClass() { return parent == null ? this : parent.getOriginalTopParentClass();
if (cls.contains(AType.ANONYMOUS_CLASS)) {
// moved to usage class
return getParentForAnonymousClass();
}
return parent == null ? this : parent.getTopParentClass();
} }
private JavaClass getParentForAnonymousClass() { /**
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS); * Return top parent class which contains code of this class.
ClassNode topParentClass = attr.getOuterCls().getTopParentClass(); * Code parent can be different from original parent after move or inline
return getRootDecompiler().convertClassNode(topParentClass); *
* @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());
}
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
if (inlinedAttr != null) {
return getRootDecompiler().convertClassNode(inlinedAttr.getInlineCls());
}
return parent;
} }
public AccessInfo getAccessInfo() { public AccessInfo getAccessInfo() {
@@ -4,6 +4,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import jadx.api.metadata.ICodeAnnotation; import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.annotations.VarNode; import jadx.api.metadata.annotations.VarNode;
@@ -32,7 +33,7 @@ public class JavaVariable implements JavaNode {
} }
@Override @Override
public String getName() { public @Nullable String getName() {
return varNode.getName(); return varNode.getName();
} }
@@ -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,
}
@@ -9,7 +9,8 @@ public interface ICodeAnnotation {
VAR, VAR,
VAR_REF, VAR_REF,
DECLARATION, DECLARATION,
OFFSET OFFSET,
END // class or method body end
} }
AnnType getAnnType(); AnnType getAnnType();
@@ -32,6 +32,22 @@ public class NodeDeclareRef implements ICodeAnnotation {
return AnnType.DECLARATION; 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 @Override
public String toString() { public String toString() {
return "NodeDeclareRef{" + node + '}'; 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";
}
}
@@ -6,16 +6,14 @@ import java.util.Map;
import java.util.NavigableMap; import java.util.NavigableMap;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.api.metadata.ICodeAnnotation; import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeAnnotation.AnnType;
import jadx.api.metadata.ICodeMetadata; import jadx.api.metadata.ICodeMetadata;
import jadx.api.metadata.ICodeNodeRef; import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef; import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
public class CodeMetadataStorage implements ICodeMetadata { public class CodeMetadataStorage implements ICodeMetadata {
@@ -55,7 +53,7 @@ public class CodeMetadataStorage implements ICodeMetadata {
} }
@Override @Override
public @Nullable ICodeAnnotation searchUp(int position, ICodeAnnotation.AnnType annType) { public @Nullable ICodeAnnotation searchUp(int position, AnnType annType) {
for (ICodeAnnotation v : navMap.tailMap(position, true).values()) { for (ICodeAnnotation v : navMap.tailMap(position, true).values()) {
if (v.getAnnType() == annType) { if (v.getAnnType() == annType) {
return v; return v;
@@ -65,7 +63,7 @@ public class CodeMetadataStorage implements ICodeMetadata {
} }
@Override @Override
public @Nullable ICodeAnnotation searchUp(int position, int limitPos, ICodeAnnotation.AnnType annType) { public @Nullable ICodeAnnotation searchUp(int position, int limitPos, AnnType annType) {
for (ICodeAnnotation v : navMap.subMap(position, true, limitPos, true).values()) { for (ICodeAnnotation v : navMap.subMap(position, true, limitPos, true).values()) {
if (v.getAnnType() == annType) { if (v.getAnnType() == annType) {
return v; return v;
@@ -99,28 +97,40 @@ public class CodeMetadataStorage implements ICodeMetadata {
@Override @Override
public ICodeNodeRef getNodeAt(int position) { public ICodeNodeRef getNodeAt(int position) {
return navMap.tailMap(position, true) int nesting = 0;
.values().stream() for (ICodeAnnotation ann : navMap.tailMap(position, true).values()) {
.flatMap(CodeMetadataStorage::mapEnclosingNode) switch (ann.getAnnType()) {
.findFirst().orElse(null); 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 @Override
public ICodeNodeRef getNodeBelow(int position) { public ICodeNodeRef getNodeBelow(int position) {
return navMap.headMap(position, true).descendingMap() for (ICodeAnnotation ann : navMap.headMap(position, true).descendingMap().values()) {
.values().stream() if (ann.getAnnType() == AnnType.DECLARATION) {
.flatMap(CodeMetadataStorage::mapEnclosingNode) ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
.findFirst().orElse(null); AnnType nodeType = node.getAnnType();
} if (nodeType == AnnType.CLASS || nodeType == AnnType.METHOD) {
return node;
private static Stream<ICodeNodeRef> mapEnclosingNode(ICodeAnnotation ann) { }
if (ann instanceof NodeDeclareRef) {
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
if (node instanceof ClassNode || node instanceof MethodNode) {
return Stream.of(node);
} }
} }
return Stream.empty(); return null;
} }
@Override @Override
@@ -135,7 +145,7 @@ public class CodeMetadataStorage implements ICodeMetadata {
@Override @Override
public String toString() { public String toString() {
return "CodeMetadata{lines=" + lines return "CodeMetadata{\nlines=" + lines
+ ", annotations=\n" + Utils.listToString(navMap.entrySet(), "\n") + "\n}"; + "\nannotations=\n " + Utils.listToString(navMap.descendingMap().entrySet(), "\n ") + "\n}";
} }
} }
@@ -191,7 +191,6 @@ public class Jadx {
passes.add(new ProcessInstructionsVisitor()); passes.add(new ProcessInstructionsVisitor());
passes.add(new BlockSplitter()); passes.add(new BlockSplitter());
passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK)));
if (args.isRawCFGOutput()) { if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw()); passes.add(DotGraphVisitor.dumpRaw());
} }
@@ -215,9 +214,6 @@ public class Jadx {
passes.add(new CodeShrinkVisitor()); passes.add(new CodeShrinkVisitor());
passes.add(new SimplifyVisitor()); passes.add(new SimplifyVisitor());
passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE))); passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE)));
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
if (args.isCfgOutput()) { if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump()); passes.add(DotGraphVisitor.dump());
} }
@@ -19,6 +19,7 @@ import jadx.api.CommentsLevel;
import jadx.api.ICodeInfo; import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter; import jadx.api.ICodeWriter;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.metadata.annotations.NodeEnd;
import jadx.api.plugins.input.data.AccessFlags; import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedType; import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.api.plugins.input.data.annotations.EncodedValue;
@@ -170,7 +171,7 @@ public class ClassGen {
ArgType sup = cls.getSuperClass(); ArgType sup = cls.getSuperClass();
if (sup != null if (sup != null
&& !sup.equals(ArgType.OBJECT) && !sup.equals(ArgType.OBJECT)
&& !cls.isEnum()) { && !cls.contains(AFlag.REMOVE_SUPER_CLASS)) {
clsCode.add("extends "); clsCode.add("extends ");
useClass(clsCode, sup); useClass(clsCode, sup);
clsCode.add(' '); clsCode.add(' ');
@@ -256,6 +257,7 @@ public class ClassGen {
addInnerClsAndMethods(clsCode); addInnerClsAndMethods(clsCode);
clsCode.decIndent(); clsCode.decIndent();
clsCode.startLine('}'); clsCode.startLine('}');
clsCode.attachAnnotation(NodeEnd.VALUE);
} }
private void addInnerClsAndMethods(ICodeWriter clsCode) { private void addInnerClsAndMethods(ICodeWriter clsCode) {
@@ -320,19 +322,25 @@ public class ClassGen {
if (inlineAttr == null || inlineAttr.notNeeded()) { if (inlineAttr == null || inlineAttr.notNeeded()) {
return false; return false;
} }
if (mth.getUseIn().isEmpty()) { try {
mth.add(AFlag.DONT_GENERATE); if (mth.getUseIn().isEmpty()) {
return true; 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() { private boolean isMethodsPresents() {
@@ -369,6 +377,7 @@ public class ClassGen {
mthGen.addInstructions(code); mthGen.addInstructions(code);
code.decIndent(); code.decIndent();
code.startLine('}'); code.startLine('}');
code.attachAnnotation(NodeEnd.VALUE);
} }
} }
@@ -1,7 +1,7 @@
package jadx.core.codegen; package jadx.core.codegen;
import java.util.ArrayDeque;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue; import java.util.Queue;
import jadx.api.ICodeWriter; import jadx.api.ICodeWriter;
@@ -23,7 +23,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public class ConditionGen extends InsnGen { public class ConditionGen extends InsnGen {
private static class CondStack { private static class CondStack {
private final Queue<IfCondition> stack = new LinkedList<>(); private final Queue<IfCondition> stack = new ArrayDeque<>();
public Queue<IfCondition> getStack() { public Queue<IfCondition> getStack() {
return stack; return stack;
@@ -14,8 +14,10 @@ import jadx.api.ICodeWriter;
import jadx.api.metadata.annotations.InsnCodeOffset; import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode; import jadx.api.metadata.annotations.VarNode;
import jadx.api.plugins.input.data.MethodHandleType; import jadx.api.plugins.input.data.MethodHandleType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitInsnAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.GenericInfoAttr; import jadx.core.dex.attributes.nodes.GenericInfoAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.attributes.nodes.LoopLabelAttr;
@@ -36,6 +38,7 @@ import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeCustomNode; import jadx.core.dex.instructions.InvokeCustomNode;
import jadx.core.dex.instructions.InvokeCustomRawNode;
import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType; import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.NewArrayNode; import jadx.core.dex.instructions.NewArrayNode;
@@ -208,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) { 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(); ClassInfo declClass = field.getDeclClass();
// TODO // TODO
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass); boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
@@ -219,7 +246,6 @@ public class InsnGen {
} }
code.add('.'); code.add('.');
} }
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
if (fieldNode != null) { if (fieldNode != null) {
code.attachAnnotation(fieldNode); code.attachAnnotation(fieldNode);
} }
@@ -230,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) { public void useClass(ICodeWriter code, ArgType type) {
mgen.getClassGen().useClass(code, type); mgen.getClassGen().useClass(code, type);
} }
@@ -693,9 +715,7 @@ public class InsnGen {
private void makeConstructor(ConstructorInsn insn, ICodeWriter code) throws CodegenException { private void makeConstructor(ConstructorInsn insn, ICodeWriter code) throws CodegenException {
ClassNode cls = mth.root().resolveClass(insn.getClassType()); ClassNode cls = mth.root().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous() && !fallback) { if (cls != null && cls.isAnonymous() && !fallback) {
cls.ensureProcessed();
inlineAnonymousConstructor(code, cls, insn); inlineAnonymousConstructor(code, cls, insn);
mth.getParentClass().addInlinedClass(cls);
return; return;
} }
if (insn.isSelf()) { if (insn.isSelf()) {
@@ -746,6 +766,7 @@ public class InsnGen {
} }
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException { private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
cls.ensureProcessed();
if (this.mth.getParentClass() == cls) { if (this.mth.getParentClass() == cls) {
cls.remove(AType.ANONYMOUS_CLASS); cls.remove(AType.ANONYMOUS_CLASS);
cls.remove(AFlag.DONT_GENERATE); cls.remove(AFlag.DONT_GENERATE);
@@ -761,6 +782,7 @@ public class InsnGen {
ctor.add(AFlag.DONT_GENERATE); ctor.add(AFlag.DONT_GENERATE);
} }
} }
code.attachDefinition(cls);
code.add("new "); code.add("new ");
useClass(code, parent); useClass(code, parent);
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth()); MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
@@ -783,6 +805,8 @@ public class InsnGen {
ClassGen classGen = new ClassGen(cls, mgen.getClassGen().getParentGen()); ClassGen classGen = new ClassGen(cls, mgen.getClassGen().getParentGen());
classGen.setOuterNameGen(mgen.getNameGen()); classGen.setOuterNameGen(mgen.getNameGen());
classGen.addClassBody(code, true); classGen.addClassBody(code, true);
mth.getParentClass().addInlinedClass(cls);
} }
private void makeInvoke(InvokeNode insn, ICodeWriter code) throws CodegenException { private void makeInvoke(InvokeNode insn, ICodeWriter code) throws CodegenException {
@@ -794,11 +818,23 @@ public class InsnGen {
MethodInfo callMth = insn.getCallMth(); MethodInfo callMth = insn.getCallMth();
MethodNode callMthNode = mth.root().resolveMethod(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; int k = 0;
switch (type) { switch (type) {
case DIRECT: case DIRECT:
case VIRTUAL: case VIRTUAL:
case INTERFACE: case INTERFACE:
case POLYMORPHIC:
InsnArg arg = insn.getArg(0); InsnArg arg = insn.getArg(0);
if (needInvokeArg(arg)) { if (needInvokeArg(arg)) {
addArgDot(code, arg); addArgDot(code, arg);
@@ -823,13 +859,44 @@ public class InsnGen {
} }
if (callMthNode != null) { if (callMthNode != null) {
code.attachAnnotation(callMthNode); code.attachAnnotation(callMthNode);
code.add(callMthNode.getAlias()); }
if (insn.contains(AFlag.FORCE_RAW_NAME)) {
code.add(callMth.getName());
} else { } else {
code.add(callMth.getAlias()); if (callMthNode != null) {
code.add(callMthNode.getAlias());
} else {
code.add(callMth.getAlias());
}
} }
generateMethodArguments(code, insn, k, callMthNode); 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 // FIXME: add 'this' for equals methods in scope
private boolean needInvokeArg(InsnArg arg) { private boolean needInvokeArg(InsnArg arg) {
if (arg.isAnyThis()) { if (arg.isAnyThis()) {
@@ -947,9 +1014,10 @@ public class InsnGen {
// force set external arg names into call method args // force set external arg names into call method args
int extArgsCount = customNode.getArgsCount(); int extArgsCount = customNode.getArgsCount();
int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg
int callArg = 0;
for (int i = startArg; i < extArgsCount; i++) { for (int i = startArg; i < extArgsCount; i++) {
RegisterArg extArg = (RegisterArg) customNode.getArg(i); RegisterArg extArg = (RegisterArg) customNode.getArg(i);
RegisterArg callRegArg = callArgs.get(i); RegisterArg callRegArg = callArgs.get(callArg++);
callRegArg.getSVar().setCodeVar(extArg.getSVar().getCodeVar()); callRegArg.getSVar().setCodeVar(extArg.getSVar().getCodeVar());
} }
code.add(" -> {"); code.add(" -> {");
@@ -26,6 +26,7 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.JadxError; import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JumpInfo; import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr; import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.ConstStringNode; import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IfNode;
@@ -144,8 +145,9 @@ public class MethodGen {
} else { } else {
classGen.useType(code, mth.getReturnType()); classGen.useType(code, mth.getReturnType());
code.add(' '); code.add(' ');
code.attachDefinition(mth); MethodNode defMth = getMethodForDefinition();
code.add(mth.getAlias()); code.attachDefinition(defMth);
code.add(defMth.getAlias());
} }
code.add('('); code.add('(');
@@ -178,6 +180,14 @@ public class MethodGen {
return true; 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) { private void addOverrideAnnotation(ICodeWriter code, MethodNode mth) {
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE); MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr == null) { if (overrideAttr == null) {
@@ -270,7 +270,7 @@ public class RegionGen extends InsnGen {
code.startLine('}'); 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) { if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k; FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) { if (fn.getParentClass().isEnum()) {
@@ -53,7 +53,9 @@ public class SimpleModeHelper {
startLabel.set(block.getId()); startLabel.set(block.getId());
} else if (predsCount == 1 && prev != null) { } else if (predsCount == 1 && prev != null) {
if (!prev.equals(preds.get(0))) { if (!prev.equals(preds.get(0))) {
startLabel.set(block.getId()); if (!block.contains(AFlag.EXC_BOTTOM_SPLITTER)) {
startLabel.set(block.getId());
}
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) { if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) {
endGoto.set(prev.getId()); endGoto.set(prev.getId());
} }
@@ -428,7 +428,9 @@ public class Deobfuscator {
return "Enum"; return "Enum";
} }
String result = ""; String result = "";
if (cls.getAccessFlags().isAbstract()) { if (cls.getAccessFlags().isInterface()) {
result += "Interface";
} else if (cls.getAccessFlags().isAbstract()) {
result += "Abstract"; result += "Abstract";
} }
@@ -21,10 +21,13 @@ public enum AFlag {
DONT_GENERATE, // process as usual, but don't output to generated code DONT_GENERATE, // process as usual, but don't output to generated code
COMMENT_OUT, // process as usual, but comment insn in generated code COMMENT_OUT, // process as usual, but comment insn in generated code
REMOVE, // can be completely removed REMOVE, // can be completely removed
REMOVE_SUPER_CLASS, // don't add super class
HIDDEN, // instruction used inside other instruction but not listed in args HIDDEN, // instruction used inside other instruction but not listed in args
DONT_RENAME, // do not rename during deobfuscation DONT_RENAME, // do not rename during deobfuscation
FORCE_RAW_NAME, // force use of raw name instead alias
ADDED_TO_REGION, ADDED_TO_REGION,
EXC_TOP_SPLITTER, EXC_TOP_SPLITTER,
@@ -34,7 +37,9 @@ public enum AFlag {
SKIP_FIRST_ARG, SKIP_FIRST_ARG,
SKIP_ARG, // skip argument in invoke call SKIP_ARG, // skip argument in invoke call
NO_SKIP_ARGS, NO_SKIP_ARGS,
ANONYMOUS_CONSTRUCTOR, ANONYMOUS_CONSTRUCTOR,
INLINE_INSTANCE_FIELD,
THIS, THIS,
SUPER, SUPER,
@@ -7,12 +7,19 @@ import jadx.core.dex.nodes.ClassNode;
public class AnonymousClassAttr extends PinnedAttribute { public class AnonymousClassAttr extends PinnedAttribute {
public enum InlineType {
CONSTRUCTOR,
INSTANCE_FIELD,
}
private final ClassNode outerCls; private final ClassNode outerCls;
private final ArgType baseType; private final ArgType baseType;
private final InlineType inlineType;
public AnonymousClassAttr(ClassNode outerCls, ArgType baseType) { public AnonymousClassAttr(ClassNode outerCls, ArgType baseType, InlineType inlineType) {
this.outerCls = outerCls; this.outerCls = outerCls;
this.baseType = baseType; this.baseType = baseType;
this.inlineType = inlineType;
} }
public ClassNode getOuterCls() { public ClassNode getOuterCls() {
@@ -23,6 +30,10 @@ public class AnonymousClassAttr extends PinnedAttribute {
return baseType; return baseType;
} }
public InlineType getInlineType() {
return inlineType;
}
@Override @Override
public AType<AnonymousClassAttr> getAttrType() { public AType<AnonymousClassAttr> getAttrType() {
return AType.ANONYMOUS_CLASS; return AType.ANONYMOUS_CLASS;
@@ -30,6 +41,6 @@ public class AnonymousClassAttr extends PinnedAttribute {
@Override @Override
public String toString() { public String toString() {
return "AnonymousClass{" + outerCls + ", base: " + baseType + '}'; return "AnonymousClass{" + outerCls + ", base: " + baseType + ", inline type: " + inlineType + '}';
} }
} }
@@ -91,6 +91,19 @@ public class LoopInfo {
this.parentLoop = parentLoop; 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 @Override
public String toString() { public String toString() {
return "LOOP:" + id + ": " + start + "->" + end; return "LOOP:" + id + ": " + start + "->" + end;
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes.nodes; package jadx.core.dex.attributes.nodes;
import java.util.LinkedList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import jadx.api.ICodeWriter; import jadx.api.ICodeWriter;
@@ -10,7 +10,7 @@ import jadx.core.dex.instructions.PhiInsn;
public class PhiListAttr implements IJadxAttribute { public class PhiListAttr implements IJadxAttribute {
private final List<PhiInsn> list = new LinkedList<>(); private final List<PhiInsn> list = new ArrayList<>();
@Override @Override
public AType<PhiListAttr> getAttrType() { public AType<PhiListAttr> getAttrType() {
@@ -1,5 +1,7 @@
package jadx.core.dex.info; package jadx.core.dex.info;
import org.intellij.lang.annotations.MagicConstant;
import jadx.api.plugins.input.data.AccessFlags; import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -20,10 +22,21 @@ public class AccessInfo {
this.type = type; this.type = type;
} }
@MagicConstant(valuesFromClass = AccessFlags.class)
public boolean containsFlag(int flag) { public boolean containsFlag(int flag) {
return (accFlags & flag) != 0; 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) { public AccessInfo remove(int flag) {
if (containsFlag(flag)) { if (containsFlag(flag)) {
return new AccessInfo(accFlags & ~flag, type); return new AccessInfo(accFlags & ~flag, type);
@@ -82,16 +82,24 @@ public class ConstStorage {
return; return;
} }
for (FieldNode f : staticFields) { for (FieldNode f : staticFields) {
AccessInfo accFlags = f.getAccessFlags(); Object value = getFieldConstValue(f);
if (accFlags.isStatic() && accFlags.isFinal()) { if (value != null) {
EncodedValue constVal = f.get(JadxAttrType.CONSTANT_VALUE); addConstField(cls, f, value, f.getAccessFlags().isPublic());
if (constVal != null && constVal.getValue() != null) {
addConstField(cls, f, constVal.getValue(), accFlags.isPublic());
}
} }
} }
} }
public static @Nullable Object getFieldConstValue(FieldNode fld) {
AccessInfo accFlags = fld.getAccessFlags();
if (accFlags.isStatic() && accFlags.isFinal()) {
EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null) {
return constVal.getValue();
}
}
return null;
}
public void removeForClass(ClassNode cls) { public void removeForClass(ClassNode cls) {
classes.remove(cls); classes.remove(cls);
globalValues.removeForCls(cls); globalValues.removeForCls(cls);
@@ -1,5 +1,6 @@
package jadx.core.dex.instructions; package jadx.core.dex.instructions;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -7,6 +8,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.ICodeReader; import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.IMethodProto;
import jadx.api.plugins.input.data.IMethodRef; import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.insns.InsnData; import jadx.api.plugins.input.insns.InsnData;
import jadx.api.plugins.input.insns.custom.IArrayPayload; import jadx.api.plugins.input.insns.custom.IArrayPayload;
@@ -25,6 +27,7 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.input.InsnDataUtils; import jadx.core.utils.input.InsnDataUtils;
@@ -440,6 +443,8 @@ public class InsnDecoder {
return invokeCustom(insn, false); return invokeCustom(insn, false);
case INVOKE_SPECIAL: case INVOKE_SPECIAL:
return invokeSpecial(insn); return invokeSpecial(insn);
case INVOKE_POLYMORPHIC:
return invokePolymorphic(insn, false);
case INVOKE_DIRECT_RANGE: case INVOKE_DIRECT_RANGE:
return invoke(insn, InvokeType.DIRECT, true); return invoke(insn, InvokeType.DIRECT, true);
@@ -451,6 +456,8 @@ public class InsnDecoder {
return invoke(insn, InvokeType.VIRTUAL, true); return invoke(insn, InvokeType.VIRTUAL, true);
case INVOKE_CUSTOM_RANGE: case INVOKE_CUSTOM_RANGE:
return invokeCustom(insn, true); return invokeCustom(insn, true);
case INVOKE_POLYMORPHIC_RANGE:
return invokePolymorphic(insn, true);
case NEW_INSTANCE: case NEW_INSTANCE:
ArgType clsType = ArgType.parse(insn.getIndexAsType()); ArgType clsType = ArgType.parse(insn.getIndexAsType());
@@ -581,6 +588,22 @@ public class InsnDecoder {
return InvokeCustomBuilder.build(method, insn, isRange); return InvokeCustomBuilder.build(method, insn, isRange);
} }
private InsnNode invokePolymorphic(InsnData insn, boolean isRange) {
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
if (mthRef == null) {
throw new JadxRuntimeException("Failed to load method reference for insn: " + insn);
}
MethodInfo callMth = MethodInfo.fromRef(root, mthRef);
IMethodProto proto = insn.getIndexAsProto(insn.getTarget());
// expand call args
List<ArgType> args = Utils.collectionMap(proto.getArgTypes(), ArgType::parse);
ArgType returnType = ArgType.parse(proto.getReturnType());
MethodInfo effectiveCallMth = MethodInfo.fromDetails(root, callMth.getDeclClass(),
callMth.getName(), args, returnType);
return new InvokePolymorphicNode(effectiveCallMth, insn, proto, callMth, isRange);
}
private InsnNode invokeSpecial(InsnData insn) { private InsnNode invokeSpecial(InsnData insn) {
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn); IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
if (mthRef == null) { if (mthRef == null) {
@@ -5,10 +5,15 @@ import java.util.List;
import jadx.api.plugins.input.data.ICallSite; import jadx.api.plugins.input.data.ICallSite;
import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.insns.InsnData; import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.instructions.invokedynamic.CustomLambdaCall; import jadx.core.dex.instructions.invokedynamic.CustomLambdaCall;
import jadx.core.dex.instructions.invokedynamic.CustomRawCall;
import jadx.core.dex.instructions.invokedynamic.CustomStringConcat; import jadx.core.dex.instructions.invokedynamic.CustomStringConcat;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.input.InsnDataUtils; import jadx.core.utils.input.InsnDataUtils;
@@ -28,8 +33,16 @@ public class InvokeCustomBuilder {
if (CustomStringConcat.isStringConcat(values)) { if (CustomStringConcat.isStringConcat(values)) {
return CustomStringConcat.buildStringConcat(insn, isRange, values); return CustomStringConcat.buildStringConcat(insn, isRange, values);
} }
// TODO: output raw dynamic call try {
throw new JadxRuntimeException("Failed to process invoke-custom instruction: " + callSite); return CustomRawCall.build(mth, insn, isRange, values);
} catch (Exception e) {
mth.addWarn("Failed to decode invoke-custom: \n" + Utils.listToString(values, "\n")
+ ",\n exception: " + Utils.getStackTrace(e));
InsnNode nop = new InsnNode(InsnType.NOP, 0);
nop.add(AFlag.SYNTHETIC);
nop.addAttr(AType.JADX_ERROR, new JadxError("Failed to decode invoke-custom: " + values, e));
return nop;
}
} catch (Exception e) { } catch (Exception e) {
throw new JadxRuntimeException("'invoke-custom' instruction processing error: " + e.getMessage(), e); throw new JadxRuntimeException("'invoke-custom' instruction processing error: " + e.getMessage(), e);
} }
@@ -0,0 +1,98 @@
package jadx.core.dex.instructions;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.invokedynamic.CustomRawCall;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
/**
* Information for raw invoke-custom instruction.<br>
* Output will be formatted as polymorphic call with equivalent semantic
* Contains two parts:
* - resolve: treated as additional invoke insn (uses only constant args)
* - invoke: call of resolved method (base for this invoke)
* <br>
* See {@link CustomRawCall} class for build details
*/
public class InvokeCustomRawNode extends InvokeNode {
private final InvokeNode resolve;
private List<EncodedValue> callSiteValues;
public InvokeCustomRawNode(InvokeNode resolve, MethodInfo mthInfo, InsnData insn, boolean isRange) {
super(mthInfo, insn, InvokeType.CUSTOM_RAW, false, isRange);
this.resolve = resolve;
}
public InvokeCustomRawNode(InvokeNode resolve, MethodInfo mthInfo, InvokeType invokeType, int argsCount) {
super(mthInfo, invokeType, argsCount);
this.resolve = resolve;
}
public InvokeNode getResolveInvoke() {
return resolve;
}
public void setCallSiteValues(List<EncodedValue> callSiteValues) {
this.callSiteValues = callSiteValues;
}
public List<EncodedValue> getCallSiteValues() {
return callSiteValues;
}
@Override
public InsnNode copy() {
InvokeCustomRawNode copy = new InvokeCustomRawNode(resolve, getCallMth(), getInvokeType(), getArgsCount());
copyCommonParams(copy);
copy.setCallSiteValues(callSiteValues);
return copy;
}
@Override
public boolean isStaticCall() {
return true;
}
@Override
public int getFirstArgOffset() {
return 0;
}
@Override
public @Nullable InsnArg getInstanceArg() {
return null;
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (obj instanceof InvokeCustomRawNode) {
return super.isSame(obj) && resolve.isSame(((InvokeCustomRawNode) obj).resolve);
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(InsnUtils.formatOffset(offset)).append(": INVOKE_CUSTOM ");
if (getResult() != null) {
sb.append(getResult()).append(" = ");
}
if (!appendArgs(sb)) {
sb.append('\n');
}
sb.append(" call-site: \n ").append(Utils.listToString(callSiteValues, "\n ")).append('\n');
return sb.toString();
}
}
@@ -67,6 +67,19 @@ public class InvokeNode extends BaseInvokeNode {
return type == InvokeType.STATIC; return type == InvokeType.STATIC;
} }
public boolean isPolymorphicCall() {
if (type == InvokeType.POLYMORPHIC) {
return true;
}
// java bytecode uses virtual call with modified method info
if (type == InvokeType.VIRTUAL
&& mth.getDeclClass().getFullName().equals("java.lang.invoke.MethodHandle")
&& (mth.getName().equals("invoke") || mth.getName().equals("invokeExact"))) {
return true;
}
return false;
}
public int getFirstArgOffset() { public int getFirstArgOffset() {
return type == InvokeType.STATIC ? 0 : 1; return type == InvokeType.STATIC ? 0 : 1;
} }
@@ -0,0 +1,66 @@
package jadx.core.dex.instructions;
import jadx.api.plugins.input.data.IMethodProto;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
public class InvokePolymorphicNode extends InvokeNode {
private final IMethodProto proto;
private final MethodInfo baseCallRef;
public InvokePolymorphicNode(MethodInfo callMth, InsnData insn, IMethodProto proto, MethodInfo baseRef, boolean isRange) {
super(callMth, insn, InvokeType.POLYMORPHIC, true, isRange);
this.proto = proto;
this.baseCallRef = baseRef;
}
public InvokePolymorphicNode(MethodInfo callMth, int argsCount, IMethodProto proto, MethodInfo baseRef) {
super(callMth, InvokeType.POLYMORPHIC, argsCount);
this.proto = proto;
this.baseCallRef = baseRef;
}
public IMethodProto getProto() {
return proto;
}
public MethodInfo getBaseCallRef() {
return baseCallRef;
}
@Override
public InsnNode copy() {
InvokePolymorphicNode copy = new InvokePolymorphicNode(getCallMth(), getArgsCount(), proto, baseCallRef);
copyCommonParams(copy);
return copy;
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof InvokePolymorphicNode) || !super.isSame(obj)) {
return false;
}
InvokePolymorphicNode other = (InvokePolymorphicNode) obj;
return proto.equals(other.proto);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(InsnUtils.formatOffset(offset)).append(": INVOKE_POLYMORPHIC ");
if (getResult() != null) {
sb.append(getResult()).append(" = ");
}
if (!appendArgs(sb)) {
sb.append('\n');
}
sb.append(" base: ").append(baseCallRef).append('\n');
sb.append(" proto: ").append(proto).append('\n');
return sb.toString();
}
}
@@ -8,4 +8,5 @@ public enum InvokeType {
SUPER, SUPER,
POLYMORPHIC, POLYMORPHIC,
CUSTOM, CUSTOM,
CUSTOM_RAW,
} }
@@ -264,6 +264,13 @@ public abstract class InsnArg extends Typed {
return false; return false;
} }
public boolean isSameVar(RegisterArg arg) {
if (isRegister()) {
return ((RegisterArg) this).sameRegAndSVar(arg);
}
return false;
}
protected final <T extends InsnArg> T copyCommonParams(T copy) { protected final <T extends InsnArg> T copyCommonParams(T copy) {
copy.copyAttributesFrom(this); copy.copyAttributesFrom(this);
copy.setParentInsn(parentInsn); copy.setParentInsn(parentInsn);
@@ -1,24 +1,26 @@
package jadx.core.dex.instructions.args; package jadx.core.dex.instructions.args;
public enum PrimitiveType { public enum PrimitiveType {
BOOLEAN("Z", "boolean"), BOOLEAN("Z", "boolean", ArgType.object("java.lang.Boolean")),
CHAR("C", "char"), CHAR("C", "char", ArgType.object("java.lang.Character")),
BYTE("B", "byte"), BYTE("B", "byte", ArgType.object("java.lang.Byte")),
SHORT("S", "short"), SHORT("S", "short", ArgType.object("java.lang.Short")),
INT("I", "int"), INT("I", "int", ArgType.object("java.lang.Integer")),
FLOAT("F", "float"), FLOAT("F", "float", ArgType.object("java.lang.Float")),
LONG("J", "long"), LONG("J", "long", ArgType.object("java.lang.Long")),
DOUBLE("D", "double"), DOUBLE("D", "double", ArgType.object("java.lang.Double")),
OBJECT("L", "OBJECT"), OBJECT("L", "OBJECT", ArgType.OBJECT),
ARRAY("[", "ARRAY"), ARRAY("[", "ARRAY", ArgType.OBJECT_ARRAY),
VOID("V", "void"); VOID("V", "void", ArgType.object("java.lang.Void"));
private final String shortName; private final String shortName;
private final String longName; private final String longName;
private final ArgType boxType;
PrimitiveType(String shortName, String longName) { PrimitiveType(String shortName, String longName, ArgType boxType) {
this.shortName = shortName; this.shortName = shortName;
this.longName = longName; this.longName = longName;
this.boxType = boxType;
} }
public String getShortName() { public String getShortName() {
@@ -29,6 +31,10 @@ public enum PrimitiveType {
return longName; return longName;
} }
public ArgType getBoxType() {
return boxType;
}
@Override @Override
public String toString() { public String toString() {
return longName; return longName;
@@ -8,6 +8,7 @@ import jadx.api.plugins.input.data.IMethodHandle;
import jadx.api.plugins.input.data.IMethodProto; import jadx.api.plugins.input.data.IMethodProto;
import jadx.api.plugins.input.data.IMethodRef; import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.data.MethodHandleType; import jadx.api.plugins.input.data.MethodHandleType;
import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.insns.InsnData; import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
@@ -34,18 +35,20 @@ public class CustomLambdaCall {
if (values.size() < 6) { if (values.size() < 6) {
return false; return false;
} }
IMethodHandle methodHandle = (IMethodHandle) values.get(0).getValue(); EncodedValue mthRef = values.get(0);
if (mthRef.getType() != EncodedType.ENCODED_METHOD_HANDLE) {
return false;
}
IMethodHandle methodHandle = (IMethodHandle) mthRef.getValue();
if (methodHandle.getType() != MethodHandleType.INVOKE_STATIC) { if (methodHandle.getType() != MethodHandleType.INVOKE_STATIC) {
return false; return false;
} }
IMethodRef methodRef = methodHandle.getMethodRef(); IMethodRef methodRef = methodHandle.getMethodRef();
if (!methodRef.getName().equals("metafactory")) {
return false;
}
if (!methodRef.getParentClassType().equals("Ljava/lang/invoke/LambdaMetafactory;")) { if (!methodRef.getParentClassType().equals("Ljava/lang/invoke/LambdaMetafactory;")) {
return false; return false;
} }
return true; String mthName = methodRef.getName();
return mthName.equals("metafactory") || mthName.equals("altMetafactory");
} }
public static InvokeCustomNode buildLambdaMethodCall(MethodNode mth, InsnData insn, boolean isRange, List<EncodedValue> values) { public static InvokeCustomNode buildLambdaMethodCall(MethodNode mth, InsnData insn, boolean isRange, List<EncodedValue> values) {
@@ -115,7 +118,7 @@ public class CustomLambdaCall {
@NotNull @NotNull
private static InvokeNode buildInvokeNode(MethodHandleType methodHandleType, InvokeCustomNode invokeCustomNode, private static InvokeNode buildInvokeNode(MethodHandleType methodHandleType, InvokeCustomNode invokeCustomNode,
MethodInfo callMthInfo) { MethodInfo callMthInfo) {
InvokeType invokeType = convertInvokeType(methodHandleType); InvokeType invokeType = InvokeCustomUtils.convertInvokeType(methodHandleType);
int callArgsCount = callMthInfo.getArgsCount(); int callArgsCount = callMthInfo.getArgsCount();
boolean instanceCall = invokeType != InvokeType.STATIC; boolean instanceCall = invokeType != InvokeType.STATIC;
if (instanceCall) { if (instanceCall) {
@@ -149,21 +152,4 @@ public class CustomLambdaCall {
} }
return invokeNode; return invokeNode;
} }
private static InvokeType convertInvokeType(MethodHandleType type) {
switch (type) {
case INVOKE_STATIC:
return InvokeType.STATIC;
case INVOKE_INSTANCE:
return InvokeType.VIRTUAL;
case INVOKE_DIRECT:
case INVOKE_CONSTRUCTOR:
return InvokeType.DIRECT;
case INVOKE_INTERFACE:
return InvokeType.INTERFACE;
default:
throw new JadxRuntimeException("Unsupported method handle type: " + type);
}
}
} }
@@ -0,0 +1,70 @@
package jadx.core.dex.instructions.invokedynamic;
import java.util.ArrayList;
import java.util.List;
import jadx.api.plugins.input.data.IMethodHandle;
import jadx.api.plugins.input.data.IMethodProto;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.InvokeCustomRawNode;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.utils.EncodedValueUtils.buildLookupArg;
import static jadx.core.utils.EncodedValueUtils.convertToInsnArg;
/**
* Show `invoke-custom` similar to polymorphic call
*/
public class CustomRawCall {
public static InsnNode build(MethodNode mth, InsnData insn, boolean isRange, List<EncodedValue> values) {
IMethodHandle resolveHandle = (IMethodHandle) values.get(0).getValue();
String invokeName = (String) values.get(1).getValue();
IMethodProto invokeProto = (IMethodProto) values.get(2).getValue();
List<InsnArg> resolveArgs = buildArgs(mth, values);
if (resolveHandle.getType().isField()) {
throw new JadxRuntimeException("Field handle not yet supported");
}
RootNode root = mth.root();
MethodInfo resolveMth = MethodInfo.fromRef(root, resolveHandle.getMethodRef());
InvokeType resolveInvokeType = InvokeCustomUtils.convertInvokeType(resolveHandle.getType());
InvokeNode resolve = new InvokeNode(resolveMth, resolveInvokeType, resolveArgs.size());
resolveArgs.forEach(resolve::addArg);
ClassInfo invokeCls = ClassInfo.fromType(root, ArgType.OBJECT); // type will be known at runtime
MethodInfo invokeMth = MethodInfo.fromMethodProto(root, invokeCls, invokeName, invokeProto);
InvokeCustomRawNode customRawNode = new InvokeCustomRawNode(resolve, invokeMth, insn, isRange);
customRawNode.setCallSiteValues(values);
return customRawNode;
}
private static List<InsnArg> buildArgs(MethodNode mth, List<EncodedValue> values) {
int valuesCount = values.size();
List<InsnArg> list = new ArrayList<>(valuesCount);
RootNode root = mth.root();
list.add(buildLookupArg(root)); // use `java.lang.invoke.MethodHandles.lookup()` as first arg
for (int i = 1; i < valuesCount; i++) {
EncodedValue value = values.get(i);
try {
list.add(convertToInsnArg(root, value));
} catch (Exception e) {
mth.addWarnComment("Failed to build arg in invoke-custom insn: " + value, e);
list.add(InsnArg.wrapArg(new ConstStringNode(value.toString())));
}
}
return list;
}
}
@@ -0,0 +1,25 @@
package jadx.core.dex.instructions.invokedynamic;
import jadx.api.plugins.input.data.MethodHandleType;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class InvokeCustomUtils {
public static InvokeType convertInvokeType(MethodHandleType type) {
switch (type) {
case INVOKE_STATIC:
return InvokeType.STATIC;
case INVOKE_INSTANCE:
return InvokeType.VIRTUAL;
case INVOKE_DIRECT:
case INVOKE_CONSTRUCTOR:
return InvokeType.DIRECT;
case INVOKE_INTERFACE:
return InvokeType.INTERFACE;
default:
throw new JadxRuntimeException("Unsupported method handle type: " + type);
}
}
}
@@ -19,29 +19,59 @@ import static jadx.core.utils.Utils.lockList;
public final class BlockNode extends AttrNode implements IBlock, Comparable<BlockNode> { public final class BlockNode extends AttrNode implements IBlock, Comparable<BlockNode> {
/**
* Const ID
*/
private final int cid;
/**
* ID linked to position in blocks list (easier to use BitSet)
* TODO: rename to avoid confusion
*/
private int id; private int id;
/**
* Offset in methods bytecode
*/
private final int startOffset; private final int startOffset;
private final List<InsnNode> instructions = new ArrayList<>(2); private final List<InsnNode> instructions = new ArrayList<>(2);
private List<BlockNode> predecessors = new ArrayList<>(1); private List<BlockNode> predecessors = new ArrayList<>(1);
private List<BlockNode> successors = new ArrayList<>(1); private List<BlockNode> successors = new ArrayList<>(1);
private List<BlockNode> cleanSuccessors; private List<BlockNode> cleanSuccessors;
// all dominators /**
* All dominators, excluding self
*/
private BitSet doms = EmptyBitSet.EMPTY; private BitSet doms = EmptyBitSet.EMPTY;
// dominance frontier
/**
* Dominance frontier
*/
private BitSet domFrontier; private BitSet domFrontier;
// immediate dominator
/**
* Immediate dominator
*/
private BlockNode idom; private BlockNode idom;
// blocks on which dominates this block
/**
* Blocks on which dominates this block
*/
private List<BlockNode> dominatesOn = new ArrayList<>(3); private List<BlockNode> dominatesOn = new ArrayList<>(3);
public BlockNode(int id, int offset) { public BlockNode(int cid, int id, int offset) {
this.cid = cid;
this.id = id; this.id = id;
this.startOffset = offset; this.startOffset = offset;
} }
public void setId(int id) { public int getCId() {
return cid;
}
void setId(int id) {
this.id = id; this.id = id;
} }
@@ -188,12 +218,12 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
return false; return false;
} }
BlockNode other = (BlockNode) obj; BlockNode other = (BlockNode) obj;
return id == other.id && startOffset == other.startOffset; return cid == other.cid && startOffset == other.startOffset;
} }
@Override @Override
public int compareTo(@NotNull BlockNode o) { public int compareTo(@NotNull BlockNode o) {
return Integer.compare(id, o.id); return Integer.compare(cid, o.cid);
} }
@Override @Override
@@ -203,6 +233,6 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
@Override @Override
public String toString() { public String toString() {
return "B:" + id + ':' + InsnUtils.formatOffset(startOffset); return "B:" + cid + ':' + InsnUtils.formatOffset(startOffset);
} }
} }
@@ -13,14 +13,14 @@ import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.DecompilationMode; import jadx.api.DecompilationMode;
import jadx.api.ICodeCache; import jadx.api.ICodeCache;
import jadx.api.ICodeInfo; import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter; import jadx.api.ICodeWriter;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.JavaClass;
import jadx.api.impl.SimpleCodeInfo;
import jadx.api.plugins.input.data.IClassData; import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.IFieldData; import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodData; import jadx.api.plugins.input.data.IMethodData;
@@ -54,8 +54,6 @@ import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED; import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> { public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
private final RootNode root; private final RootNode root;
private final IClassData clsData; private final IClassData clsData;
@@ -99,6 +97,8 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
// cache maps // cache maps
private Map<MethodInfo, MethodNode> mthInfoMap = Collections.emptyMap(); private Map<MethodInfo, MethodNode> mthInfoMap = Collections.emptyMap();
private JavaClass javaNode;
public ClassNode(RootNode root, IClassData cls) { public ClassNode(RootNode root, IClassData cls) {
this.root = root; this.root = root;
this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType())); this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType()));
@@ -170,10 +170,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return ArgType.object(superType); return ArgType.object(superType);
} }
public void updateGenericClsData(ArgType superClass, List<ArgType> interfaces, List<ArgType> generics) { public void updateGenericClsData(List<ArgType> generics, ArgType superClass, List<ArgType> interfaces) {
this.generics = generics;
this.superClass = superClass; this.superClass = superClass;
this.interfaces = interfaces; this.interfaces = interfaces;
this.generics = generics;
} }
private static void processAttributes(ClassNode cls) { private static void processAttributes(ClassNode cls) {
@@ -378,7 +378,13 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return code; return code;
} }
} }
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this); ICodeInfo codeInfo;
try {
codeInfo = root.getProcessClasses().generateCode(this);
} catch (Throwable e) {
addError("Code generation failed", e);
codeInfo = new SimpleCodeInfo(Utils.getStackTrace(e));
}
if (codeInfo != ICodeInfo.EMPTY) { if (codeInfo != ICodeInfo.EMPTY) {
codeCache.add(clsRawName, codeInfo); codeCache.add(clsRawName, codeInfo);
} }
@@ -462,6 +468,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
} }
public void addField(FieldNode fld) { public void addField(FieldNode fld) {
if (fields == null || fields.isEmpty()) {
fields = new ArrayList<>(1);
}
fields.add(fld); fields.add(fld);
} }
@@ -650,6 +659,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return contains(AType.ANONYMOUS_CLASS); return contains(AType.ANONYMOUS_CLASS);
} }
public boolean isSynthetic() {
return contains(AFlag.SYNTHETIC);
}
public boolean isInner() { public boolean isInner() {
return parentClass != this; return parentClass != this;
} }
@@ -791,7 +804,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
} }
public void addCodegenDep(ClassNode dep) { public void addCodegenDep(ClassNode dep) {
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep); if (!codegenDeps.contains(dep)) {
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
}
} }
public int getTotalDepsCount() { public int getTotalDepsCount() {
@@ -819,6 +834,14 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return clsData == null ? "synthetic" : clsData.getInputFileName(); return clsData == null ? "synthetic" : clsData.getInputFileName();
} }
public JavaClass getJavaNode() {
return javaNode;
}
public void setJavaNode(JavaClass javaNode) {
this.javaNode = javaNode;
}
@Override @Override
public AnnType getAnnType() { public AnnType getAnnType() {
return AnnType.CLASS; return AnnType.CLASS;
@@ -3,6 +3,7 @@ package jadx.core.dex.nodes;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import jadx.api.JavaField;
import jadx.api.plugins.input.data.IFieldData; import jadx.api.plugins.input.data.IFieldData;
import jadx.core.dex.attributes.nodes.NotificationAttrNode; import jadx.core.dex.attributes.nodes.NotificationAttrNode;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
@@ -21,6 +22,8 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
private List<MethodNode> useIn = Collections.emptyList(); private List<MethodNode> useIn = Collections.emptyList();
private JavaField javaNode;
public static FieldNode build(ClassNode cls, IFieldData fieldData) { public static FieldNode build(ClassNode cls, IFieldData fieldData) {
FieldInfo fieldInfo = FieldInfo.fromRef(cls.root(), fieldData); FieldInfo fieldInfo = FieldInfo.fromRef(cls.root(), fieldData);
FieldNode fieldNode = new FieldNode(cls, fieldInfo, fieldData.getAccessFlags()); FieldNode fieldNode = new FieldNode(cls, fieldInfo, fieldData.getAccessFlags());
@@ -57,6 +60,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
return accFlags.isStatic(); return accFlags.isStatic();
} }
public boolean isInstance() {
return !accFlags.isStatic();
}
public String getName() { public String getName() {
return fieldInfo.getName(); return fieldInfo.getName();
} }
@@ -65,6 +72,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
return fieldInfo.getAlias(); return fieldInfo.getAlias();
} }
public void rename(String alias) {
fieldInfo.setAlias(alias);
}
public ArgType getType() { public ArgType getType() {
return type; return type;
} }
@@ -73,6 +84,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
return parentClass; return parentClass;
} }
public ClassNode getTopParentClass() {
return parentClass.getTopParentClass();
}
public List<MethodNode> getUseIn() { public List<MethodNode> getUseIn() {
return useIn; return useIn;
} }
@@ -100,6 +115,14 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
return parentClass.root(); return parentClass.root();
} }
public JavaField getJavaNode() {
return javaNode;
}
public void setJavaNode(JavaField javaNode) {
this.javaNode = javaNode;
}
@Override @Override
public AnnType getAnnType() { public AnnType getAnnType() {
return AnnType.FIELD; return AnnType.FIELD;
@@ -264,21 +264,6 @@ public class InsnNode extends LineAttrNode {
} }
} }
public boolean canReorderRecursive() {
if (!canReorder()) {
return false;
}
for (InsnArg arg : this.getArguments()) {
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
if (!wrapInsn.canReorderRecursive()) {
return false;
}
}
}
return true;
}
public boolean containsWrappedInsn() { public boolean containsWrappedInsn() {
for (InsnArg arg : this.getArguments()) { for (InsnArg arg : this.getArguments()) {
if (arg.isInsnWrap()) { if (arg.isInsnWrap()) {
@@ -554,19 +539,25 @@ public class InsnNode extends LineAttrNode {
return super.equals(obj); return super.equals(obj);
} }
protected void appendArgs(StringBuilder sb) { /**
* Append arguments type, wrap line if too long
*
* @return true if args wrapped
*/
protected boolean appendArgs(StringBuilder sb) {
if (arguments.isEmpty()) { if (arguments.isEmpty()) {
return; return false;
} }
String argsStr = Utils.listToString(arguments); String argsStr = Utils.listToString(arguments);
if (argsStr.length() < 120) { if (argsStr.length() < 120) {
sb.append(argsStr); sb.append(argsStr);
} else { return false;
// wrap args
String separator = ICodeWriter.NL + " ";
sb.append(separator).append(Utils.listToString(arguments, separator));
sb.append(ICodeWriter.NL);
} }
// wrap args
String separator = ICodeWriter.NL + " ";
sb.append(separator).append(Utils.listToString(arguments, separator));
sb.append(ICodeWriter.NL);
return true;
} }
@Override @Override
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.JavaMethod;
import jadx.api.plugins.input.data.ICodeReader; import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.IDebugInfo; import jadx.api.plugins.input.data.IDebugInfo;
import jadx.api.plugins.input.data.IMethodData; import jadx.api.plugins.input.data.IMethodData;
@@ -61,6 +62,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
private List<RegisterArg> argsList; private List<RegisterArg> argsList;
private InsnNode[] instructions; private InsnNode[] instructions;
private List<BlockNode> blocks; private List<BlockNode> blocks;
private int blocksMaxCId;
private BlockNode enterBlock; private BlockNode enterBlock;
private BlockNode exitBlock; private BlockNode exitBlock;
private List<SSAVar> sVars; private List<SSAVar> sVars;
@@ -70,6 +72,8 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
private List<MethodNode> useIn = Collections.emptyList(); private List<MethodNode> useIn = Collections.emptyList();
private JavaMethod javaNode;
public static MethodNode build(ClassNode classNode, IMethodData methodData) { public static MethodNode build(ClassNode classNode, IMethodData methodData) {
MethodNode methodNode = new MethodNode(classNode, methodData); MethodNode methodNode = new MethodNode(classNode, methodData);
methodNode.addAttrs(methodData.getAttributes()); methodNode.addAttrs(methodData.getAttributes());
@@ -316,6 +320,19 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return blocks; return blocks;
} }
public void setBasicBlocks(List<BlockNode> blocks) {
this.blocks = blocks;
int i = 0;
for (BlockNode block : blocks) {
block.setId(i);
i++;
}
}
public int getNextBlockCId() {
return blocksMaxCId++;
}
public BlockNode getEnterBlock() { public BlockNode getEnterBlock() {
return enterBlock; return enterBlock;
} }
@@ -596,6 +613,14 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
this.useIn = useIn; this.useIn = useIn;
} }
public JavaMethod getJavaNode() {
return javaNode;
}
public void setJavaNode(JavaMethod javaNode) {
this.javaNode = javaNode;
}
@Override @Override
public AnnType getAnnType() { public AnnType getAnnType() {
return AnnType.METHOD; return AnnType.METHOD;
@@ -42,7 +42,9 @@ import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.android.AndroidResourcesUtils; import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.xmlgen.ResTableParser; import jadx.core.xmlgen.IResParser;
import jadx.core.xmlgen.ManifestAttributes;
import jadx.core.xmlgen.ResDecoder;
import jadx.core.xmlgen.ResourceStorage; import jadx.core.xmlgen.ResourceStorage;
import jadx.core.xmlgen.entry.ResourceEntry; import jadx.core.xmlgen.entry.ResourceEntry;
import jadx.core.xmlgen.entry.ValuesParser; import jadx.core.xmlgen.entry.ValuesParser;
@@ -163,32 +165,37 @@ public class RootNode {
} }
public void loadResources(List<ResourceFile> resources) { public void loadResources(List<ResourceFile> resources) {
ResourceFile arsc = null; ResourceFile arsc = getResourceFile(resources);
for (ResourceFile rf : resources) {
if (rf.getType() == ResourceType.ARSC) {
arsc = rf;
break;
}
}
if (arsc == null) { if (arsc == null) {
LOG.debug("'.arsc' file not found"); LOG.debug("'.arsc' file not found");
return; return;
} }
try { try {
ResTableParser parser = ResourcesLoader.decodeStream(arsc, (size, is) -> { IResParser parser = ResourcesLoader.decodeStream(arsc, (size, is) -> ResDecoder.decode(this, arsc, is));
ResTableParser tableParser = new ResTableParser(this);
tableParser.decode(is);
return tableParser;
});
if (parser != null) { if (parser != null) {
processResources(parser.getResStorage()); processResources(parser.getResStorage());
updateObfuscatedFiles(parser, resources); updateObfuscatedFiles(parser, resources);
updateManifestAttribMap(parser);
} }
} catch (Exception e) { } catch (Exception e) {
LOG.error("Failed to parse '.arsc' file", e); LOG.error("Failed to parse '.arsc' file", e);
} }
} }
private void updateManifestAttribMap(IResParser parser) {
ManifestAttributes manifestAttributes = ManifestAttributes.getInstance();
manifestAttributes.updateAttributes(parser);
}
private @Nullable ResourceFile getResourceFile(List<ResourceFile> resources) {
for (ResourceFile rf : resources) {
if (rf.getType() == ResourceType.ARSC) {
return rf;
}
}
return null;
}
public void processResources(ResourceStorage resStorage) { public void processResources(ResourceStorage resStorage) {
constValues.setResourcesNames(resStorage.getResourcesNames()); constValues.setResourcesNames(resStorage.getResourcesNames());
appPackage = resStorage.getAppPackage(); appPackage = resStorage.getAppPackage();
@@ -209,7 +216,7 @@ public class RootNode {
} }
} }
private void updateObfuscatedFiles(ResTableParser parser, List<ResourceFile> resources) { private void updateObfuscatedFiles(IResParser parser, List<ResourceFile> resources) {
if (args.isSkipResources()) { if (args.isSkipResources()) {
return; return;
} }
@@ -156,7 +156,7 @@ public final class IfCondition extends AttrNode {
return i; return i;
} }
if (c.getOp() == IfOp.EQ && c.getB().isFalse()) { if (c.getOp() == IfOp.EQ && c.getB().isFalse()) {
cond = not(new IfCondition(c.invert())); cond = new IfCondition(Mode.NOT, Collections.singletonList(new IfCondition(c.invert())));
} else { } else {
c.normalize(); c.normalize();
} }
@@ -49,6 +49,10 @@ public final class LoopRegion extends ConditionRegion {
return header; return header;
} }
public boolean isEndless() {
return header == null;
}
public IRegion getBody() { public IRegion getBody() {
return body; return body;
} }
@@ -60,6 +60,10 @@ public class TryCatchBlockAttr implements IJadxAttribute {
return throwFound; return throwFound;
} }
public int getId() {
return id;
}
public List<ExceptionHandler> getHandlers() { public List<ExceptionHandler> getHandlers() {
return handlers; return handlers;
} }
@@ -10,6 +10,7 @@ import java.util.Objects;
import jadx.api.plugins.input.data.AccessFlags; import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.MethodReplaceAttr; import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
@@ -225,7 +226,7 @@ public class ClassModifier extends AbstractVisitor {
} }
private static boolean removeBridgeMethod(ClassNode cls, MethodNode mth) { private static boolean removeBridgeMethod(ClassNode cls, MethodNode mth) {
if (cls.root().getArgs().isRenameValid()) { if (cls.root().getArgs().isInlineMethods()) { // simple wrapper remove is same as inline
List<InsnNode> allInsns = BlockUtils.collectAllInsns(mth.getBasicBlocks()); List<InsnNode> allInsns = BlockUtils.collectAllInsns(mth.getBasicBlocks());
if (allInsns.size() == 1) { if (allInsns.size() == 1) {
InsnNode wrappedInsn = allInsns.get(0); InsnNode wrappedInsn = allInsns.get(0);
@@ -235,12 +236,10 @@ public class ClassModifier extends AbstractVisitor {
wrappedInsn = ((InsnWrapArg) arg).getWrapInsn(); wrappedInsn = ((InsnWrapArg) arg).getWrapInsn();
} }
} }
if (checkSyntheticWrapper(mth, wrappedInsn)) { return checkSyntheticWrapper(mth, wrappedInsn);
return true;
}
} }
} }
return !isMethodUnique(cls, mth); return false;
} }
private static boolean checkSyntheticWrapper(MethodNode mth, InsnNode insn) { private static boolean checkSyntheticWrapper(MethodNode mth, InsnNode insn) {
@@ -283,6 +282,9 @@ public class ClassModifier extends AbstractVisitor {
if (!Objects.equals(wrappedMth.getAlias(), alias)) { if (!Objects.equals(wrappedMth.getAlias(), alias)) {
wrappedMth.getMethodInfo().setAlias(alias); wrappedMth.getMethodInfo().setAlias(alias);
} }
wrappedMth.addAttr(new MethodReplaceAttr(mth));
wrappedMth.copyAttributeFrom(mth, AType.METHOD_OVERRIDE);
wrappedMth.addDebugComment("Method merged with bridge method");
return true; return true;
} }
@@ -299,20 +301,6 @@ public class ClassModifier extends AbstractVisitor {
return false; return false;
} }
private static boolean isMethodUnique(ClassNode cls, MethodNode mth) {
MethodInfo mi = mth.getMethodInfo();
for (MethodNode otherMth : cls.getMethods()) {
if (otherMth != mth) {
MethodInfo omi = otherMth.getMethodInfo();
if (omi.getName().equals(mi.getName())
&& Objects.equals(omi.getArgumentsTypes(), mi.getArgumentsTypes())) {
return false;
}
}
}
return true;
}
/** /**
* Remove public empty constructors (static or default) * Remove public empty constructors (static or default)
*/ */
@@ -62,45 +62,47 @@ public class ConstInlineVisitor extends AbstractVisitor {
|| insn.getResult() == null) { || insn.getResult() == null) {
return; return;
} }
SSAVar sVar = insn.getResult().getSVar(); SSAVar sVar = insn.getResult().getSVar();
InsnArg constArg; InsnArg constArg;
Runnable onSuccess = null; Runnable onSuccess = null;
switch (insn.getType()) {
InsnType insnType = insn.getType(); case CONST:
if (insnType == InsnType.CONST || insnType == InsnType.MOVE) { case MOVE: {
constArg = insn.getArg(0); constArg = insn.getArg(0);
if (!constArg.isLiteral()) { if (!constArg.isLiteral()) {
return;
}
long lit = ((LiteralArg) constArg).getLiteral();
if (lit == 0 && forbidNullInlines(sVar)) {
// all usages forbids inlining
return;
}
break;
}
case CONST_STR: {
String s = ((ConstStringNode) insn).getString();
FieldNode f = mth.getParentClass().getConstField(s);
if (f == null) {
InsnNode copy = insn.copyWithoutResult();
constArg = InsnArg.wrapArg(copy);
} else {
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
constArg = InsnArg.wrapArg(constGet);
constArg.setType(ArgType.STRING);
onSuccess = () -> f.addUseIn(mth);
}
break;
}
case CONST_CLASS: {
if (sVar.isUsedInPhi()) {
return;
}
constArg = InsnArg.wrapArg(insn.copyWithoutResult());
constArg.setType(ArgType.CLASS);
break;
}
default:
return; return;
}
long lit = ((LiteralArg) constArg).getLiteral();
if (lit == 0 && forbidNullInlines(sVar)) {
// all usages forbids inlining
return;
}
} else if (insnType == InsnType.CONST_STR) {
if (sVar.isUsedInPhi()) {
return;
}
String s = ((ConstStringNode) insn).getString();
FieldNode f = mth.getParentClass().getConstField(s);
if (f == null) {
InsnNode copy = insn.copyWithoutResult();
constArg = InsnArg.wrapArg(copy);
} else {
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
constArg = InsnArg.wrapArg(constGet);
constArg.setType(ArgType.STRING);
onSuccess = () -> f.addUseIn(mth);
}
} else if (insnType == InsnType.CONST_CLASS) {
if (sVar.isUsedInPhi()) {
return;
}
constArg = InsnArg.wrapArg(insn.copyWithoutResult());
constArg.setType(ArgType.CLASS);
} else {
return;
} }
// all check passed, run replace // all check passed, run replace
@@ -123,17 +125,30 @@ public class ConstInlineVisitor extends AbstractVisitor {
int k = 0; int k = 0;
for (RegisterArg useArg : useList) { for (RegisterArg useArg : useList) {
InsnNode insn = useArg.getParentInsn(); InsnNode insn = useArg.getParentInsn();
if (insn == null) { if (insn != null && forbidNullArgInline(insn, useArg)) {
continue;
}
if (!canUseNull(insn, useArg)) {
useArg.add(AFlag.DONT_INLINE_CONST);
k++; k++;
} }
} }
return k == useList.size(); return k == useList.size();
} }
private static boolean forbidNullArgInline(InsnNode insn, RegisterArg useArg) {
switch (insn.getType()) {
case MOVE:
case CAST:
case CHECK_CAST:
// result is null, chain checks
return forbidNullInlines(insn.getResult().getSVar());
default:
if (!canUseNull(insn, useArg)) {
useArg.add(AFlag.DONT_INLINE_CONST);
return true;
}
return false;
}
}
private static boolean canUseNull(InsnNode insn, RegisterArg useArg) { private static boolean canUseNull(InsnNode insn, RegisterArg useArg) {
switch (insn.getType()) { switch (insn.getType()) {
case INVOKE: case INVOKE:
@@ -100,7 +100,7 @@ public class DotGraphVisitor extends AbstractVisitor {
if (insnArr == null) { if (insnArr == null) {
return; return;
} }
BlockNode block = new BlockNode(0, 0); BlockNode block = new BlockNode(0, 0, 0);
List<InsnNode> insnList = block.getInstructions(); List<InsnNode> insnList = block.getInstructions();
for (InsnNode insn : insnArr) { for (InsnNode insn : insnArr) {
if (insn != null) { if (insn != null) {
@@ -199,7 +199,7 @@ public class DotGraphVisitor extends AbstractVisitor {
dot.add("color=red,"); dot.add("color=red,");
} }
dot.add("label=\"{"); dot.add("label=\"{");
dot.add(String.valueOf(block.getId())).add("\\:\\ "); dot.add(String.valueOf(block.getCId())).add("\\:\\ ");
dot.add(InsnUtils.formatOffset(block.getStartOffset())); dot.add(InsnUtils.formatOffset(block.getStartOffset()));
if (!attrs.isEmpty()) { if (!attrs.isEmpty()) {
dot.add('|').add(attrs); dot.add('|').add(attrs);
@@ -230,10 +230,10 @@ public class DotGraphVisitor extends AbstractVisitor {
if (PRINT_DOMINATORS) { if (PRINT_DOMINATORS) {
for (BlockNode c : block.getDominatesOn()) { for (BlockNode c : block.getDominatesOn()) {
conn.startLine(block.getId() + " -> " + c.getId() + "[color=green];"); conn.startLine(block.getCId() + " -> " + c.getCId() + "[color=green];");
} }
for (BlockNode dom : BlockUtils.bitSetToBlocks(mth, block.getDomFrontier())) { for (BlockNode dom : BlockUtils.bitSetToBlocks(mth, block.getDomFrontier())) {
conn.startLine("f_" + block.getId() + " -> f_" + dom.getId() + "[color=blue];"); conn.startLine("f_" + block.getCId() + " -> f_" + dom.getCId() + "[color=blue];");
} }
} }
} }
@@ -273,7 +273,7 @@ public class DotGraphVisitor extends AbstractVisitor {
private String makeName(IContainer c) { private String makeName(IContainer c) {
String name; String name;
if (c instanceof BlockNode) { if (c instanceof BlockNode) {
name = "Node_" + ((BlockNode) c).getId(); name = "Node_" + ((BlockNode) c).getCId();
} else if (c instanceof IBlock) { } else if (c instanceof IBlock) {
name = "Node_" + c.getClass().getSimpleName() + '_' + c.hashCode(); name = "Node_" + c.getClass().getSimpleName() + '_' + c.hashCode();
} else { } else {
@@ -18,6 +18,7 @@ import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField; import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
@@ -26,6 +27,7 @@ import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.InsnWrapArg;
@@ -35,9 +37,13 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.regions.Region;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.utils.BlockInsnPair; import jadx.core.utils.BlockInsnPair;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
@@ -45,6 +51,7 @@ import jadx.core.utils.InsnRemover;
import jadx.core.utils.InsnUtils; import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.utils.InsnUtils.checkInsnType; import static jadx.core.utils.InsnUtils.checkInsnType;
import static jadx.core.utils.InsnUtils.getSingleArg; import static jadx.core.utils.InsnUtils.getSingleArg;
@@ -53,12 +60,21 @@ import static jadx.core.utils.InsnUtils.getWrappedInsn;
@JadxVisitor( @JadxVisitor(
name = "EnumVisitor", name = "EnumVisitor",
desc = "Restore enum classes", desc = "Restore enum classes",
runAfter = { CodeShrinkVisitor.class, ModVisitor.class, ReSugarCode.class }, runAfter = {
runBefore = { ExtractFieldInit.class } CodeShrinkVisitor.class, // all possible instructions already inlined
ModVisitor.class,
ReSugarCode.class,
IfRegionVisitor.class, // ternary operator inlined
CheckRegions.class // regions processing finished
},
runBefore = {
ExtractFieldInit.class
}
) )
public class EnumVisitor extends AbstractVisitor { public class EnumVisitor extends AbstractVisitor {
private MethodInfo enumValueOfMth; private MethodInfo enumValueOfMth;
private MethodInfo cloneMth;
@Override @Override
public void init(RootNode root) { public void init(RootNode root) {
@@ -68,86 +84,77 @@ public class EnumVisitor extends AbstractVisitor {
"valueOf", "valueOf",
Arrays.asList(ArgType.CLASS, ArgType.STRING), Arrays.asList(ArgType.CLASS, ArgType.STRING),
ArgType.ENUM); ArgType.ENUM);
cloneMth = MethodInfo.fromDetails(root,
ClassInfo.fromType(root, ArgType.OBJECT),
"clone",
Collections.emptyList(),
ArgType.OBJECT);
} }
@Override @Override
public boolean visit(ClassNode cls) throws JadxException { public boolean visit(ClassNode cls) throws JadxException {
boolean converted; if (cls.isEnum()) {
try { boolean converted;
converted = convertToEnum(cls); try {
} catch (Exception e) { converted = convertToEnum(cls);
cls.addWarnComment("Enum visitor error", e); } catch (Exception e) {
converted = false; cls.addWarnComment("Enum visitor error", e);
} converted = false;
if (!converted) { }
AccessInfo accessFlags = cls.getAccessFlags(); if (!converted) {
if (accessFlags.isEnum()) { AccessInfo accessFlags = cls.getAccessFlags();
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM)); if (accessFlags.isEnum()) {
cls.addWarnComment("Failed to restore enum class, 'enum' modifier removed"); cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
cls.addWarnComment("Failed to restore enum class, 'enum' modifier and super class removed");
}
} }
} }
return true; return true;
} }
private boolean convertToEnum(ClassNode cls) { private boolean convertToEnum(ClassNode cls) {
if (!cls.isEnum()) { ArgType superType = cls.getSuperClass();
return false; if (superType != null && superType.getObject().equals(ArgType.ENUM.getObject())) {
cls.add(AFlag.REMOVE_SUPER_CLASS);
} }
MethodNode classInitMth = cls.getClassInitMth(); MethodNode classInitMth = cls.getClassInitMth();
if (classInitMth == null) { if (classInitMth == null) {
cls.addWarnComment("Enum class init method not found"); cls.addWarnComment("Enum class init method not found");
return false; return false;
} }
if (classInitMth.getBasicBlocks().isEmpty()) { Region staticRegion = classInitMth.getRegion();
if (staticRegion == null || classInitMth.getBasicBlocks().isEmpty()) {
return false; return false;
} }
ArgType clsType = cls.getClassInfo().getType(); // collect blocks on linear part of static method (ignore branching on method end)
List<BlockNode> staticBlocks = new ArrayList<>();
// search "$VALUES" field (holds all enum values) for (IContainer subBlock : staticRegion.getSubBlocks()) {
List<FieldNode> valuesCandidates = cls.getFields().stream() if (subBlock instanceof BlockNode) {
.filter(f -> f.getAccessFlags().isStatic()) staticBlocks.add((BlockNode) subBlock);
.filter(f -> f.getType().isArray()) } else {
.filter(f -> Objects.equals(f.getType().getArrayRootElement(), clsType)) break;
.collect(Collectors.toList());
if (valuesCandidates.isEmpty()) {
return false;
}
if (valuesCandidates.size() > 1) {
valuesCandidates.removeIf(f -> !f.getAccessFlags().isSynthetic());
}
if (valuesCandidates.size() > 1) {
Optional<FieldNode> valuesOpt = valuesCandidates.stream().filter(f -> f.getName().equals("$VALUES")).findAny();
if (valuesOpt.isPresent()) {
valuesCandidates.clear();
valuesCandidates.add(valuesOpt.get());
} }
} }
if (valuesCandidates.size() != 1) { if (staticBlocks.isEmpty()) {
cls.addWarnComment("Found several \"values\" enum fields: " + valuesCandidates); cls.addWarnComment("Unexpected branching in enum static init block");
return false; return false;
} }
FieldNode valuesField = valuesCandidates.get(0); EnumData data = new EnumData(cls, classInitMth, staticBlocks);
List<InsnNode> toRemove = new ArrayList<>(); if (!searchValuesField(data)) {
// search "$VALUES" array init and collect enum fields
BlockInsnPair valuesInitPair = getValuesInitInsn(classInitMth, valuesField);
if (valuesInitPair == null) {
return false; return false;
} }
BlockNode staticBlock = valuesInitPair.getBlock();
InsnNode valuesInitInsn = valuesInitPair.getInsn();
List<EnumField> enumFields = null; List<EnumField> enumFields = null;
InsnArg arrArg = valuesInitInsn.getArg(0); InsnArg arrArg = data.valuesInitInsn.getArg(0);
if (arrArg.isInsnWrap()) { if (arrArg.isInsnWrap()) {
InsnNode wrappedInsn = ((InsnWrapArg) arrArg).getWrapInsn(); InsnNode wrappedInsn = ((InsnWrapArg) arrArg).getWrapInsn();
enumFields = extractEnumFieldsFromInsn(cls, staticBlock, wrappedInsn, toRemove); enumFields = extractEnumFieldsFromInsn(data, wrappedInsn);
} }
if (enumFields == null) { if (enumFields == null) {
cls.addWarnComment("Unknown enum class pattern. Please report as an issue!");
return false; return false;
} }
toRemove.add(valuesInitInsn); data.toRemove.add(data.valuesInitInsn);
// all checks complete, perform transform // all checks complete, perform transform
EnumClassAttr attr = new EnumClassAttr(enumFields); EnumClassAttr attr = new EnumClassAttr(enumFields);
@@ -167,59 +174,92 @@ public class EnumVisitor extends AbstractVisitor {
fieldNode.getFieldInfo().setAlias(name); fieldNode.getFieldInfo().setAlias(name);
} }
fieldNode.add(AFlag.DONT_GENERATE); fieldNode.add(AFlag.DONT_GENERATE);
processConstructorInsn(cls, enumField, classInitMth, staticBlock, toRemove); processConstructorInsn(data, enumField, classInitMth);
} }
valuesField.add(AFlag.DONT_GENERATE); data.valuesField.add(AFlag.DONT_GENERATE);
InsnRemover.removeAllAndUnbind(classInitMth, staticBlock, toRemove); InsnRemover.removeAllAndUnbind(classInitMth, data.toRemove);
if (classInitMth.countInsns() == 0) { if (classInitMth.countInsns() == 0) {
classInitMth.add(AFlag.DONT_GENERATE); classInitMth.add(AFlag.DONT_GENERATE);
} else if (!toRemove.isEmpty()) { } else if (!data.toRemove.isEmpty()) {
CodeShrinkVisitor.shrinkMethod(classInitMth); CodeShrinkVisitor.shrinkMethod(classInitMth);
} }
removeEnumMethods(cls, clsType, valuesField); removeEnumMethods(cls, data.valuesField);
return true; return true;
} }
private void processConstructorInsn(ClassNode cls, EnumField enumField, MethodNode classInitMth, /**
BlockNode staticBlock, List<InsnNode> toRemove) { * Search "$VALUES" field (holds all enum values)
ConstructorInsn co = enumField.getConstrInsn(); */
ClassInfo enumClsInfo = co.getClassType(); private boolean searchValuesField(EnumData data) {
if (!enumClsInfo.equals(cls.getClassInfo())) { ArgType clsType = data.cls.getClassInfo().getType();
ClassNode enumCls = cls.root().resolveClass(enumClsInfo); List<FieldNode> valuesCandidates = data.cls.getFields().stream()
if (enumCls != null) { .filter(f -> f.getAccessFlags().isStatic())
processEnumCls(cls, enumField, enumCls); .filter(f -> f.getType().isArray())
.filter(f -> Objects.equals(f.getType().getArrayRootElement(), clsType))
.collect(Collectors.toList());
if (valuesCandidates.isEmpty()) {
data.cls.addWarnComment("$VALUES field not found");
return false;
}
if (valuesCandidates.size() > 1) {
valuesCandidates.removeIf(f -> !f.getAccessFlags().isSynthetic());
}
if (valuesCandidates.size() > 1) {
Optional<FieldNode> valuesOpt = valuesCandidates.stream().filter(f -> f.getName().equals("$VALUES")).findAny();
if (valuesOpt.isPresent()) {
valuesCandidates.clear();
valuesCandidates.add(valuesOpt.get());
} }
} }
List<RegisterArg> regs = new ArrayList<>(); if (valuesCandidates.size() != 1) {
co.getRegisterArgs(regs); data.cls.addWarnComment("Found several \"values\" enum fields: " + valuesCandidates);
if (!regs.isEmpty()) { return false;
cls.addWarnComment("Init of enum " + enumField.getField().getName() + " can be incorrect");
} }
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth()); data.valuesField = valuesCandidates.get(0);
// search "$VALUES" array init and collect enum fields
BlockInsnPair valuesInitPair = getValuesInitInsn(data);
if (valuesInitPair == null) {
return false;
}
data.valuesInitInsn = valuesInitPair.getInsn();
return true;
}
private void processConstructorInsn(EnumData data, EnumField enumField, MethodNode classInitMth) {
ConstructorInsn co = enumField.getConstrInsn();
ClassInfo enumClsInfo = co.getClassType();
if (!enumClsInfo.equals(data.cls.getClassInfo())) {
ClassNode enumCls = data.cls.root().resolveClass(enumClsInfo);
if (enumCls != null) {
processEnumCls(data.cls, enumField, enumCls);
}
}
MethodNode ctrMth = data.cls.root().resolveMethod(co.getCallMth());
if (ctrMth != null) { if (ctrMth != null) {
markArgsForSkip(ctrMth); markArgsForSkip(ctrMth);
} }
RegisterArg coResArg = co.getResult(); RegisterArg coResArg = co.getResult();
if (coResArg == null || coResArg.getSVar().getUseList().size() <= 2) { if (coResArg == null || coResArg.getSVar().getUseList().size() <= 2) {
toRemove.add(co); data.toRemove.add(co);
} else { } else {
// constructor result used in other places -> replace constructor with enum field get (SGET) // constructor result used in other places -> replace constructor with enum field get (SGET)
IndexInsnNode enumGet = new IndexInsnNode(InsnType.SGET, enumField.getField().getFieldInfo(), 0); IndexInsnNode enumGet = new IndexInsnNode(InsnType.SGET, enumField.getField().getFieldInfo(), 0);
enumGet.setResult(coResArg.duplicate()); enumGet.setResult(coResArg.duplicate());
BlockUtils.replaceInsn(classInitMth, staticBlock, co, enumGet); BlockUtils.replaceInsn(classInitMth, co, enumGet);
} }
} }
@Nullable @Nullable
private List<EnumField> extractEnumFieldsFromInsn(ClassNode cls, BlockNode staticBlock, private List<EnumField> extractEnumFieldsFromInsn(EnumData enumData, InsnNode wrappedInsn) {
InsnNode wrappedInsn, List<InsnNode> toRemove) {
switch (wrappedInsn.getType()) { switch (wrappedInsn.getType()) {
case FILLED_NEW_ARRAY: case FILLED_NEW_ARRAY:
return extractEnumFieldsFromFilledArray(cls, wrappedInsn, staticBlock, toRemove); return extractEnumFieldsFromFilledArray(enumData, wrappedInsn);
case INVOKE: case INVOKE:
// handle redirection of values array fill (added in java 15) // handle redirection of values array fill (added in java 15)
return extractEnumFieldsFromInvoke(cls, staticBlock, (InvokeNode) wrappedInsn, toRemove); return extractEnumFieldsFromInvoke(enumData, (InvokeNode) wrappedInsn);
case NEW_ARRAY: case NEW_ARRAY:
InsnArg arg = wrappedInsn.getArg(0); InsnArg arg = wrappedInsn.getArg(0);
@@ -234,10 +274,9 @@ public class EnumVisitor extends AbstractVisitor {
} }
} }
private List<EnumField> extractEnumFieldsFromInvoke(ClassNode cls, BlockNode staticBlock, private List<EnumField> extractEnumFieldsFromInvoke(EnumData enumData, InvokeNode invokeNode) {
InvokeNode invokeNode, List<InsnNode> toRemove) {
MethodInfo callMth = invokeNode.getCallMth(); MethodInfo callMth = invokeNode.getCallMth();
MethodNode valuesMth = cls.root().resolveMethod(callMth); MethodNode valuesMth = enumData.cls.root().resolveMethod(callMth);
if (valuesMth == null || valuesMth.isVoidReturn()) { if (valuesMth == null || valuesMth.isVoidReturn()) {
return null; return null;
} }
@@ -247,16 +286,16 @@ public class EnumVisitor extends AbstractVisitor {
if (wrappedInsn == null) { if (wrappedInsn == null) {
return null; return null;
} }
List<EnumField> enumFields = extractEnumFieldsFromInsn(cls, staticBlock, wrappedInsn, toRemove); List<EnumField> enumFields = extractEnumFieldsFromInsn(enumData, wrappedInsn);
if (enumFields != null) { if (enumFields != null) {
valuesMth.add(AFlag.DONT_GENERATE); valuesMth.add(AFlag.DONT_GENERATE);
} }
return enumFields; return enumFields;
} }
private BlockInsnPair getValuesInitInsn(MethodNode classInitMth, FieldNode valuesField) { private BlockInsnPair getValuesInitInsn(EnumData data) {
FieldInfo searchField = valuesField.getFieldInfo(); FieldInfo searchField = data.valuesField.getFieldInfo();
for (BlockNode blockNode : classInitMth.getBasicBlocks()) { for (BlockNode blockNode : data.staticBlocks) {
for (InsnNode insn : blockNode.getInstructions()) { for (InsnNode insn : blockNode.getInstructions()) {
if (insn.getType() == InsnType.SPUT) { if (insn.getType() == InsnType.SPUT) {
IndexInsnNode indexInsnNode = (IndexInsnNode) insn; IndexInsnNode indexInsnNode = (IndexInsnNode) insn;
@@ -270,50 +309,49 @@ public class EnumVisitor extends AbstractVisitor {
return null; return null;
} }
private List<EnumField> extractEnumFieldsFromFilledArray(ClassNode cls, InsnNode arrFillInsn, BlockNode staticBlock, private List<EnumField> extractEnumFieldsFromFilledArray(EnumData enumData, InsnNode arrFillInsn) {
List<InsnNode> toRemove) {
List<EnumField> enumFields = new ArrayList<>(); List<EnumField> enumFields = new ArrayList<>();
for (InsnArg arg : arrFillInsn.getArguments()) { for (InsnArg arg : arrFillInsn.getArguments()) {
EnumField field = null; EnumField field = null;
if (arg.isInsnWrap()) { if (arg.isInsnWrap()) {
InsnNode wrappedInsn = ((InsnWrapArg) arg).getWrapInsn(); InsnNode wrappedInsn = ((InsnWrapArg) arg).getWrapInsn();
field = processEnumFieldByWrappedInsn(cls, wrappedInsn, staticBlock, toRemove); field = processEnumFieldByWrappedInsn(enumData, wrappedInsn);
} else if (arg.isRegister()) { } else if (arg.isRegister()) {
field = processEnumFieldByRegister(cls, (RegisterArg) arg, staticBlock, toRemove); field = processEnumFieldByRegister(enumData, (RegisterArg) arg);
} }
if (field == null) { if (field == null) {
return null; return null;
} }
enumFields.add(field); enumFields.add(field);
} }
toRemove.add(arrFillInsn); enumData.toRemove.add(arrFillInsn);
return enumFields; return enumFields;
} }
private EnumField processEnumFieldByWrappedInsn(ClassNode cls, InsnNode wrappedInsn, BlockNode staticBlock, List<InsnNode> toRemove) { private EnumField processEnumFieldByWrappedInsn(EnumData data, InsnNode wrappedInsn) {
if (wrappedInsn.getType() == InsnType.SGET) { if (wrappedInsn.getType() == InsnType.SGET) {
return processEnumFieldByField(cls, wrappedInsn, staticBlock, toRemove); return processEnumFieldByField(data, wrappedInsn);
} }
ConstructorInsn constructorInsn = castConstructorInsn(wrappedInsn); ConstructorInsn constructorInsn = castConstructorInsn(wrappedInsn);
if (constructorInsn != null) { if (constructorInsn != null) {
FieldNode enumFieldNode = createFakeField(cls, "EF" + constructorInsn.getOffset()); FieldNode enumFieldNode = createFakeField(data.cls, "EF" + constructorInsn.getOffset());
cls.addField(enumFieldNode); data.cls.addField(enumFieldNode);
return createEnumFieldByConstructor(cls, enumFieldNode, constructorInsn); return createEnumFieldByConstructor(data.cls, enumFieldNode, constructorInsn);
} }
return null; return null;
} }
@Nullable @Nullable
private EnumField processEnumFieldByField(ClassNode cls, InsnNode sgetInsn, BlockNode staticBlock, List<InsnNode> toRemove) { private EnumField processEnumFieldByField(EnumData data, InsnNode sgetInsn) {
if (sgetInsn.getType() != InsnType.SGET) { if (sgetInsn.getType() != InsnType.SGET) {
return null; return null;
} }
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sgetInsn).getIndex(); FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sgetInsn).getIndex();
FieldNode enumFieldNode = cls.searchField(fieldInfo); FieldNode enumFieldNode = data.cls.searchField(fieldInfo);
if (enumFieldNode == null) { if (enumFieldNode == null) {
return null; return null;
} }
InsnNode sputInsn = searchFieldPutInsn(cls, staticBlock, enumFieldNode); InsnNode sputInsn = searchFieldPutInsn(data, enumFieldNode);
if (sputInsn == null) { if (sputInsn == null) {
return null; return null;
} }
@@ -324,17 +362,17 @@ public class EnumVisitor extends AbstractVisitor {
} }
RegisterArg sgetResult = sgetInsn.getResult(); RegisterArg sgetResult = sgetInsn.getResult();
if (sgetResult == null || sgetResult.getSVar().getUseCount() == 1) { if (sgetResult == null || sgetResult.getSVar().getUseCount() == 1) {
toRemove.add(sgetInsn); data.toRemove.add(sgetInsn);
} }
toRemove.add(sputInsn); data.toRemove.add(sputInsn);
return createEnumFieldByConstructor(cls, enumFieldNode, co); return createEnumFieldByConstructor(data.cls, enumFieldNode, co);
} }
@Nullable @Nullable
private EnumField processEnumFieldByRegister(ClassNode cls, RegisterArg arg, BlockNode staticBlock, List<InsnNode> toRemove) { private EnumField processEnumFieldByRegister(EnumData data, RegisterArg arg) {
InsnNode assignInsn = arg.getAssignInsn(); InsnNode assignInsn = arg.getAssignInsn();
if (assignInsn != null && assignInsn.getType() == InsnType.SGET) { if (assignInsn != null && assignInsn.getType() == InsnType.SGET) {
return processEnumFieldByField(cls, assignInsn, staticBlock, toRemove); return processEnumFieldByField(data, assignInsn);
} }
SSAVar ssaVar = arg.getSVar(); SSAVar ssaVar = arg.getSVar();
@@ -345,12 +383,12 @@ public class EnumVisitor extends AbstractVisitor {
if (constrInsn == null || constrInsn.getType() != InsnType.CONSTRUCTOR) { if (constrInsn == null || constrInsn.getType() != InsnType.CONSTRUCTOR) {
return null; return null;
} }
FieldNode enumFieldNode = searchEnumField(cls, ssaVar, toRemove); FieldNode enumFieldNode = searchEnumField(data, ssaVar);
if (enumFieldNode == null) { if (enumFieldNode == null) {
enumFieldNode = createFakeField(cls, "EF" + arg.getRegNum()); enumFieldNode = createFakeField(data.cls, "EF" + arg.getRegNum());
cls.addField(enumFieldNode); data.cls.addField(enumFieldNode);
} }
return createEnumFieldByConstructor(cls, enumFieldNode, (ConstructorInsn) constrInsn); return createEnumFieldByConstructor(data.cls, enumFieldNode, (ConstructorInsn) constrInsn);
} }
private FieldNode createFakeField(ClassNode cls, String name) { private FieldNode createFakeField(ClassNode cls, String name) {
@@ -363,20 +401,21 @@ public class EnumVisitor extends AbstractVisitor {
} }
@Nullable @Nullable
private FieldNode searchEnumField(ClassNode cls, SSAVar ssaVar, List<InsnNode> toRemove) { private FieldNode searchEnumField(EnumData data, SSAVar ssaVar) {
InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn(); InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn();
if (sputInsn == null || sputInsn.getType() != InsnType.SPUT) { if (sputInsn == null || sputInsn.getType() != InsnType.SPUT) {
return null; return null;
} }
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex(); FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
FieldNode enumFieldNode = cls.searchField(fieldInfo); FieldNode enumFieldNode = data.cls.searchField(fieldInfo);
if (enumFieldNode == null) { if (enumFieldNode == null) {
return null; return null;
} }
toRemove.add(sputInsn); data.toRemove.add(sputInsn);
return enumFieldNode; return enumFieldNode;
} }
@SuppressWarnings("StatementWithEmptyBody")
private EnumField createEnumFieldByConstructor(ClassNode cls, FieldNode enumFieldNode, ConstructorInsn co) { private EnumField createEnumFieldByConstructor(ClassNode cls, FieldNode enumFieldNode, ConstructorInsn co) {
// usually constructor signature is '<init>(Ljava/lang/String;I)V'. // usually constructor signature is '<init>(Ljava/lang/String;I)V'.
// sometimes for one field enum second arg can be omitted // sometimes for one field enum second arg can be omitted
@@ -399,31 +438,38 @@ public class EnumVisitor extends AbstractVisitor {
if (ctrMth == null) { if (ctrMth == null) {
return null; return null;
} }
List<RegisterArg> regs = new ArrayList<>();
co.getRegisterArgs(regs);
if (!regs.isEmpty()) {
throw new JadxRuntimeException("Init of enum " + enumFieldNode.getName() + " uses external variables");
}
return new EnumField(enumFieldNode, co); return new EnumField(enumFieldNode, co);
} }
@Nullable @Nullable
private InsnNode searchFieldPutInsn(ClassNode cls, BlockNode staticBlock, FieldNode enumFieldNode) { private InsnNode searchFieldPutInsn(EnumData data, FieldNode enumFieldNode) {
for (InsnNode sputInsn : staticBlock.getInstructions()) { for (BlockNode block : data.staticBlocks) {
if (sputInsn != null && sputInsn.getType() == InsnType.SPUT) { for (InsnNode sputInsn : block.getInstructions()) {
FieldInfo f = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex(); if (sputInsn != null && sputInsn.getType() == InsnType.SPUT) {
FieldNode fieldNode = cls.searchField(f); FieldInfo f = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
if (Objects.equals(fieldNode, enumFieldNode)) { FieldNode fieldNode = data.cls.searchField(f);
return sputInsn; if (Objects.equals(fieldNode, enumFieldNode)) {
return sputInsn;
}
} }
} }
} }
return null; return null;
} }
private void removeEnumMethods(ClassNode cls, ArgType clsType, FieldNode valuesField) { private void removeEnumMethods(ClassNode cls, FieldNode valuesField) {
String valuesMethod = "values()" + TypeGen.signature(ArgType.array(clsType)); ArgType clsType = cls.getClassInfo().getType();
FieldInfo valuesFieldInfo = valuesField.getFieldInfo(); String valuesMethodShortId = "values()" + TypeGen.signature(ArgType.array(clsType));
MethodNode valuesMethod = null;
// remove compiler generated methods // remove compiler generated methods
for (MethodNode mth : cls.getMethods()) { for (MethodNode mth : cls.getMethods()) {
MethodInfo mi = mth.getMethodInfo(); MethodInfo mi = mth.getMethodInfo();
if (mi.isClassInit()) { if (mi.isClassInit() || mth.isNoCode()) {
continue; continue;
} }
String shortId = mi.getShortId(); String shortId = mi.getShortId();
@@ -432,12 +478,33 @@ public class EnumVisitor extends AbstractVisitor {
mth.add(AFlag.DONT_GENERATE); mth.add(AFlag.DONT_GENERATE);
} }
markArgsForSkip(mth); markArgsForSkip(mth);
} else if (shortId.equals(valuesMethod) } else if (mi.getShortId().equals(valuesMethodShortId)) {
|| usesValuesField(mth, valuesFieldInfo) if (isValuesMethod(mth, clsType)) {
|| simpleValueOfMth(mth, clsType)) { valuesMethod = mth;
mth.add(AFlag.DONT_GENERATE);
} else {
// custom values method => rename to resolve conflict with enum method
mth.getMethodInfo().setAlias("valuesCustom");
mth.addAttr(new RenameReasonAttr(mth).append("to resolve conflict with enum method"));
}
} else if (isValuesMethod(mth, clsType)) {
if (!mth.getMethodInfo().getAlias().equals("values") && !mth.getUseIn().isEmpty()) {
// rename to use default values method
mth.getMethodInfo().setAlias("values");
mth.addAttr(new RenameReasonAttr(mth).append("to match enum method name"));
mth.add(AFlag.DONT_RENAME);
}
valuesMethod = mth;
mth.add(AFlag.DONT_GENERATE);
} else if (simpleValueOfMth(mth, clsType)) {
mth.add(AFlag.DONT_GENERATE); mth.add(AFlag.DONT_GENERATE);
} }
} }
FieldInfo valuesFieldInfo = valuesField.getFieldInfo();
for (MethodNode mth : cls.getMethods()) {
// fix access to 'values' field and 'values()' method
fixValuesAccess(mth, valuesFieldInfo, clsType, valuesMethod);
}
} }
private void markArgsForSkip(MethodNode mth) { private void markArgsForSkip(MethodNode mth) {
@@ -458,6 +525,25 @@ public class EnumVisitor extends AbstractVisitor {
return false; return false;
} }
// TODO: support other method patterns ???
private boolean isValuesMethod(MethodNode mth, ArgType clsType) {
ArgType retType = mth.getReturnType();
if (!retType.isArray() || !retType.getArrayElement().equals(clsType)) {
return false;
}
InsnNode returnInsn = BlockUtils.getOnlyOneInsnFromMth(mth);
if (returnInsn == null || returnInsn.getType() != InsnType.RETURN || returnInsn.getArgsCount() != 1) {
return false;
}
InsnNode wrappedInsn = getWrappedInsn(getSingleArg(returnInsn));
IndexInsnNode castInsn = (IndexInsnNode) checkInsnType(wrappedInsn, InsnType.CHECK_CAST);
if (castInsn != null && Objects.equals(castInsn.getIndex(), ArgType.array(clsType))) {
InvokeNode invokeInsn = (InvokeNode) checkInsnType(getWrappedInsn(getSingleArg(castInsn)), InsnType.INVOKE);
return invokeInsn != null && invokeInsn.getCallMth().equals(cloneMth);
}
return false;
}
private boolean simpleValueOfMth(MethodNode mth, ArgType clsType) { private boolean simpleValueOfMth(MethodNode mth, ArgType clsType) {
InsnNode returnInsn = InsnUtils.searchSingleReturnInsn(mth, insn -> insn.getArgsCount() == 1); InsnNode returnInsn = InsnUtils.searchSingleReturnInsn(mth, insn -> insn.getArgsCount() == 1);
if (returnInsn == null) { if (returnInsn == null) {
@@ -472,9 +558,41 @@ public class EnumVisitor extends AbstractVisitor {
return false; return false;
} }
private boolean usesValuesField(MethodNode mth, FieldInfo valuesFieldInfo) { private void fixValuesAccess(MethodNode mth, FieldInfo valuesFieldInfo, ArgType clsType, @Nullable MethodNode valuesMethod) {
MethodInfo mi = mth.getMethodInfo();
if (mi.isConstructor() || mi.isClassInit() || mth.isNoCode() || mth == valuesMethod) {
return;
}
// search value field usage
Predicate<InsnNode> insnTest = insn -> Objects.equals(((IndexInsnNode) insn).getIndex(), valuesFieldInfo); Predicate<InsnNode> insnTest = insn -> Objects.equals(((IndexInsnNode) insn).getIndex(), valuesFieldInfo);
return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null; InsnNode useInsn = InsnUtils.searchInsn(mth, InsnType.SGET, insnTest);
if (useInsn == null) {
return;
}
// replace 'values' field with 'values()' method
InsnUtils.replaceInsns(mth, insn -> {
if (insn.getType() == InsnType.SGET && insnTest.test(insn)) {
MethodInfo valueMth = valuesMethod == null
? getValueMthInfo(mth.root(), clsType)
: valuesMethod.getMethodInfo();
InvokeNode invokeNode = new InvokeNode(valueMth, InvokeType.STATIC, 0);
invokeNode.setResult(insn.getResult());
if (valuesMethod == null) {
// forcing enum method (can overlap and get renamed by custom method)
invokeNode.add(AFlag.FORCE_RAW_NAME);
}
mth.addDebugComment("Replace access to removed values field (" + valuesFieldInfo.getName() + ") with 'values()' method");
return invokeNode;
}
return null;
});
}
private MethodInfo getValueMthInfo(RootNode root, ArgType clsType) {
return MethodInfo.fromDetails(root,
ClassInfo.fromType(root, clsType),
"values",
Collections.emptyList(), ArgType.array(clsType));
} }
private static void processEnumCls(ClassNode cls, EnumField field, ClassNode innerCls) { private static void processEnumCls(ClassNode cls, EnumField field, ClassNode innerCls) {
@@ -524,4 +642,19 @@ public class EnumVisitor extends AbstractVisitor {
} }
return null; return null;
} }
private static class EnumData {
final ClassNode cls;
final MethodNode classInitMth;
final List<BlockNode> staticBlocks;
final List<InsnNode> toRemove = new ArrayList<>();
FieldNode valuesField;
InsnNode valuesInitInsn;
public EnumData(ClassNode cls, MethodNode classInitMth, List<BlockNode> staticBlocks) {
this.cls = cls;
this.classInitMth = classInitMth;
this.staticBlocks = staticBlocks;
}
}
} }
@@ -29,6 +29,7 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnRemover; import jadx.core.utils.InsnRemover;
import jadx.core.utils.ListUtils;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
@@ -45,20 +46,22 @@ public class ExtractFieldInit extends AbstractVisitor {
for (ClassNode inner : cls.getInnerClasses()) { for (ClassNode inner : cls.getInnerClasses()) {
visit(inner); visit(inner);
} }
moveStaticFieldsInit(cls); if (!cls.getFields().isEmpty()) {
moveCommonFieldsInit(cls); moveStaticFieldsInit(cls);
moveCommonFieldsInit(cls);
}
return false; return false;
} }
private static final class FieldInitInfo { private static final class FieldInitInfo {
final FieldNode fieldNode; final FieldNode fieldNode;
final IndexInsnNode putInsn; final IndexInsnNode putInsn;
final boolean singlePath; final boolean canMove;
public FieldInitInfo(FieldNode fieldNode, IndexInsnNode putInsn, boolean singlePath) { public FieldInitInfo(FieldNode fieldNode, IndexInsnNode putInsn, boolean canMove) {
this.fieldNode = fieldNode; this.fieldNode = fieldNode;
this.putInsn = putInsn; this.putInsn = putInsn;
this.singlePath = singlePath; this.canMove = canMove;
} }
} }
@@ -80,6 +83,9 @@ public class ExtractFieldInit extends AbstractVisitor {
|| classInitMth.getBasicBlocks() == null) { || classInitMth.getBasicBlocks() == null) {
return; return;
} }
if (ListUtils.noneMatch(cls.getFields(), FieldNode::isStatic)) {
return;
}
while (processStaticFields(cls, classInitMth)) { while (processStaticFields(cls, classInitMth)) {
// sometimes instructions moved to field init prevent from vars inline -> inline and try again // sometimes instructions moved to field init prevent from vars inline -> inline and try again
CodeShrinkVisitor.shrinkMethod(classInitMth); CodeShrinkVisitor.shrinkMethod(classInitMth);
@@ -116,15 +122,15 @@ public class ExtractFieldInit extends AbstractVisitor {
} }
private static void moveCommonFieldsInit(ClassNode cls) { private static void moveCommonFieldsInit(ClassNode cls) {
if (ListUtils.noneMatch(cls.getFields(), FieldNode::isInstance)) {
return;
}
List<MethodNode> constructors = getConstructorsList(cls); List<MethodNode> constructors = getConstructorsList(cls);
if (constructors.isEmpty()) { if (constructors.isEmpty()) {
return; return;
} }
List<ConstructorInitInfo> infoList = new ArrayList<>(constructors.size()); List<ConstructorInitInfo> infoList = new ArrayList<>(constructors.size());
for (MethodNode constructorMth : constructors) { for (MethodNode constructorMth : constructors) {
if (constructorMth.isNoCode()) {
return;
}
List<FieldInitInfo> inits = collectFieldsInit(cls, constructorMth, InsnType.IPUT); List<FieldInitInfo> inits = collectFieldsInit(cls, constructorMth, InsnType.IPUT);
filterFieldsInit(inits); filterFieldsInit(inits);
if (inits.isEmpty()) { if (inits.isEmpty()) {
@@ -168,19 +174,25 @@ public class ExtractFieldInit extends AbstractVisitor {
Set<BlockNode> singlePathBlocks = new HashSet<>(); Set<BlockNode> singlePathBlocks = new HashSet<>();
BlockUtils.visitSinglePath(mth.getEnterBlock(), singlePathBlocks::add); BlockUtils.visitSinglePath(mth.getEnterBlock(), singlePathBlocks::add);
boolean canReorder = true;
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) { for (InsnNode insn : block.getInstructions()) {
boolean fieldInsn = false;
if (insn.getType() == putType) { if (insn.getType() == putType) {
IndexInsnNode putInsn = (IndexInsnNode) insn; IndexInsnNode putInsn = (IndexInsnNode) insn;
FieldInfo field = (FieldInfo) putInsn.getIndex(); FieldInfo field = (FieldInfo) putInsn.getIndex();
if (field.getDeclClass().equals(cls.getClassInfo())) { if (field.getDeclClass().equals(cls.getClassInfo())) {
FieldNode fn = cls.searchField(field); FieldNode fn = cls.searchField(field);
if (fn != null) { if (fn != null) {
boolean singlePath = singlePathBlocks.contains(block); boolean canMove = canReorder && singlePathBlocks.contains(block);
fieldsInit.add(new FieldInitInfo(fn, putInsn, singlePath)); fieldsInit.add(new FieldInitInfo(fn, putInsn, canMove));
fieldInsn = true;
} }
} }
} }
if (!fieldInsn && canReorder && !insn.canReorder()) {
canReorder = false;
}
} }
} }
return fieldsInit; return fieldsInit;
@@ -226,14 +238,14 @@ public class ExtractFieldInit extends AbstractVisitor {
} }
private static boolean checkInsn(FieldInitInfo initInfo) { private static boolean checkInsn(FieldInitInfo initInfo) {
if (!initInfo.singlePath) { if (!initInfo.canMove) {
return false; return false;
} }
IndexInsnNode insn = initInfo.putInsn; IndexInsnNode insn = initInfo.putInsn;
InsnArg arg = insn.getArg(0); InsnArg arg = insn.getArg(0);
if (arg.isInsnWrap()) { if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn(); InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
if (!wrapInsn.canReorderRecursive() && insn.contains(AType.EXC_CATCH)) { if (!wrapInsn.canReorder() && insn.contains(AType.EXC_CATCH)) {
return false; return false;
} }
} else { } else {
@@ -364,7 +376,7 @@ public class ExtractFieldInit extends AbstractVisitor {
AccessInfo accFlags = mth.getAccessFlags(); AccessInfo accFlags = mth.getAccessFlags();
if (!accFlags.isStatic() && accFlags.isConstructor()) { if (!accFlags.isStatic() && accFlags.isConstructor()) {
list.add(mth); list.add(mth);
if (BlockUtils.isAllBlocksEmpty(mth.getBasicBlocks())) { if (mth.isNoCode() || BlockUtils.isAllBlocksEmpty(mth.getBasicBlocks())) {
return Collections.emptyList(); return Collections.emptyList();
} }
} }
@@ -372,8 +384,14 @@ public class ExtractFieldInit extends AbstractVisitor {
return list; return list;
} }
private static void addFieldInitAttr(MethodNode mth, FieldNode field, InsnNode insn) { private static void addFieldInitAttr(MethodNode mth, FieldNode field, IndexInsnNode putInsn) {
InsnNode assignInsn = InsnNode.wrapArg(insn.getArg(0)); InsnNode assignInsn;
InsnArg fldArg = putInsn.getArg(0);
if (fldArg.isInsnWrap()) {
assignInsn = ((InsnWrapArg) fldArg).getWrapInsn();
} else {
assignInsn = InsnNode.wrapArg(fldArg);
}
field.addAttr(new FieldInitInsnAttr(mth, assignInsn)); field.addAttr(new FieldInitInsnAttr(mth, assignInsn));
} }
} }
@@ -80,13 +80,47 @@ public class MarkMethodsForInline extends AbstractVisitor {
return addInlineAttr(mth, insn); return addInlineAttr(mth, insn);
} }
if (insnsCount == 2 && insns.get(1).getType() == InsnType.RETURN) { if (insnsCount == 2 && insns.get(1).getType() == InsnType.RETURN) {
// synthetic field setter InsnNode firstInsn = insns.get(0);
return addInlineAttr(mth, insns.get(0)); InsnNode retInsn = insns.get(1);
if (retInsn.getArgsCount() == 0
|| isSyntheticAccessPattern(mth, firstInsn, retInsn)) {
return addInlineAttr(mth, firstInsn);
}
} }
// TODO: inline field arithmetics. Disabled tests: TestAnonymousClass3a and TestAnonymousClass5 // TODO: inline field arithmetics. Disabled tests: TestAnonymousClass3a and TestAnonymousClass5
return null; return null;
} }
private static boolean isSyntheticAccessPattern(MethodNode mth, InsnNode firstInsn, InsnNode retInsn) {
List<RegisterArg> mthRegs = mth.getArgRegs();
switch (firstInsn.getType()) {
case IGET:
return mthRegs.size() == 1
&& retInsn.getArg(0).isSameVar(firstInsn.getResult())
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
case SGET:
return mthRegs.size() == 0
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
case IPUT:
return mthRegs.size() == 2
&& retInsn.getArg(0).isSameVar(mthRegs.get(1))
&& firstInsn.getArg(0).isSameVar(mthRegs.get(1))
&& firstInsn.getArg(1).isSameVar(mthRegs.get(0));
case SPUT:
return mthRegs.size() == 1
&& retInsn.getArg(0).isSameVar(mthRegs.get(0))
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
case INVOKE:
return mthRegs.size() >= 1
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0))
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
default:
return false;
}
}
private static MethodInlineAttr addInlineAttr(MethodNode mth, InsnNode insn) { private static MethodInlineAttr addInlineAttr(MethodNode mth, InsnNode insn) {
if (!fixVisibilityOfInlineCode(mth, insn)) { if (!fixVisibilityOfInlineCode(mth, insn)) {
return null; return null;
@@ -90,7 +90,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
for (ArgType superType : superData.getSuperTypes()) { for (ArgType superType : superData.getSuperTypes()) {
ClassNode classNode = mth.root().resolveClass(superType); ClassNode classNode = mth.root().resolveClass(superType);
if (classNode != null) { if (classNode != null) {
MethodNode ovrdMth = searchOverriddenMethod(classNode, signature); MethodNode ovrdMth = searchOverriddenMethod(classNode, mth, signature);
if (ovrdMth != null) { if (ovrdMth != null) {
if (isMethodVisibleInCls(ovrdMth, cls)) { if (isMethodVisibleInCls(ovrdMth, cls)) {
overrideList.add(ovrdMth); overrideList.add(ovrdMth);
@@ -107,6 +107,8 @@ public class OverrideMethodVisitor extends AbstractVisitor {
Map<String, ClspMethod> methodsMap = clsDetails.getMethodsMap(); Map<String, ClspMethod> methodsMap = clsDetails.getMethodsMap();
for (Map.Entry<String, ClspMethod> entry : methodsMap.entrySet()) { for (Map.Entry<String, ClspMethod> entry : methodsMap.entrySet()) {
String mthShortId = entry.getKey(); String mthShortId = entry.getKey();
// do not check full signature, classpath methods can be trusted
// i.e. doesn't contain methods with same signature in one class
if (mthShortId.startsWith(signature)) { if (mthShortId.startsWith(signature)) {
overrideList.add(entry.getValue()); overrideList.add(entry.getValue());
break; break;
@@ -130,12 +132,30 @@ public class OverrideMethodVisitor extends AbstractVisitor {
} }
@Nullable @Nullable
private MethodNode searchOverriddenMethod(ClassNode cls, String signature) { private MethodNode searchOverriddenMethod(ClassNode cls, MethodNode mth, String signature) {
// search by exact full signature (with return value) to fight obfuscation (see test
// 'TestOverrideWithSameName')
String shortId = mth.getMethodInfo().getShortId();
for (MethodNode supMth : cls.getMethods()) { for (MethodNode supMth : cls.getMethods()) {
if (!supMth.getAccessFlags().isStatic() && supMth.getMethodInfo().getShortId().startsWith(signature)) { if (supMth.getMethodInfo().getShortId().equals(shortId) && !supMth.getAccessFlags().isStatic()) {
return supMth; return supMth;
} }
} }
// search by signature without return value and check if return value is wider type
for (MethodNode supMth : cls.getMethods()) {
if (supMth.getMethodInfo().getShortId().startsWith(signature) && !supMth.getAccessFlags().isStatic()) {
TypeCompare typeCompare = cls.root().getTypeCompare();
ArgType supRetType = supMth.getMethodInfo().getReturnType();
ArgType mthRetType = mth.getMethodInfo().getReturnType();
TypeCompareEnum res = typeCompare.compareTypes(supRetType, mthRetType);
if (res.isWider()) {
return supMth;
}
if (res == TypeCompareEnum.UNKNOWN || res == TypeCompareEnum.CONFLICT) {
mth.addDebugComment("Possible override for method " + supMth.getMethodInfo().getFullId());
}
}
}
return null; return null;
} }
@@ -10,9 +10,11 @@ import java.util.Set;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr; import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr.InlineType;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
@@ -30,6 +32,7 @@ import jadx.core.utils.exceptions.JadxException;
UsageInfoVisitor.class UsageInfoVisitor.class
} }
) )
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public class ProcessAnonymous extends AbstractVisitor { public class ProcessAnonymous extends AbstractVisitor {
private boolean inlineAnonymousClasses; private boolean inlineAnonymousClasses;
@@ -64,17 +67,26 @@ public class ProcessAnonymous extends AbstractVisitor {
if (!canBeAnonymous(cls)) { if (!canBeAnonymous(cls)) {
return; return;
} }
MethodNode anonymousConstructor = checkUsage(cls); MethodNode anonymousConstructor = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
if (anonymousConstructor == null) { if (anonymousConstructor == null) {
return; return;
} }
InlineType inlineType = checkUsage(cls, anonymousConstructor);
if (inlineType == null) {
return;
}
ArgType baseType = getBaseType(cls); ArgType baseType = getBaseType(cls);
if (baseType == null) { if (baseType == null) {
return; return;
} }
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass(); ClassNode outerCls;
if (inlineType == InlineType.INSTANCE_FIELD) {
outerCls = cls.getUseInMth().get(0).getParentClass();
} else {
outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
}
outerCls.addInlinedClass(cls); outerCls.addInlinedClass(cls);
cls.addAttr(new AnonymousClassAttr(outerCls, baseType)); cls.addAttr(new AnonymousClassAttr(outerCls, baseType, inlineType));
cls.add(AFlag.DONT_GENERATE); cls.add(AFlag.DONT_GENERATE);
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR); anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
@@ -202,14 +214,11 @@ public class ProcessAnonymous extends AbstractVisitor {
* Checks: * Checks:
* - class have only one constructor which used only once (allow common code for field init) * - class have only one constructor which used only once (allow common code for field init)
* - methods or fields not used outside (allow only nested inner classes with synthetic usage) * - methods or fields not used outside (allow only nested inner classes with synthetic usage)
* - if constructor used only in class init check if possible inline by instance field
* *
* @return anonymous constructor method * @return decided inline type
*/ */
private static MethodNode checkUsage(ClassNode cls) { private static InlineType checkUsage(ClassNode cls, MethodNode ctr) {
MethodNode ctr = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
if (ctr == null) {
return null;
}
if (ctr.getUseIn().size() != 1) { if (ctr.getUseIn().size() != 1) {
// check if used in common field init in all constructors // check if used in common field init in all constructors
if (!checkForCommonFieldInit(ctr)) { if (!checkForCommonFieldInit(ctr)) {
@@ -219,9 +228,30 @@ public class ProcessAnonymous extends AbstractVisitor {
MethodNode ctrUseMth = ctr.getUseIn().get(0); MethodNode ctrUseMth = ctr.getUseIn().get(0);
ClassNode ctrUseCls = ctrUseMth.getParentClass(); ClassNode ctrUseCls = ctrUseMth.getParentClass();
if (ctrUseCls.equals(cls)) { if (ctrUseCls.equals(cls)) {
if (checkForInstanceFieldUsage(cls, ctr)) {
return InlineType.INSTANCE_FIELD;
}
// exclude self usage // exclude self usage
return null; return null;
} }
if (ctrUseCls.getTopParentClass().equals(cls)) {
// exclude usage inside inner classes
return null;
}
if (!checkMethodsUsage(cls, ctr, ctrUseMth)) {
return null;
}
for (FieldNode field : cls.getFields()) {
for (MethodNode useMth : field.getUseIn()) {
if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
return null;
}
}
}
return InlineType.CONSTRUCTOR;
}
private static boolean checkMethodsUsage(ClassNode cls, MethodNode ctr, MethodNode ctrUseMth) {
for (MethodNode mth : cls.getMethods()) { for (MethodNode mth : cls.getMethods()) {
if (mth == ctr) { if (mth == ctr) {
continue; continue;
@@ -231,18 +261,46 @@ public class ProcessAnonymous extends AbstractVisitor {
continue; continue;
} }
if (badMethodUsage(cls, useMth, mth.getAccessFlags())) { if (badMethodUsage(cls, useMth, mth.getAccessFlags())) {
return null; return false;
} }
} }
} }
return true;
}
private static boolean checkForInstanceFieldUsage(ClassNode cls, MethodNode ctr) {
MethodNode ctrUseMth = ctr.getUseIn().get(0);
if (!ctrUseMth.getMethodInfo().isClassInit()) {
return false;
}
FieldNode instFld = ListUtils.filterOnlyOne(cls.getFields(),
f -> f.getAccessFlags().containsFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL)
&& f.getFieldInfo().getType().equals(cls.getClassInfo().getType()));
if (instFld == null) {
return false;
}
List<MethodNode> instFldUseIn = instFld.getUseIn();
if (instFldUseIn.size() != 2
|| !instFldUseIn.contains(ctrUseMth) // initialized in class init
|| !instFldUseIn.containsAll(cls.getUseInMth()) // class used only with this field
) {
return false;
}
if (!checkMethodsUsage(cls, ctr, ctrUseMth)) {
return false;
}
for (FieldNode field : cls.getFields()) { for (FieldNode field : cls.getFields()) {
if (field == instFld) {
continue;
}
for (MethodNode useMth : field.getUseIn()) { for (MethodNode useMth : field.getUseIn()) {
if (badMethodUsage(cls, useMth, field.getAccessFlags())) { if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
return null; return false;
} }
} }
} }
return ctr; instFld.add(AFlag.INLINE_INSTANCE_FIELD);
return true;
} }
private static boolean badMethodUsage(ClassNode cls, MethodNode useMth, AccessInfo accessFlags) { private static boolean badMethodUsage(ClassNode cls, MethodNode useMth, AccessInfo accessFlags) {
@@ -293,6 +351,13 @@ public class ProcessAnonymous extends AbstractVisitor {
if (cls.root().getClsp().isImplements(superCls.getObject(), interfaceType.getObject())) { if (cls.root().getClsp().isImplements(superCls.getObject(), interfaceType.getObject())) {
return superCls; return superCls;
} }
if (cls.root().getArgs().isAllowInlineKotlinLambda()) {
if (superCls.getObject().equals("kotlin.jvm.internal.Lambda")) {
// Inline such class with have different semantic: missing 'arity' property.
// For now, it is unclear how it may affect code execution.
return interfaceType;
}
}
return null; return null;
} }
} }
@@ -1,12 +1,12 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.usage.UsageInfoVisitor; import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
@JadxVisitor( @JadxVisitor(
@@ -45,7 +45,14 @@ public class ProcessMethodsForInline extends AbstractVisitor {
} }
AccessInfo accessFlags = mth.getAccessFlags(); AccessInfo accessFlags = mth.getAccessFlags();
boolean isSynthetic = accessFlags.isSynthetic() || mth.getName().contains("$"); boolean isSynthetic = accessFlags.isSynthetic() || mth.getName().contains("$");
return isSynthetic && (accessFlags.isStatic() || mth.isConstructor()); return isSynthetic && canInlineMethod(mth, accessFlags);
}
private static boolean canInlineMethod(MethodNode mth, AccessInfo accessFlags) {
if (accessFlags.isStatic()) {
return true;
}
return mth.isConstructor() && mth.root().getArgs().isInlineAnonymousClasses();
} }
private static void fixClassDependencies(MethodNode mth) { private static void fixClassDependencies(MethodNode mth) {
@@ -54,8 +61,14 @@ public class ProcessMethodsForInline extends AbstractVisitor {
// remove possible cross dependency // remove possible cross dependency
// to force class with inline method to be processed before its usage // to force class with inline method to be processed before its usage
ClassNode useTopCls = useInMth.getTopParentClass(); ClassNode useTopCls = useInMth.getTopParentClass();
parentClass.setDependencies(ListUtils.safeRemoveAndTrim(parentClass.getDependencies(), useTopCls)); if (useTopCls != parentClass) {
useTopCls.addCodegenDep(parentClass); parentClass.removeDependency(useTopCls);
useTopCls.addCodegenDep(parentClass);
if (Consts.DEBUG_USAGE) {
parentClass.addDebugComment("Remove dependency: " + useTopCls + " to inline " + mth);
useTopCls.addDebugComment("Add dependency: " + parentClass + " to inline " + mth);
}
}
} }
} }
} }
@@ -2,8 +2,12 @@ package jadx.core.dex.visitors;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
@@ -18,7 +22,6 @@ import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
public class SignatureProcessor extends AbstractVisitor { public class SignatureProcessor extends AbstractVisitor {
private RootNode root; private RootNode root;
@Override @Override
@@ -55,12 +58,54 @@ public class SignatureProcessor extends AbstractVisitor {
break; break;
} }
} }
cls.updateGenericClsData(superClass, interfaces, generics); generics = fixTypeParamDeclarations(cls, generics, superClass, interfaces);
cls.updateGenericClsData(generics, superClass, interfaces);
} catch (Exception e) { } catch (Exception e) {
cls.addWarnComment("Failed to parse class signature: " + sp.getSignature(), e); cls.addWarnComment("Failed to parse class signature: " + sp.getSignature(), e);
} }
} }
/**
* Add missing type parameters from super type and interfaces to make code compilable
*/
private static List<ArgType> fixTypeParamDeclarations(ClassNode cls,
List<ArgType> generics, ArgType superClass, List<ArgType> interfaces) {
if (interfaces.isEmpty() && superClass.equals(ArgType.OBJECT)) {
return generics;
}
Set<String> typeParams = new HashSet<>();
superClass.visitTypes(t -> addGenericType(typeParams, t));
interfaces.forEach(i -> i.visitTypes(t -> addGenericType(typeParams, t)));
if (typeParams.isEmpty()) {
return generics;
}
List<ArgType> knownTypeParams;
if (cls.isInner()) {
knownTypeParams = new ArrayList<>(generics);
cls.visitParentClasses(p -> knownTypeParams.addAll(p.getGenericTypeParameters()));
} else {
knownTypeParams = generics;
}
for (ArgType declTypeParam : knownTypeParams) {
typeParams.remove(declTypeParam.getObject());
}
if (typeParams.isEmpty()) {
return generics;
}
cls.addInfoComment("Add missing generic type declarations: " + typeParams);
List<ArgType> fixedGenerics = new ArrayList<>(generics.size() + typeParams.size());
fixedGenerics.addAll(generics);
typeParams.stream().sorted().map(ArgType::genericType).forEach(fixedGenerics::add);
return fixedGenerics;
}
private static @Nullable Object addGenericType(Set<String> usedTypeParameters, ArgType t) {
if (t.isGenericType()) {
usedTypeParameters.add(t.getObject());
}
return null;
}
private ArgType validateClsType(ClassNode cls, ArgType candidateType, ArgType currentType) { private ArgType validateClsType(ClassNode cls, ArgType candidateType, ArgType currentType) {
if (!candidateType.isObject()) { if (!candidateType.isObject()) {
cls.addWarnComment("Incorrect class signature, class is not object: " + SignatureParser.getSignature(cls)); cls.addWarnComment("Incorrect class signature, class is not object: " + SignatureParser.getSignature(cls));
@@ -50,7 +50,7 @@ public class BlockExceptionHandler {
return false; return false;
} }
BlockProcessor.updateCleanSuccessors(mth); BlockProcessor.updateCleanSuccessors(mth);
BlockProcessor.computeDominanceFrontier(mth); DominatorTree.computeDominanceFrontier(mth);
processCatchAttr(mth); processCatchAttr(mth);
initExcHandlers(mth); initExcHandlers(mth);
@@ -171,10 +171,6 @@ public class BlockExceptionHandler {
} }
} }
protected static void removeTmpConnections(MethodNode mth) {
mth.getBasicBlocks().forEach(BlockExceptionHandler::removeTmpConnection);
}
private static void removeTmpConnection(BlockNode block) { private static void removeTmpConnection(BlockNode block) {
TmpEdgeAttr tmpEdgeAttr = block.get(AType.TMP_EDGE); TmpEdgeAttr tmpEdgeAttr = block.get(AType.TMP_EDGE);
if (tmpEdgeAttr != null) { if (tmpEdgeAttr != null) {
@@ -402,6 +398,13 @@ public class BlockExceptionHandler {
} }
BlockNode topDom = BlockUtils.getCommonDominator(mth, blocks); BlockNode topDom = BlockUtils.getCommonDominator(mth, blocks);
if (topDom != null) { if (topDom != null) {
// dominator always return one up block if blocks already contains dominator, use successor instead
if (topDom.getSuccessors().size() == 1) {
BlockNode upBlock = topDom.getSuccessors().get(0);
if (blocks.contains(upBlock)) {
return upBlock;
}
}
return adjustTopBlock(topDom); return adjustTopBlock(topDom);
} }
throw new JadxRuntimeException("Failed to find top block for try-catch from: " + blocks); throw new JadxRuntimeException("Failed to find top block for try-catch from: " + blocks);
@@ -1,11 +1,7 @@
package jadx.core.dex.visitors.blocks; package jadx.core.dex.visitors.blocks;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -31,7 +27,6 @@ import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.visitors.blocks.BlockSplitter.connect; import static jadx.core.dex.visitors.blocks.BlockSplitter.connect;
import static jadx.core.utils.EmptyBitSet.EMPTY;
public class BlockProcessor extends AbstractVisitor { public class BlockProcessor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(BlockProcessor.class); private static final Logger LOG = LoggerFactory.getLogger(BlockProcessor.class);
@@ -50,29 +45,23 @@ public class BlockProcessor extends AbstractVisitor {
computeDominators(mth); computeDominators(mth);
if (independentBlockTreeMod(mth)) { if (independentBlockTreeMod(mth)) {
checkForUnreachableBlocks(mth); checkForUnreachableBlocks(mth);
clearBlocksState(mth);
computeDominators(mth); computeDominators(mth);
} }
if (FixMultiEntryLoops.process(mth)) { if (FixMultiEntryLoops.process(mth)) {
clearBlocksState(mth);
computeDominators(mth); computeDominators(mth);
} }
updateCleanSuccessors(mth); updateCleanSuccessors(mth);
int i = 0; int i = 0;
while (modifyBlocksTree(mth)) { while (modifyBlocksTree(mth)) {
// revert calculations
clearBlocksState(mth);
// recalculate dominators tree
computeDominators(mth); computeDominators(mth);
if (i++ > 100) { if (i++ > 100) {
throw new JadxRuntimeException("CFG modification limit reached, blocks count: " + mth.getBasicBlocks().size()); throw new JadxRuntimeException("CFG modification limit reached, blocks count: " + mth.getBasicBlocks().size());
} }
} }
checkForUnreachableBlocks(mth); checkForUnreachableBlocks(mth);
computeDominanceFrontier(mth); DominatorTree.computeDominanceFrontier(mth);
registerLoops(mth); registerLoops(mth);
processNestedLoops(mth); processNestedLoops(mth);
@@ -209,139 +198,9 @@ public class BlockProcessor extends AbstractVisitor {
} }
private static void computeDominators(MethodNode mth) { private static void computeDominators(MethodNode mth) {
List<BlockNode> basicBlocks = mth.getBasicBlocks(); clearBlocksState(mth);
int nBlocks = basicBlocks.size(); DominatorTree.compute(mth);
for (int i = 0; i < nBlocks; i++) {
BlockNode block = basicBlocks.get(i);
block.setId(i);
block.setDoms(new BitSet(nBlocks));
block.getDoms().set(0, nBlocks);
}
BlockNode entryBlock = mth.getEnterBlock();
calcDominators(basicBlocks, entryBlock);
markLoops(mth); markLoops(mth);
// clear self dominance
basicBlocks.forEach(block -> {
block.getDoms().clear(block.getId());
if (block.getDoms().isEmpty()) {
block.setDoms(EMPTY);
}
});
calcImmediateDominators(mth, basicBlocks, entryBlock);
}
private static void calcDominators(List<BlockNode> basicBlocks, BlockNode entryBlock) {
entryBlock.getDoms().clear();
entryBlock.getDoms().set(entryBlock.getId());
BitSet domSet = new BitSet(basicBlocks.size());
boolean changed;
do {
changed = false;
for (BlockNode block : basicBlocks) {
if (block == entryBlock) {
continue;
}
BitSet d = block.getDoms();
if (!changed) {
domSet.clear();
domSet.or(d);
}
for (BlockNode pred : block.getPredecessors()) {
d.and(pred.getDoms());
}
d.set(block.getId());
if (!changed && !d.equals(domSet)) {
changed = true;
}
}
} while (changed);
}
private static void calcImmediateDominators(MethodNode mth, List<BlockNode> basicBlocks, BlockNode entryBlock) {
for (BlockNode block : basicBlocks) {
if (block == entryBlock) {
continue;
}
BlockNode idom;
List<BlockNode> preds = block.getPredecessors();
if (preds.size() == 1) {
idom = preds.get(0);
} else {
BitSet bs = new BitSet(block.getDoms().length());
bs.or(block.getDoms());
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
BlockNode dom = basicBlocks.get(i);
bs.andNot(dom.getDoms());
}
if (bs.cardinality() != 1) {
throw new JadxRuntimeException("Can't find immediate dominator for block " + block
+ " in " + bs + " preds:" + preds);
}
idom = basicBlocks.get(bs.nextSetBit(0));
}
block.setIDom(idom);
idom.addDominatesOn(block);
}
}
static void computeDominanceFrontier(MethodNode mth) {
mth.getExitBlock().setDomFrontier(EMPTY);
List<BlockNode> domSortedBlocks = new ArrayList<>(mth.getBasicBlocks().size());
Deque<BlockNode> stack = new LinkedList<>();
stack.push(mth.getEnterBlock());
while (!stack.isEmpty()) {
BlockNode node = stack.pop();
for (BlockNode dominated : node.getDominatesOn()) {
stack.push(dominated);
}
domSortedBlocks.add(node);
}
Collections.reverse(domSortedBlocks);
for (BlockNode block : domSortedBlocks) {
try {
computeBlockDF(mth, block);
} catch (Exception e) {
throw new JadxRuntimeException("Failed compute block dominance frontier", e);
}
}
}
private static void computeBlockDF(MethodNode mth, BlockNode block) {
if (block.getDomFrontier() != null) {
return;
}
List<BlockNode> blocks = mth.getBasicBlocks();
BitSet domFrontier = null;
for (BlockNode s : block.getSuccessors()) {
if (s.getIDom() != block) {
if (domFrontier == null) {
domFrontier = new BitSet(blocks.size());
}
domFrontier.set(s.getId());
}
}
for (BlockNode c : block.getDominatesOn()) {
BitSet frontier = c.getDomFrontier();
if (frontier == null) {
throw new JadxRuntimeException("Dominance frontier not calculated for dominated block: " + c + ", from: " + block);
}
for (int p = frontier.nextSetBit(0); p >= 0; p = frontier.nextSetBit(p + 1)) {
if (blocks.get(p).getIDom() != block) {
if (domFrontier == null) {
domFrontier = new BitSet(blocks.size());
}
domFrontier.set(p);
}
}
}
if (domFrontier == null || domFrontier.isEmpty()) {
domFrontier = EMPTY;
}
block.setDomFrontier(domFrontier);
} }
private static void markLoops(MethodNode mth) { private static void markLoops(MethodNode mth) {
@@ -349,7 +208,7 @@ public class BlockProcessor extends AbstractVisitor {
// Every successor that dominates its predecessor is a header of a loop, // Every successor that dominates its predecessor is a header of a loop,
// block -> successor is a back edge. // block -> successor is a back edge.
block.getSuccessors().forEach(successor -> { block.getSuccessors().forEach(successor -> {
if (block.getDoms().get(successor.getId())) { if (block.getDoms().get(successor.getId()) || block == successor) {
successor.add(AFlag.LOOP_START); successor.add(AFlag.LOOP_START);
block.add(AFlag.LOOP_END); block.add(AFlag.LOOP_END);
@@ -572,7 +431,7 @@ public class BlockProcessor extends AbstractVisitor {
BlockNode loopHeader = loop.getStart(); BlockNode loopHeader = loop.getStart();
List<BlockNode> preds = loopHeader.getPredecessors(); List<BlockNode> preds = loopHeader.getPredecessors();
if (preds.size() > 2) { if (preds.size() > 2) {
List<BlockNode> blocks = new LinkedList<>(preds); List<BlockNode> blocks = new ArrayList<>(preds);
blocks.removeIf(block -> block.contains(AFlag.LOOP_END)); blocks.removeIf(block -> block.contains(AFlag.LOOP_END));
BlockNode first = blocks.remove(0); BlockNode first = blocks.remove(0);
BlockNode preHeader = BlockSplitter.insertBlockBetween(mth, first, loopHeader); BlockNode preHeader = BlockSplitter.insertBlockBetween(mth, first, loopHeader);
@@ -137,8 +137,9 @@ public class BlockSplitter extends AbstractVisitor {
} }
static BlockNode startNewBlock(MethodNode mth, int offset) { static BlockNode startNewBlock(MethodNode mth, int offset) {
BlockNode block = new BlockNode(mth.getBasicBlocks().size(), offset); List<BlockNode> blocks = mth.getBasicBlocks();
mth.getBasicBlocks().add(block); BlockNode block = new BlockNode(mth.getNextBlockCId(), blocks.size(), offset);
blocks.add(block);
return block; return block;
} }
@@ -391,7 +392,8 @@ public class BlockSplitter extends AbstractVisitor {
&& block.getSuccessors().size() <= 1 && block.getSuccessors().size() <= 1
&& !block.getPredecessors().isEmpty() && !block.getPredecessors().isEmpty()
&& !block.contains(AFlag.MTH_ENTER_BLOCK) && !block.contains(AFlag.MTH_ENTER_BLOCK)
&& !block.contains(AFlag.MTH_EXIT_BLOCK); && !block.contains(AFlag.MTH_EXIT_BLOCK)
&& !block.getSuccessors().contains(block); // no self loop
} }
static void collectSuccessors(BlockNode startBlock, BlockNode methodEnterBlock, Set<BlockNode> toRemove) { static void collectSuccessors(BlockNode startBlock, BlockNode methodEnterBlock, Set<BlockNode> toRemove) {
@@ -0,0 +1,168 @@
package jadx.core.dex.visitors.blocks;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.EmptyBitSet;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Build dominator tree based on the algorithm described in paper:
* Cooper, Keith D.; Harvey, Timothy J; Kennedy, Ken (2001).
* "A Simple, Fast Dominance Algorithm"
* http://www.hipersoft.rice.edu/grads/publications/dom14.pdf
*/
@SuppressWarnings("JavadocLinkAsPlainText")
public class DominatorTree {
public static void compute(MethodNode mth) {
List<BlockNode> sorted = sortBlocks(mth);
BlockNode[] doms = build(sorted);
apply(sorted, doms);
}
private static List<BlockNode> sortBlocks(MethodNode mth) {
int blocksCount = mth.getBasicBlocks().size();
List<BlockNode> sorted = new ArrayList<>(blocksCount);
BlockUtils.dfsVisit(mth, sorted::add);
if (sorted.size() != blocksCount) {
throw new JadxRuntimeException("Found unreachable blocks");
}
mth.setBasicBlocks(sorted);
return sorted;
}
@NotNull
private static BlockNode[] build(List<BlockNode> sorted) {
int blocksCount = sorted.size();
BlockNode[] doms = new BlockNode[blocksCount];
doms[0] = sorted.get(0);
boolean changed = true;
while (changed) {
changed = false;
for (int blockId = 1; blockId < blocksCount; blockId++) {
BlockNode b = sorted.get(blockId);
List<BlockNode> preds = b.getPredecessors();
int pickedPred = -1;
BlockNode newIDom = null;
for (BlockNode pred : preds) {
int id = pred.getId();
if (doms[id] != null) {
newIDom = pred;
pickedPred = id;
break;
}
}
if (newIDom == null) {
throw new JadxRuntimeException("No predecessors for block: " + b);
}
for (BlockNode predBlock : preds) {
int predId = predBlock.getId();
if (predId == pickedPred) {
continue;
}
if (doms[predId] != null) {
newIDom = intersect(sorted, doms, predBlock, newIDom);
}
}
if (doms[blockId] != newIDom) {
doms[blockId] = newIDom;
changed = true;
}
}
}
return doms;
}
private static BlockNode intersect(List<BlockNode> sorted, BlockNode[] doms, BlockNode b1, BlockNode b2) {
int f1 = b1.getId();
int f2 = b2.getId();
while (f1 != f2) {
while (f1 > f2) {
f1 = doms[f1].getId();
}
while (f2 > f1) {
f2 = doms[f2].getId();
}
}
return sorted.get(f1);
}
private static void apply(List<BlockNode> sorted, BlockNode[] doms) {
BlockNode enterBlock = sorted.get(0);
enterBlock.setDoms(EmptyBitSet.EMPTY);
enterBlock.setIDom(null);
int blocksCount = sorted.size();
for (int i = 1; i < blocksCount; i++) {
BlockNode block = sorted.get(i);
BlockNode idom = doms[i];
block.setIDom(idom);
idom.addDominatesOn(block);
BitSet domBS = collectDoms(doms, idom);
domBS.clear(i);
block.setDoms(domBS);
}
}
private static BitSet collectDoms(BlockNode[] doms, BlockNode idom) {
BitSet domBS = new BitSet(doms.length);
BlockNode nextIDom = idom;
while (true) {
int id = nextIDom.getId();
if (domBS.get(id)) {
break;
}
domBS.set(id);
BitSet curDoms = nextIDom.getDoms();
if (curDoms != null) {
// use already collected set
domBS.or(curDoms);
break;
}
nextIDom = doms[id];
}
return domBS;
}
public static void computeDominanceFrontier(MethodNode mth) {
List<BlockNode> blocks = mth.getBasicBlocks();
for (BlockNode block : blocks) {
block.setDomFrontier(null);
}
int blocksCount = blocks.size();
for (BlockNode block : blocks) {
List<BlockNode> preds = block.getPredecessors();
if (preds.size() >= 2) {
BlockNode idom = block.getIDom();
for (BlockNode pred : preds) {
BlockNode runner = pred;
while (runner != idom) {
addToDF(runner, block, blocksCount);
runner = runner.getIDom();
}
}
}
}
for (BlockNode block : blocks) {
BitSet df = block.getDomFrontier();
if (df == null || df.isEmpty()) {
block.setDomFrontier(EmptyBitSet.EMPTY);
}
}
}
private static void addToDF(BlockNode block, BlockNode dfBlock, int blocksCount) {
BitSet df = block.getDomFrontier();
if (df == null) {
df = new BitSet(blocksCount);
block.setDomFrontier(df);
}
df.set(dfBlock.getId());
}
}
@@ -6,6 +6,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
@@ -19,6 +20,10 @@ public class FinallyExtractInfo {
private final InsnsSlice finallyInsnsSlice = new InsnsSlice(); private final InsnsSlice finallyInsnsSlice = new InsnsSlice();
private final BlockNode startBlock; private final BlockNode startBlock;
private InsnsSlice curDupSlice;
private List<InsnNode> curDupInsns;
private int curDupInsnsOffset;
public FinallyExtractInfo(MethodNode mth, ExceptionHandler finallyHandler, BlockNode startBlock, List<BlockNode> allHandlerBlocks) { public FinallyExtractInfo(MethodNode mth, ExceptionHandler finallyHandler, BlockNode startBlock, List<BlockNode> allHandlerBlocks) {
this.mth = mth; this.mth = mth;
this.finallyHandler = finallyHandler; this.finallyHandler = finallyHandler;
@@ -54,6 +59,27 @@ public class FinallyExtractInfo {
return startBlock; return startBlock;
} }
public InsnsSlice getCurDupSlice() {
return curDupSlice;
}
public void setCurDupSlice(InsnsSlice curDupSlice) {
this.curDupSlice = curDupSlice;
}
public List<InsnNode> getCurDupInsns() {
return curDupInsns;
}
public int getCurDupInsnsOffset() {
return curDupInsnsOffset;
}
public void setCurDupInsns(List<InsnNode> insns, int offset) {
this.curDupInsns = insns;
this.curDupInsnsOffset = offset;
}
@Override @Override
public String toString() { public String toString() {
return "FinallyExtractInfo{" return "FinallyExtractInfo{"
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
@@ -28,6 +29,7 @@ import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.JadxVisitor; import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnList;
import jadx.core.utils.ListUtils; import jadx.core.utils.ListUtils;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
@@ -376,6 +378,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
* 'Finally' instructions can start in the middle of the first block. * 'Finally' instructions can start in the middle of the first block.
*/ */
private static InsnsSlice isStartBlock(BlockNode dupBlock, BlockNode finallyBlock, FinallyExtractInfo extractInfo) { private static InsnsSlice isStartBlock(BlockNode dupBlock, BlockNode finallyBlock, FinallyExtractInfo extractInfo) {
extractInfo.setCurDupSlice(null);
List<InsnNode> dupInsns = dupBlock.getInstructions(); List<InsnNode> dupInsns = dupBlock.getInstructions();
List<InsnNode> finallyInsns = finallyBlock.getInstructions(); List<InsnNode> finallyInsns = finallyBlock.getInstructions();
int dupSize = dupInsns.size(); int dupSize = dupInsns.size();
@@ -386,7 +389,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
int startPos; int startPos;
int endPos = 0; int endPos = 0;
if (dupSize == finSize) { if (dupSize == finSize) {
if (!checkInsns(dupInsns, finallyInsns, 0)) { if (!checkInsns(extractInfo, dupInsns, finallyInsns, 0)) {
return null; return null;
} }
startPos = 0; startPos = 0;
@@ -394,11 +397,11 @@ public class MarkFinallyVisitor extends AbstractVisitor {
// dupSize > finSize // dupSize > finSize
startPos = dupSize - finSize; startPos = dupSize - finSize;
// fast check from end of block // fast check from end of block
if (!checkInsns(dupInsns, finallyInsns, startPos)) { if (!checkInsns(extractInfo, dupInsns, finallyInsns, startPos)) {
// search start insn // search start insn
boolean found = false; boolean found = false;
for (int i = 1; i < startPos; i++) { for (int i = 1; i < startPos; i++) {
if (checkInsns(dupInsns, finallyInsns, i)) { if (checkInsns(extractInfo, dupInsns, finallyInsns, i)) {
startPos = i; startPos = i;
endPos = finSize + i; endPos = finSize + i;
found = true; found = true;
@@ -414,6 +417,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
// put instructions into slices // put instructions into slices
boolean complete; boolean complete;
InsnsSlice slice = new InsnsSlice(); InsnsSlice slice = new InsnsSlice();
extractInfo.setCurDupSlice(slice);
int endIndex; int endIndex;
if (endPos != 0) { if (endPos != 0) {
endIndex = endPos + 1; endIndex = endPos + 1;
@@ -453,11 +457,12 @@ public class MarkFinallyVisitor extends AbstractVisitor {
return slice; return slice;
} }
private static boolean checkInsns(List<InsnNode> remInsns, List<InsnNode> finallyInsns, int delta) { private static boolean checkInsns(FinallyExtractInfo extractInfo, List<InsnNode> dupInsns, List<InsnNode> finallyInsns, int delta) {
extractInfo.setCurDupInsns(dupInsns, delta);
for (int i = finallyInsns.size() - 1; i >= 0; i--) { for (int i = finallyInsns.size() - 1; i >= 0; i--) {
InsnNode startInsn = finallyInsns.get(i); InsnNode startInsn = finallyInsns.get(i);
InsnNode remInsn = remInsns.get(delta + i); InsnNode dupInsn = dupInsns.get(delta + i);
if (!sameInsns(remInsn, startInsn)) { if (!sameInsns(extractInfo, dupInsn, startInsn)) {
return false; return false;
} }
} }
@@ -509,8 +514,9 @@ public class MarkFinallyVisitor extends AbstractVisitor {
if (dupInsnCount < finallyInsnCount) { if (dupInsnCount < finallyInsnCount) {
return false; return false;
} }
extractInfo.setCurDupInsns(dupInsns, 0);
for (int i = 0; i < finallyInsnCount; i++) { for (int i = 0; i < finallyInsnCount; i++) {
if (!sameInsns(dupInsns.get(i), finallyInsns.get(i))) { if (!sameInsns(extractInfo, dupInsns.get(i), finallyInsns.get(i))) {
return false; return false;
} }
} }
@@ -524,26 +530,85 @@ public class MarkFinallyVisitor extends AbstractVisitor {
return true; return true;
} }
private static boolean sameInsns(InsnNode remInsn, InsnNode fInsn) { private static boolean sameInsns(FinallyExtractInfo extractInfo, InsnNode dupInsn, InsnNode fInsn) {
if (!remInsn.isSame(fInsn)) { if (!dupInsn.isSame(fInsn)) {
return false; return false;
} }
// TODO: check instance arg in ConstructorInsn // TODO: check instance arg in ConstructorInsn
// TODO: compare literals for (int i = 0; i < dupInsn.getArgsCount(); i++) {
for (int i = 0; i < remInsn.getArgsCount(); i++) { InsnArg dupArg = dupInsn.getArg(i);
InsnArg remArg = remInsn.getArg(i);
InsnArg fArg = fInsn.getArg(i); InsnArg fArg = fInsn.getArg(i);
if (remArg.isRegister() != fArg.isRegister()) { if (!isSameArgs(extractInfo, dupArg, fArg)) {
return false; return false;
} }
boolean remConst = remArg.isConst(); }
if (remConst != fArg.isConst()) { return true;
return false; }
}
if (remConst && !remArg.isSameConst(fArg)) { @SuppressWarnings("RedundantIfStatement")
private static boolean isSameArgs(FinallyExtractInfo extractInfo, InsnArg dupArg, InsnArg fArg) {
boolean isReg = dupArg.isRegister();
if (isReg != fArg.isRegister()) {
return false;
}
if (isReg) {
RegisterArg dupReg = (RegisterArg) dupArg;
RegisterArg fReg = (RegisterArg) fArg;
if (!dupReg.sameCodeVar(fReg)
&& !sameDebugInfo(dupReg, fReg)
&& assignedOutsideHandler(extractInfo, dupReg, fReg)
&& assignInsnDifferent(dupReg, fReg)) {
return false; return false;
} }
} }
boolean remConst = dupArg.isConst();
if (remConst != fArg.isConst()) {
return false;
}
if (remConst && !dupArg.isSameConst(fArg)) {
return false;
}
return true;
}
private static boolean sameDebugInfo(RegisterArg dupReg, RegisterArg fReg) {
RegDebugInfoAttr fDbgInfo = fReg.get(AType.REG_DEBUG_INFO);
RegDebugInfoAttr dupDbgInfo = dupReg.get(AType.REG_DEBUG_INFO);
if (fDbgInfo == null || dupDbgInfo == null) {
return false;
}
return dupDbgInfo.equals(fDbgInfo);
}
private static boolean assignInsnDifferent(RegisterArg dupReg, RegisterArg fReg) {
InsnNode assignInsn = fReg.getAssignInsn();
InsnNode dupAssign = dupReg.getAssignInsn();
if (assignInsn == null || dupAssign == null) {
return true;
}
if (!assignInsn.isSame(dupAssign)) {
return true;
}
if (assignInsn.isConstInsn() && dupAssign.isConstInsn()) {
return !assignInsn.isDeepEquals(dupAssign);
}
return false;
}
@SuppressWarnings("RedundantIfStatement")
private static boolean assignedOutsideHandler(FinallyExtractInfo extractInfo, RegisterArg dupReg, RegisterArg fReg) {
if (InsnList.contains(extractInfo.getFinallyInsnsSlice().getInsnsList(), fReg.getAssignInsn())) {
return false;
}
InsnNode dupAssign = dupReg.getAssignInsn();
InsnsSlice curDupSlice = extractInfo.getCurDupSlice();
if (curDupSlice != null && InsnList.contains(curDupSlice.getInsnsList(), dupAssign)) {
return false;
}
List<InsnNode> curDupInsns = extractInfo.getCurDupInsns();
if (Utils.notEmpty(curDupInsns) && InsnList.contains(curDupInsns, dupAssign, extractInfo.getCurDupInsnsOffset())) {
return false;
}
return true; return true;
} }
@@ -8,6 +8,7 @@ import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.Region; import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
public class CleanRegions extends AbstractVisitor { public class CleanRegions extends AbstractVisitor {
@@ -42,6 +43,13 @@ public class CleanRegions extends AbstractVisitor {
BlockNode block = (BlockNode) container; BlockNode block = (BlockNode) container;
return block.getInstructions().isEmpty(); return block.getInstructions().isEmpty();
} }
if (container instanceof LoopRegion) {
LoopRegion loopRegion = (LoopRegion) container;
if (loopRegion.isEndless()) {
// keep empty endless loops
return false;
}
}
if (container instanceof IRegion) { if (container instanceof IRegion) {
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks(); List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
for (IContainer subBlock : subBlocks) { for (IContainer subBlock : subBlocks) {
@@ -19,6 +19,10 @@ public class DepthRegionTraversal {
traverseInternal(mth, visitor, mth.getRegion()); traverseInternal(mth, visitor, mth.getRegion());
} }
public static void traverse(MethodNode mth, IContainer container, IRegionVisitor visitor) {
traverseInternal(mth, visitor, container);
}
public static void traverseIterative(MethodNode mth, IRegionIterativeVisitor visitor) { public static void traverseIterative(MethodNode mth, IRegionIterativeVisitor visitor) {
boolean repeat; boolean repeat;
int k = 0; int k = 0;
@@ -3,8 +3,10 @@ package jadx.core.dex.visitors.regions;
import java.util.List; import java.util.List;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.Region; import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.conditions.IfCondition; import jadx.core.dex.regions.conditions.IfCondition;
@@ -45,7 +47,7 @@ public class IfRegionVisitor extends AbstractVisitor {
} }
} }
@SuppressWarnings("UnnecessaryReturnStatement") @SuppressWarnings({ "UnnecessaryReturnStatement", "StatementWithEmptyBody" })
private static void orderBranches(MethodNode mth, IfRegion ifRegion) { private static void orderBranches(MethodNode mth, IfRegion ifRegion) {
if (RegionUtils.isEmpty(ifRegion.getElseRegion())) { if (RegionUtils.isEmpty(ifRegion.getElseRegion())) {
return; return;
@@ -79,9 +81,15 @@ public class IfRegionVisitor extends AbstractVisitor {
return; return;
} }
} }
boolean lastRegion = ifRegion == RegionUtils.getLastRegion(mth.getRegion()); boolean lastRegion = RegionUtils.hasExitEdge(ifRegion);
if (elseSize == 1 && lastRegion && mth.isVoidReturn()) { if (elseSize == 1 && lastRegion && mth.isVoidReturn()) {
// single return at method end will be removed later InsnNode lastElseInsn = RegionUtils.getLastInsn(ifRegion.getElseRegion());
if (lastElseInsn != null && lastElseInsn.getType() == InsnType.THROW) {
// move `throw` into `then` block
invertIfRegion(ifRegion);
} else {
// single return at method end will be removed later
}
return; return;
} }
if (!lastRegion) { if (!lastRegion) {
@@ -1,6 +1,6 @@
package jadx.core.dex.visitors.regions; package jadx.core.dex.visitors.regions;
import java.util.LinkedList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -125,7 +125,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
return false; return false;
} }
// can't make loop if argument from increment instruction is assign in loop // can't make loop if argument from increment instruction is assign in loop
List<RegisterArg> args = new LinkedList<>(); List<RegisterArg> args = new ArrayList<>();
incrInsn.getRegisterArgs(args); incrInsn.getRegisterArgs(args);
for (RegisterArg iArg : args) { for (RegisterArg iArg : args) {
try { try {
@@ -268,7 +268,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|| !checkInvoke(nextCall, "java.util.Iterator", "next()Ljava/lang/Object;")) { || !checkInvoke(nextCall, "java.util.Iterator", "next()Ljava/lang/Object;")) {
return false; return false;
} }
List<InsnNode> toSkip = new LinkedList<>(); List<InsnNode> toSkip = new ArrayList<>();
RegisterArg iterVar; RegisterArg iterVar;
if (nextCall.contains(AFlag.WRAPPED)) { if (nextCall.contains(AFlag.WRAPPED)) {
InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, nextCall); InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, nextCall);
@@ -464,7 +464,7 @@ public class RegionMaker {
BlockNode exitEnd = BlockUtils.followEmptyPath(exit); BlockNode exitEnd = BlockUtils.followEmptyPath(exit);
List<LoopInfo> loops = exitEnd.getAll(AType.LOOP); List<LoopInfo> loops = exitEnd.getAll(AType.LOOP);
for (LoopInfo loopAtEnd : loops) { for (LoopInfo loopAtEnd : loops) {
if (loopAtEnd != loop) { if (loopAtEnd != loop && loop.hasParent(loopAtEnd)) {
insertEdge = exitEdge; insertEdge = exitEdge;
confirm = true; confirm = true;
break; break;
@@ -10,6 +10,7 @@ import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.TernaryInsn; import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IContainer;
@@ -73,11 +74,7 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
return false; return false;
} }
if (elseRegion == null) { if (elseRegion == null) {
if (mth.isConstructor()) { return processOneBranchTernary(mth, ifRegion);
// force ternary conversion to inline all code in 'super' or 'this' calls
return processOneBranchTernary(mth, ifRegion);
}
return false;
} }
BlockNode tb = getTernaryInsnBlock(thenRegion); BlockNode tb = getTernaryInsnBlock(thenRegion);
BlockNode eb = getTernaryInsnBlock(elseRegion); BlockNode eb = getTernaryInsnBlock(elseRegion);
@@ -93,21 +90,8 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
InsnNode thenInsn = tb.getInstructions().get(0); InsnNode thenInsn = tb.getInstructions().get(0);
InsnNode elseInsn = eb.getInstructions().get(0); InsnNode elseInsn = eb.getInstructions().get(0);
if (mth.contains(AFlag.USE_LINES_HINTS) if (!verifyLineHints(mth, thenInsn, elseInsn)) {
&& thenInsn.getSourceLine() != elseInsn.getSourceLine()) { return false;
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
// sometimes source lines incorrect
if (!checkLineStats(thenInsn, elseInsn)) {
return false;
}
} else {
// no debug info
if (containsTernary(thenInsn) || containsTernary(elseInsn)) {
// don't make nested ternary by default
// TODO: add addition checks
return false;
}
}
} }
RegisterArg thenResArg = thenInsn.getResult(); RegisterArg thenResArg = thenInsn.getResult();
@@ -184,6 +168,20 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
return false; return false;
} }
private static boolean verifyLineHints(MethodNode mth, InsnNode thenInsn, InsnNode elseInsn) {
if (mth.contains(AFlag.USE_LINES_HINTS)
&& thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
// sometimes source lines incorrect
return checkLineStats(thenInsn, elseInsn);
}
// don't make nested ternary by default
// TODO: add addition checks
return !containsTernary(thenInsn) && !containsTernary(elseInsn);
}
return true;
}
private static void clearConditionBlocks(List<BlockNode> conditionBlocks, BlockNode header) { private static void clearConditionBlocks(List<BlockNode> conditionBlocks, BlockNode header) {
for (BlockNode block : conditionBlocks) { for (BlockNode block : conditionBlocks) {
if (block != header) { if (block != header) {
@@ -277,6 +275,7 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
return false; return false;
} }
@SuppressWarnings("StatementWithEmptyBody")
private static void replaceWithTernary(MethodNode mth, IfRegion ifRegion, BlockNode block, InsnNode insn) { private static void replaceWithTernary(MethodNode mth, IfRegion ifRegion, BlockNode block, InsnNode insn) {
RegisterArg resArg = insn.getResult(); RegisterArg resArg = insn.getResult();
if (resArg.getSVar().getUseList().size() != 1) { if (resArg.getSVar().getUseList().size() != 1) {
@@ -296,17 +295,45 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
if (otherArg == null) { if (otherArg == null) {
return; return;
} }
InsnNode elseAssign = otherArg.getAssignInsn();
if (mth.isConstructor() || (mth.getParentClass().isEnum() && mth.getMethodInfo().isClassInit())) {
// forcing ternary inline for constructors (will help in moving super call to the top) and enums
// skip code style checks
} else {
if (elseAssign != null && elseAssign.isConstInsn()) {
if (!verifyLineHints(mth, insn, elseAssign)) {
return;
}
} else {
if (insn.getResult().sameCodeVar(otherArg)) {
// don't use same variable in else branch to prevent: l = (l == 0) ? 1 : l
return;
}
}
}
// all checks passed // all checks passed
BlockNode header = ifRegion.getConditionBlocks().get(0); BlockNode header = ifRegion.getConditionBlocks().get(0);
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) { if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
return; return;
} }
InsnArg elseArg;
if (elseAssign != null && elseAssign.isConstInsn()) {
// inline constant
SSAVar elseVar = elseAssign.getResult().getSVar();
if (elseVar.getUseCount() == 1 && elseVar.getOnlyOneUseInPhi() == phiInsn) {
InsnRemover.remove(mth, elseAssign);
}
elseArg = InsnArg.wrapInsnIntoArg(elseAssign);
} else {
elseArg = otherArg;
}
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(),
phiInsn.getResult(), InsnArg.wrapInsnIntoArg(insn), otherArg); phiInsn.getResult(), InsnArg.wrapInsnIntoArg(insn), elseArg);
ternInsn.simplifyCondition();
InsnRemover.unbindResult(mth, insn); InsnRemover.unbindResult(mth, insn);
InsnList.remove(block, insn); InsnList.remove(block, insn);
InsnRemover.unbindAllArgs(mth, phiInsn); InsnRemover.unbindAllArgs(mth, phiInsn);
header.getInstructions().clear(); header.getInstructions().clear();
ternInsn.rebindArgs(); ternInsn.rebindArgs();
@@ -15,8 +15,9 @@ import jadx.core.codegen.json.JsonMappingGen;
import jadx.core.deobf.Deobfuscator; import jadx.core.deobf.Deobfuscator;
import jadx.core.deobf.NameMapper; import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr; import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
@@ -45,8 +46,8 @@ public class RenameVisitor extends AbstractVisitor {
deobfuscator.execute(); deobfuscator.execute();
} }
checkClasses(deobfuscator, root, args);
UserRenames.applyForNodes(root); UserRenames.applyForNodes(root);
checkClasses(deobfuscator, root, args);
if (args.isDeobfuscationOn() || !args.isJsonOutput()) { if (args.isDeobfuscationOn() || !args.isJsonOutput()) {
deobfuscator.savePresets(); deobfuscator.savePresets();
@@ -196,13 +197,8 @@ public class RenameVisitor extends AbstractVisitor {
if (args.isRenameValid()) { if (args.isRenameValid()) {
Set<String> names = new HashSet<>(methods.size()); Set<String> names = new HashSet<>(methods.size());
for (MethodNode mth : methods) { for (MethodNode mth : methods) {
AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isBridge() || accessFlags.isSynthetic()
|| mth.contains(AFlag.DONT_GENERATE) /* this flag not set yet */) {
continue;
}
String signature = mth.getMethodInfo().makeSignature(true, false); String signature = mth.getMethodInfo().makeSignature(true, false);
if (!names.add(signature)) { if (!names.add(signature) && canRename(mth)) {
deobfuscator.forceRenameMethod(mth); deobfuscator.forceRenameMethod(mth);
mth.addAttr(new RenameReasonAttr("collision with other method in class")); mth.addAttr(new RenameReasonAttr("collision with other method in class"));
} }
@@ -210,6 +206,23 @@ public class RenameVisitor extends AbstractVisitor {
} }
} }
private static boolean canRename(MethodNode mth) {
if (mth.contains(AFlag.DONT_RENAME)) {
return false;
}
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr != null) {
for (MethodNode relatedMth : overrideAttr.getRelatedMthNodes()) {
if (relatedMth != mth && mth.getParentClass().equals(relatedMth.getParentClass())) {
// ignore rename if exists related method from same class (bridge method in most cases)
// such rename will also rename current method and will not help to resolve name collision
return false;
}
}
}
return true;
}
private static void processRootPackages(Deobfuscator deobfuscator, RootNode root, List<ClassNode> classes) { private static void processRootPackages(Deobfuscator deobfuscator, RootNode root, List<ClassNode> classes) {
Set<String> rootPkgs = collectRootPkgs(classes); Set<String> rootPkgs = collectRootPkgs(classes);
root.getCacheStorage().setRootPkgs(rootPkgs); root.getCacheStorage().setRootPkgs(rootPkgs);
@@ -1,7 +1,7 @@
package jadx.core.dex.visitors.shrink; package jadx.core.dex.visitors.shrink;
import java.util.ArrayList;
import java.util.BitSet; import java.util.BitSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
@@ -30,7 +30,7 @@ final class ArgsInfo {
} }
public static List<RegisterArg> getArgs(InsnNode insn) { public static List<RegisterArg> getArgs(InsnNode insn) {
List<RegisterArg> args = new LinkedList<>(); List<RegisterArg> args = new ArrayList<>();
addArgs(insn, args); addArgs(insn, args);
return args; return args;
} }
@@ -9,6 +9,8 @@ import java.util.Set;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeCustomNode;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.Named; import jadx.core.dex.instructions.args.Named;
@@ -123,6 +125,9 @@ public class CodeShrinkVisitor extends AbstractVisitor {
return; return;
} }
} }
if (!checkLambdaInline(arg, assignInsn)) {
return;
}
int assignPos = insnList.getIndex(assignInsn); int assignPos = insnList.getIndex(assignInsn);
if (assignPos != -1) { if (assignPos != -1) {
@@ -145,6 +150,26 @@ public class CodeShrinkVisitor extends AbstractVisitor {
} }
} }
/**
* Forbid inline lambda into invoke as an instance arg, i.e. this will not compile:
* {@code () -> { ... }.apply(); }
*/
private static boolean checkLambdaInline(RegisterArg arg, InsnNode assignInsn) {
if (assignInsn.getType() == InsnType.INVOKE && assignInsn instanceof InvokeCustomNode) {
for (RegisterArg useArg : arg.getSVar().getUseList()) {
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn != null && parentInsn.getType() == InsnType.INVOKE) {
InvokeNode invokeNode = (InvokeNode) parentInsn;
InsnArg instArg = invokeNode.getInstanceArg();
if (instArg != null && instArg == useArg) {
return false;
}
}
}
}
return true;
}
private static boolean varWithSameNameExists(MethodNode mth, SSAVar inlineVar) { private static boolean varWithSameNameExists(MethodNode mth, SSAVar inlineVar) {
for (SSAVar ssaVar : mth.getSVars()) { for (SSAVar ssaVar : mth.getSVars()) {
if (ssaVar == inlineVar || ssaVar.getCodeVar() == inlineVar.getCodeVar()) { if (ssaVar == inlineVar || ssaVar.getCodeVar() == inlineVar.getCodeVar()) {
@@ -1,10 +1,10 @@
package jadx.core.dex.visitors.ssa; package jadx.core.dex.visitors.ssa;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.BitSet; import java.util.BitSet;
import java.util.Deque; import java.util.Deque;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
@@ -81,7 +81,7 @@ public class SSATransform extends AbstractVisitor {
int blocksCount = blocks.size(); int blocksCount = blocks.size();
BitSet hasPhi = new BitSet(blocksCount); BitSet hasPhi = new BitSet(blocksCount);
BitSet processed = new BitSet(blocksCount); BitSet processed = new BitSet(blocksCount);
Deque<BlockNode> workList = new LinkedList<>(); Deque<BlockNode> workList = new ArrayDeque<>();
BitSet assignBlocks = la.getAssignBlocks(regNum); BitSet assignBlocks = la.getAssignBlocks(regNum);
for (int id = assignBlocks.nextSetBit(0); id >= 0; id = assignBlocks.nextSetBit(id + 1)) { for (int id = assignBlocks.nextSetBit(0); id >= 0; id = assignBlocks.nextSetBit(id + 1)) {
@@ -136,7 +136,7 @@ public class SSATransform extends AbstractVisitor {
RenameState initState = RenameState.init(mth); RenameState initState = RenameState.init(mth);
initPhiInEnterBlock(initState); initPhiInEnterBlock(initState);
Deque<RenameState> stack = new LinkedList<>(); Deque<RenameState> stack = new ArrayDeque<>();
stack.push(initState); stack.push(initState);
while (!stack.isEmpty()) { while (!stack.isEmpty()) {
RenameState state = stack.pop(); RenameState state = stack.pop();
@@ -325,7 +325,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
ClassNode ctrCls = root.resolveClass(ctr.getClassType()); ClassNode ctrCls = root.resolveClass(ctr.getClassType());
if (ctrCls != null && ctrCls.contains(AFlag.DONT_GENERATE)) { if (ctrCls != null && ctrCls.contains(AFlag.DONT_GENERATE)) {
AnonymousClassAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS); AnonymousClassAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS);
if (baseTypeAttr != null) { if (baseTypeAttr != null && baseTypeAttr.getInlineType() == AnonymousClassAttr.InlineType.CONSTRUCTOR) {
return baseTypeAttr.getBaseType(); return baseTypeAttr.getBaseType();
} }
} }
@@ -913,25 +913,20 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
} }
boolean fixed = false; boolean fixed = false;
for (ITypeBound bound : typeInfo.getBounds()) { for (RegisterArg arg : new ArrayList<>(var.getUseList())) {
if (bound.getBound() == BoundEnum.USE if (fixBooleanUsage(mth, arg)) {
&& fixBooleanUsage(mth, bound)) {
fixed = true; fixed = true;
} }
} }
return fixed; return fixed;
} }
private boolean fixBooleanUsage(MethodNode mth, ITypeBound bound) { private boolean fixBooleanUsage(MethodNode mth, RegisterArg boundArg) {
ArgType boundType = bound.getType(); ArgType boundType = boundArg.getInitType();
if (boundType == ArgType.BOOLEAN if (boundType == ArgType.BOOLEAN
|| (boundType.isTypeKnown() && !boundType.isPrimitive())) { || (boundType.isTypeKnown() && !boundType.isPrimitive())) {
return false; return false;
} }
RegisterArg boundArg = bound.getArg();
if (boundArg == null) {
return false;
}
InsnNode insn = boundArg.getParentInsn(); InsnNode insn = boundArg.getParentInsn();
if (insn == null || insn.getType() == InsnType.IF) { if (insn == null || insn.getType() == InsnType.IF) {
return false; return false;
@@ -6,6 +6,7 @@ import java.io.StringReader;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
@@ -29,6 +30,8 @@ public class ExportGradleProject {
private static final Logger LOG = LoggerFactory.getLogger(ExportGradleProject.class); private static final Logger LOG = LoggerFactory.getLogger(ExportGradleProject.class);
private static final Pattern ILLEGAL_GRADLE_CHARS = Pattern.compile("[/\\\\:>\"?*|]");
private static final Set<String> IGNORE_CLS_NAMES = new HashSet<>(Arrays.asList( private static final Set<String> IGNORE_CLS_NAMES = new HashSet<>(Arrays.asList(
"R", "R",
"BuildConfig")); "BuildConfig"));
@@ -72,7 +75,7 @@ public class ExportGradleProject {
private void saveSettingsGradle() throws IOException { private void saveSettingsGradle() throws IOException {
TemplateFile tmpl = TemplateFile.fromResources("/export/settings.gradle.tmpl"); TemplateFile tmpl = TemplateFile.fromResources("/export/settings.gradle.tmpl");
tmpl.add("applicationName", applicationParams.getApplicationName()); tmpl.add("applicationName", ILLEGAL_GRADLE_CHARS.matcher(applicationParams.getApplicationName()).replaceAll(""));
tmpl.save(new File(projectDir, "settings.gradle")); tmpl.save(new File(projectDir, "settings.gradle"));
} }
@@ -0,0 +1,62 @@
package jadx.core.utils;
import java.util.HashSet;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.deobf.NameMapper;
import jadx.core.deobf.TldHelper;
public class BetterName {
private static final Logger LOG = LoggerFactory.getLogger(BetterName.class);
private static final boolean DEBUG = true;
public static String compareAndGet(String first, String second) {
if (Objects.equals(first, second)) {
return first;
}
int firstRating = calcRating(first);
int secondRating = calcRating(second);
boolean firstBetter = firstRating >= secondRating;
if (DEBUG) {
if (firstBetter) {
LOG.info("Better name: '{}' > '{}' ({} > {})", first, second, firstRating, secondRating);
} else {
LOG.info("Better name: '{}' > '{}' ({} > {})", second, first, secondRating, firstRating);
}
}
return firstBetter ? first : second;
}
public static int calcRating(String str) {
int rating = str.length() * 3;
rating += differentCharsCount(str) * 20;
if (NameMapper.isAllCharsPrintable(str)) {
rating += 100;
}
if (NameMapper.isValidIdentifier(str)) {
rating += 50;
}
if (TldHelper.contains(str)) {
rating += 20;
}
if (str.contains("_")) {
// rare in obfuscated names
rating += 100;
}
return rating;
}
private static int differentCharsCount(String str) {
String lower = str.toLowerCase(Locale.ROOT);
Set<Integer> chars = new HashSet<>();
StringUtils.visitCodePoints(lower, chars::add);
return chars.size();
}
}
@@ -697,7 +697,7 @@ public class BlockUtils {
} }
/** /**
* Search lowest common ancestor in dominator tree for input set. * Search the lowest common ancestor in dominator tree for input set.
*/ */
@Nullable @Nullable
public static BlockNode getCommonDominator(MethodNode mth, List<BlockNode> blocks) { public static BlockNode getCommonDominator(MethodNode mth, List<BlockNode> blocks) {
@@ -1013,6 +1013,9 @@ public class BlockUtils {
*/ */
@Nullable @Nullable
public static InsnNode getOnlyOneInsnFromMth(MethodNode mth) { public static InsnNode getOnlyOneInsnFromMth(MethodNode mth) {
if (mth.isNoCode()) {
return null;
}
InsnNode insn = null; InsnNode insn = null;
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
List<InsnNode> blockInsns = block.getInstructions(); List<InsnNode> blockInsns = block.getInstructions();
@@ -246,4 +246,29 @@ public class DebugUtils {
Set<Object> seen = ConcurrentHashMap.newKeySet(); Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(keyExtractor.apply(t)); return t -> seen.add(keyExtractor.apply(t));
} }
private static Map<String, Long> execTimes;
public static void initExecTimes() {
execTimes = new ConcurrentHashMap<>();
}
public static void mergeExecTimeFromStart(String tag, long startTimeMillis) {
mergeExecTime(tag, System.currentTimeMillis() - startTimeMillis);
}
public static void mergeExecTime(String tag, long execTimeMillis) {
execTimes.merge(tag, execTimeMillis, Long::sum);
}
public static void printExecTimes() {
System.out.println("Exec times:");
execTimes.forEach((tag, time) -> System.out.println(" " + tag + ": " + time + "ms"));
}
public static void printExecTimesWithTotal(long totalMillis) {
System.out.println("Exec times: total " + totalMillis + "ms");
execTimes.forEach((tag, time) -> System.out.println(" " + tag + ": " + time + "ms"
+ String.format(" (%.2f%%)", time * 100. / (double) totalMillis)));
}
} }
@@ -139,11 +139,13 @@ public class DecompilerScheduler implements IDecompileScheduler {
} }
private void dumpBatchesStats(List<JavaClass> classes, List<List<JavaClass>> result, List<DepInfo> deps) { private void dumpBatchesStats(List<JavaClass> classes, List<List<JavaClass>> result, List<DepInfo> deps) {
int clsInBatches = result.stream().mapToInt(List::size).sum();
double avg = result.stream().mapToInt(List::size).average().orElse(-1); double avg = result.stream().mapToInt(List::size).average().orElse(-1);
int maxSingleDeps = classes.stream().mapToInt(JavaClass::getTotalDepsCount).max().orElse(-1); int maxSingleDeps = classes.stream().mapToInt(JavaClass::getTotalDepsCount).max().orElse(-1);
int maxSubDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1); int maxSubDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1);
LOG.info("Batches stats:" LOG.info("Batches stats:"
+ "\n input classes: " + classes.size() + "\n input classes: " + classes.size()
+ ",\n classes in batches: " + clsInBatches
+ ",\n batches: " + result.size() + ",\n batches: " + result.size()
+ ",\n average batch size: " + String.format("%.2f", avg) + ",\n average batch size: " + String.format("%.2f", avg)
+ ",\n max single deps count: " + maxSingleDeps + ",\n max single deps count: " + maxSingleDeps
@@ -1,11 +1,33 @@
package jadx.core.utils; package jadx.core.utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.IMethodHandle;
import jadx.api.plugins.input.data.IMethodProto;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.data.MethodHandleType;
import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class EncodedValueUtils { public class EncodedValueUtils {
@@ -50,4 +72,108 @@ public class EncodedValueUtils {
return null; return null;
} }
} }
public static InsnArg convertToInsnArg(RootNode root, EncodedValue value) {
Object obj = value.getValue();
switch (value.getType()) {
case ENCODED_NULL:
case ENCODED_BYTE:
case ENCODED_SHORT:
case ENCODED_CHAR:
case ENCODED_INT:
case ENCODED_LONG:
case ENCODED_FLOAT:
case ENCODED_DOUBLE:
return (InsnArg) convertToConstValue(value);
case ENCODED_BOOLEAN:
return InsnArg.lit(((Boolean) obj) ? 0 : 1, ArgType.BOOLEAN);
case ENCODED_STRING:
return InsnArg.wrapArg(new ConstStringNode((String) obj));
case ENCODED_TYPE:
return InsnArg.wrapArg(new ConstClassNode(ArgType.parse((String) obj)));
case ENCODED_METHOD_TYPE:
return InsnArg.wrapArg(buildMethodType(root, (IMethodProto) obj));
case ENCODED_METHOD_HANDLE:
return InsnArg.wrapArg(buildMethodHandle(root, (IMethodHandle) obj));
}
throw new JadxRuntimeException("Unsupported type for raw invoke-custom: " + value.getType());
}
private static InvokeNode buildMethodType(RootNode root, IMethodProto methodProto) {
ArgType retType = ArgType.parse(methodProto.getReturnType());
List<ArgType> argTypes = Utils.collectionMap(methodProto.getArgTypes(), ArgType::parse);
List<ArgType> callTypes = new ArrayList<>(1 + argTypes.size());
callTypes.add(retType);
callTypes.addAll(argTypes);
ArgType mthType = ArgType.object("java.lang.invoke.MethodType");
ClassInfo cls = ClassInfo.fromType(root, mthType);
MethodInfo mth = MethodInfo.fromDetails(root, cls, "methodType", callTypes, mthType);
InvokeNode invoke = new InvokeNode(mth, InvokeType.STATIC, callTypes.size());
for (ArgType type : callTypes) {
InsnNode argInsn;
if (type.isPrimitive()) {
argInsn = new IndexInsnNode(InsnType.SGET, getTypeField(root, type.getPrimitiveType()), 0);
} else {
argInsn = new ConstClassNode(type);
}
invoke.addArg(InsnArg.wrapArg(argInsn));
}
return invoke;
}
public static FieldInfo getTypeField(RootNode root, PrimitiveType type) {
ArgType boxType = type.getBoxType();
ClassInfo boxCls = ClassInfo.fromType(root, boxType);
return FieldInfo.from(root, boxCls, "TYPE", boxType);
}
/**
* Build `MethodHandles.lookup().find{type}(methodCls, methodName, methodType)`
*/
private static InsnNode buildMethodHandle(RootNode root, IMethodHandle methodHandle) {
if (methodHandle.getType().isField()) {
// TODO: lookup for field
return new ConstStringNode("FIELD:" + methodHandle.getFieldRef());
}
IMethodRef methodRef = methodHandle.getMethodRef();
methodRef.load();
ClassInfo lookupCls = ClassInfo.fromName(root, "java.lang.invoke.MethodHandles.Lookup");
MethodInfo findMethod = MethodInfo.fromDetails(root, lookupCls,
getFindMethodName(methodHandle.getType()),
Arrays.asList(ArgType.CLASS, ArgType.STRING, ArgType.object("java.lang.invoke.MethodType")),
ArgType.object("java.lang.invoke.MethodHandle"));
InvokeNode invoke = new InvokeNode(findMethod, InvokeType.DIRECT, 4);
invoke.addArg(buildLookupArg(root));
invoke.addArg(InsnArg.wrapArg(new ConstClassNode(ArgType.object(methodRef.getParentClassType()))));
invoke.addArg(InsnArg.wrapArg(new ConstStringNode(methodRef.getName())));
invoke.addArg(InsnArg.wrapArg(buildMethodType(root, methodRef)));
return invoke;
}
public static InsnArg buildLookupArg(RootNode root) {
ArgType lookupType = ArgType.object("java.lang.invoke.MethodHandles.Lookup");
ClassInfo cls = ClassInfo.fromName(root, "java.lang.invoke.MethodHandles");
MethodInfo mth = MethodInfo.fromDetails(root, cls, "lookup", Collections.emptyList(), lookupType);
return InsnArg.wrapArg(new InvokeNode(mth, InvokeType.STATIC, 0));
}
private static String getFindMethodName(MethodHandleType type) {
switch (type) {
case INVOKE_STATIC:
return "findStatic";
case INVOKE_CONSTRUCTOR:
return "findConstructor";
case INVOKE_INSTANCE:
case INVOKE_DIRECT:
case INVOKE_INTERFACE:
return "findVirtual";
default:
return "<" + type + '>';
}
}
} }
@@ -29,8 +29,12 @@ public final class InsnList implements Iterable<InsnNode> {
} }
public static int getIndex(List<InsnNode> list, InsnNode insn) { public static int getIndex(List<InsnNode> list, InsnNode insn) {
return getIndex(list, insn, 0);
}
public static int getIndex(List<InsnNode> list, InsnNode insn, int startOffset) {
int size = list.size(); int size = list.size();
for (int i = 0; i < size; i++) { for (int i = startOffset; i < size; i++) {
if (list.get(i) == insn) { if (list.get(i) == insn) {
return i; return i;
} }
@@ -38,6 +42,14 @@ public final class InsnList implements Iterable<InsnNode> {
return -1; return -1;
} }
public static boolean contains(List<InsnNode> list, InsnNode insn) {
return getIndex(list, insn, 0) != -1;
}
public static boolean contains(List<InsnNode> list, InsnNode insn, int startOffset) {
return getIndex(list, insn, startOffset) != -1;
}
public int getIndex(InsnNode insn) { public int getIndex(InsnNode insn) {
return getIndex(list, insn); return getIndex(list, insn);
} }
@@ -17,6 +17,7 @@ import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -228,6 +229,18 @@ public class InsnRemover {
removeAll(block.getInstructions(), insns); removeAll(block.getInstructions(), insns);
} }
public static void removeAllAndUnbind(MethodNode mth, IContainer container, List<InsnNode> insns) {
unbindInsns(mth, insns);
RegionUtils.visitBlocks(mth, container, b -> removeAll(b.getInstructions(), insns));
}
public static void removeAllAndUnbind(MethodNode mth, List<InsnNode> insns) {
unbindInsns(mth, insns);
for (BlockNode block : mth.getBasicBlocks()) {
removeAll(block.getInstructions(), insns);
}
}
public static void removeAllWithoutUnbind(BlockNode block, List<InsnNode> insns) { public static void removeAllWithoutUnbind(BlockNode block, List<InsnNode> insns) {
removeAll(block.getInstructions(), insns); removeAll(block.getInstructions(), insns);
} }
@@ -1,6 +1,7 @@
package jadx.core.utils; package jadx.core.utils;
import java.util.List; import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -140,6 +141,37 @@ public class InsnUtils {
return null; return null;
} }
public static void replaceInsns(MethodNode mth, Function<InsnNode, InsnNode> replaceFunction) {
for (BlockNode block : mth.getBasicBlocks()) {
List<InsnNode> insns = block.getInstructions();
int insnsCount = insns.size();
for (int i = 0; i < insnsCount; i++) {
InsnNode insn = insns.get(i);
replaceInsnsInInsn(mth, insn, replaceFunction);
InsnNode replace = replaceFunction.apply(insn);
if (replace != null) {
BlockUtils.replaceInsn(mth, block, i, replace);
}
}
}
}
public static void replaceInsnsInInsn(MethodNode mth, InsnNode insn, Function<InsnNode, InsnNode> replaceFunction) {
int argsCount = insn.getArgsCount();
for (int i = 0; i < argsCount; i++) {
InsnArg arg = insn.getArg(i);
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
replaceInsnsInInsn(mth, wrapInsn, replaceFunction);
InsnNode replace = replaceFunction.apply(wrapInsn);
if (replace != null) {
InsnRemover.unbindArgUsage(mth, arg);
insn.setArg(i, InsnArg.wrapInsnIntoArg(replace));
}
}
}
}
@Nullable @Nullable
public static RegisterArg getRegFromInsn(List<RegisterArg> regs, InsnType insnType) { public static RegisterArg getRegFromInsn(List<RegisterArg> regs, InsnType insnType) {
for (RegisterArg reg : regs) { for (RegisterArg reg : regs) {
@@ -160,6 +160,10 @@ public class ListUtils {
return true; return true;
} }
public static <T> boolean noneMatch(Collection<T> list, Predicate<T> test) {
return !anyMatch(list, test);
}
public static <T> boolean anyMatch(Collection<T> list, Predicate<T> test) { public static <T> boolean anyMatch(Collection<T> list, Predicate<T> test) {
if (list == null || list.isEmpty()) { if (list == null || list.isEmpty()) {
return false; return false;
@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -22,9 +23,12 @@ import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.Region; import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlockAttr; import jadx.core.dex.trycatch.TryCatchBlockAttr;
import jadx.core.dex.visitors.regions.AbstractRegionVisitor;
import jadx.core.dex.visitors.regions.DepthRegionTraversal;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
public class RegionUtils { public class RegionUtils {
@@ -145,20 +149,6 @@ public class RegionUtils {
} }
} }
@Nullable
public static IContainer getLastRegion(@Nullable IContainer container) {
if (container == null) {
return null;
}
if (container instanceof IBlock || container instanceof IBranchRegion) {
return container;
}
if (container instanceof IRegion) {
return getLastRegion(Utils.last(((IRegion) container).getSubBlocks()));
}
throw new JadxRuntimeException(unknownContainerType(container));
}
public static boolean isExitBlock(MethodNode mth, IContainer container) { public static boolean isExitBlock(MethodNode mth, IContainer container) {
if (container instanceof BlockNode) { if (container instanceof BlockNode) {
return BlockUtils.isExitBlock(mth, (BlockNode) container); return BlockUtils.isExitBlock(mth, (BlockNode) container);
@@ -289,7 +279,11 @@ public class RegionUtils {
} }
} }
return false; return false;
} else if (container instanceof IRegion) { }
if (container instanceof LoopRegion) {
return true;
}
if (container instanceof IRegion) {
IRegion region = (IRegion) container; IRegion region = (IRegion) container;
for (IContainer block : region.getSubBlocks()) { for (IContainer block : region.getSubBlocks()) {
if (notEmpty(block)) { if (notEmpty(block)) {
@@ -297,9 +291,8 @@ public class RegionUtils {
} }
} }
return false; return false;
} else {
throw new JadxRuntimeException(unknownContainerType(container));
} }
throw new JadxRuntimeException(unknownContainerType(container));
} }
public static void getAllRegionBlocks(IContainer container, Set<IBlock> blocks) { public static void getAllRegionBlocks(IContainer container, Set<IBlock> blocks) {
@@ -483,4 +476,13 @@ public class RegionUtils {
} }
return "Unknown container type: " + container.getClass(); return "Unknown container type: " + container.getClass();
} }
public static void visitBlocks(MethodNode mth, IContainer container, Consumer<IBlock> visitor) {
DepthRegionTraversal.traverse(mth, container, new AbstractRegionVisitor() {
@Override
public void processBlock(MethodNode mth, IBlock block) {
visitor.accept(block);
}
});
}
} }
@@ -129,7 +129,7 @@ public class AndroidResourcesUtils {
FieldNode newResField = new FieldNode(typeCls, rFieldInfo, FieldNode newResField = new FieldNode(typeCls, rFieldInfo,
AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL); AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL);
newResField.addAttr(new EncodedValue(EncodedType.ENCODED_INT, resource.getId())); newResField.addAttr(new EncodedValue(EncodedType.ENCODED_INT, resource.getId()));
typeCls.getFields().add(newResField); typeCls.addField(newResField);
if (rClsExists) { if (rClsExists) {
newResField.addInfoComment("Added by JADX"); newResField.addInfoComment("Added by JADX");
} }
@@ -10,12 +10,16 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitOption; import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
@@ -108,13 +112,8 @@ public class FileUtils {
} }
public static boolean deleteDir(File dir) { public static boolean deleteDir(File dir) {
File[] content = dir.listFiles(); deleteDir(dir.toPath());
if (content != null) { return true;
for (File file : content) {
deleteDir(file);
}
}
return dir.delete();
} }
public static void deleteDirIfExists(Path dir) { public static void deleteDirIfExists(Path dir) {
@@ -127,16 +126,23 @@ public class FileUtils {
} }
} }
private static final SimpleFileVisitor<Path> FILE_DELETE_VISITOR = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
};
private static void deleteDir(Path dir) { private static void deleteDir(Path dir) {
try (Stream<Path> pathStream = Files.walk(dir)) { try {
pathStream.sorted(Comparator.reverseOrder()) Files.walkFileTree(dir, Collections.emptySet(), Integer.MAX_VALUE, FILE_DELETE_VISITOR);
.forEach(path -> {
try {
Files.delete(path);
} catch (Exception e) {
throw new JadxRuntimeException("Failed to delete path: " + path.toAbsolutePath(), e);
}
});
} catch (Exception e) { } catch (Exception e) {
throw new JadxRuntimeException("Failed to delete directory " + dir, e); throw new JadxRuntimeException("Failed to delete directory " + dir, e);
} }
@@ -228,7 +234,8 @@ public class FileUtils {
public static void writeFile(Path file, String data) throws IOException { public static void writeFile(Path file, String data) throws IOException {
FileUtils.makeDirsForFile(file); FileUtils.makeDirsForFile(file);
Files.write(file, data.getBytes(StandardCharsets.UTF_8)); Files.write(file, data.getBytes(StandardCharsets.UTF_8),
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
} }
public static String readFile(Path textFile) throws IOException { public static String readFile(Path textFile) throws IOException {
@@ -8,6 +8,7 @@ import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -51,7 +52,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
private boolean isLastEnd = true; private boolean isLastEnd = true;
private boolean isOneLine = true; private boolean isOneLine = true;
private int namespaceDepth = 0; private int namespaceDepth = 0;
private int[] resourceIds; private @Nullable int[] resourceIds;
private final RootNode rootNode; private final RootNode rootNode;
private String appPackageName; private String appPackageName;
@@ -358,7 +359,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
// As the outcome of https://github.com/skylot/jadx/issues/1208 // As the outcome of https://github.com/skylot/jadx/issues/1208
// Android seems to favor entries from AndroidResMap and only if // Android seems to favor entries from AndroidResMap and only if
// there is no entry uses the values form the XML string pool // there is no entry uses the values form the XML string pool
if (0 <= id && id < resourceIds.length) { if (resourceIds != null && 0 <= id && id < resourceIds.length) {
int resId = resourceIds[id]; int resId = resourceIds[id];
String str = ValuesParser.getAndroidResMap().get(resId); String str = ValuesParser.getAndroidResMap().get(resId);
if (str != null) { if (str != null) {
@@ -26,7 +26,7 @@ public class CommonBinaryParser extends ParserConstants {
int[] stringsOffset = is.readInt32Array(stringCount); int[] stringsOffset = is.readInt32Array(stringCount);
int[] stylesOffset = is.readInt32Array(styleCount); int[] stylesOffset = is.readInt32Array(styleCount);
is.checkPos(start + stringsStart, "Expected strings start"); is.skipToPos(start + stringsStart, "Expected strings start");
String[] strings = new String[stringCount]; String[] strings = new String[stringCount];
byte[] strData = is.readInt8Array((int) (chunkEnd - is.getPos())); byte[] strData = is.readInt8Array((int) (chunkEnd - is.getPos()));
if ((flags & UTF8_FLAG) != 0) { if ((flags & UTF8_FLAG) != 0) {
@@ -0,0 +1,13 @@
package jadx.core.xmlgen;
import java.io.IOException;
import java.io.InputStream;
public interface IResParser {
void decode(InputStream inputStream) throws IOException;
ResourceStorage getResStorage();
String[] getStrings();
}
@@ -4,10 +4,8 @@ import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
@@ -19,6 +17,9 @@ import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.xmlgen.entry.RawNamedValue;
import jadx.core.xmlgen.entry.ResourceEntry;
import jadx.core.xmlgen.entry.ValuesParser;
public class ManifestAttributes { public class ManifestAttributes {
private static final Logger LOG = LoggerFactory.getLogger(ManifestAttributes.class); private static final Logger LOG = LoggerFactory.getLogger(ManifestAttributes.class);
@@ -54,6 +55,8 @@ public class ManifestAttributes {
private final Map<String, MAttr> attrMap = new HashMap<>(); private final Map<String, MAttr> attrMap = new HashMap<>();
private final Map<String, MAttr> appAttrMap = new HashMap<>();
private static ManifestAttributes instance; private static ManifestAttributes instance;
public static ManifestAttributes getInstance() { public static ManifestAttributes getInstance() {
@@ -170,12 +173,15 @@ public class ManifestAttributes {
public String decode(String attrName, long value) { public String decode(String attrName, long value) {
MAttr attr = attrMap.get(attrName); MAttr attr = attrMap.get(attrName);
if (attr == null) { if (attr == null) {
return null; attr = appAttrMap.get(attrName);
if (attr == null) {
return null;
}
} }
if (attr.getType() == MAttrType.ENUM) { if (attr.getType() == MAttrType.ENUM) {
return attr.getValues().get(value); return attr.getValues().get(value);
} else if (attr.getType() == MAttrType.FLAG) { } else if (attr.getType() == MAttrType.FLAG) {
List<String> flagList = new LinkedList<>(); List<String> flagList = new ArrayList<>();
List<Long> attrKeys = new ArrayList<>(attr.getValues().keySet()); List<Long> attrKeys = new ArrayList<>(attr.getValues().keySet());
attrKeys.sort((a, b) -> Long.compare(b, a)); // sort descending attrKeys.sort((a, b) -> Long.compare(b, a)); // sort descending
for (Long key : attrKeys) { for (Long key : attrKeys) {
@@ -188,8 +194,36 @@ public class ManifestAttributes {
value ^= key; value ^= key;
} }
} }
return flagList.stream().collect(Collectors.joining("|")); return String.join("|", flagList);
} }
return null; return null;
} }
public void updateAttributes(IResParser parser) {
appAttrMap.clear();
ResourceStorage resStorage = parser.getResStorage();
ValuesParser vp = new ValuesParser(parser.getStrings(), resStorage.getResourcesNames());
for (ResourceEntry ri : resStorage.getResources()) {
if (ri.getTypeName().equals("attr") && ri.getNamedValues().size() > 1) {
RawNamedValue first = ri.getNamedValues().get(0);
MAttrType attrTyp;
if (first.getRawValue().getData() == ValuesParser.ATTR_TYPE_FLAGS) {
attrTyp = MAttrType.FLAG;
} else if (first.getRawValue().getData() == ValuesParser.ATTR_TYPE_ENUM || first.getRawValue().getData() == 65600) {
attrTyp = MAttrType.ENUM;
} else {
continue;
}
MAttr attr = new MAttr(attrTyp);
for (int i = 1; i < ri.getNamedValues().size(); i++) {
RawNamedValue rv = ri.getNamedValues().get(i);
String value = vp.decodeNameRef(rv.getNameRef());
attr.getValues().put((long) rv.getRawValue().getData(), value.startsWith("id.") ? value.substring(3) : value);
}
appAttrMap.put(ri.getKeyName(), attr);
}
}
}
} }

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