Compare commits

...

136 Commits

Author SHA1 Message Date
Skylot c4c3d42d16 build: fix release workflow 2024-11-11 22:46:31 +00:00
Skylot 58c36de8c2 build: add release github workflow 2024-11-10 20:15:34 +00:00
Skylot b4ca566a19 build: exclude shadow jar from publishing to maven 2024-11-10 20:15:34 +00:00
Skylot e644bad758 chore: update dependencies 2024-11-10 20:15:34 +00:00
Skylot dd86abcc5e chore: remove LGTM config 2024-11-10 17:26:24 +00:00
Skylot f0513a1a55 fix: insert new filled array insn before first usage (#2340) 2024-11-07 20:02:41 +00:00
Skylot be6cb573b1 feat(plugins): allow to set minimum required jadx version in plugin info (#2314) 2024-11-06 16:30:22 +00:00
pubiqq 5d064d3e50 feat(res): improve resource names (PR #2316) 2024-11-06 18:08:35 +03:00
pubiqq 15b6309e2c fix: support "fall-through to default" case in switch-over-string (PR #2338) 2024-11-05 21:41:03 +00:00
Skylot 417bb7a7e9 feat: add method to update all blocks related info in method (#2335) 2024-11-02 22:08:26 +00:00
Ahmet Bilal Can 4c74e8e300 feat: give ability to plugins to edit blocks before locking (PR #2336)
Plugins can use `.before('BlockFinisher')` to edit blocks before they are locked.
2024-11-02 21:38:57 +00:00
Skylot 57238de6ff feat(cli): add option to disable plugins (#2277) 2024-11-01 20:13:34 +00:00
Skylot 313c4a121a fix: improve negation condition checks for switch over string (#2333) 2024-11-01 16:33:28 +00:00
pubiqq 39912398fc fix: unwrap consts in switch-over-string (PR #2332) 2024-10-31 21:24:19 +00:00
Skylot 7544d1a113 fix: prevent endless loop in pre header insertion mod (#2300) 2024-10-31 20:00:06 +00:00
pubiqq cfbe5ab672 fix: fix default branch position in switch-over-string (PR #2331) 2024-10-31 19:58:35 +00:00
Ruffalo Lavoisier 2661b91a6f feat(gui): create Frida hooking snippet for all methods in the class (PR #2328) 2024-10-30 17:59:29 +00:00
Skylot 61578e8793 fix(gui): correct tabs filter in "Close others" tab action (#2330) 2024-10-30 17:20:16 +00:00
Skylot cc6a893402 feat: allow to disable installed plugins (#2277) 2024-10-28 23:35:28 +03:00
pubiqq 4d8a5d6671 fix(core): fix primitive-to-primitive conversions (PR #2326) 2024-10-28 20:33:10 +00:00
Skylot 982307b1ac fix(gui): use correct section filter in plugins list 2024-10-25 19:01:43 +01:00
Skylot 37054dc84e fix(gui): load plugins settings in temp context without UI (#2206) 2024-10-25 18:51:38 +01:00
Skylot 343e2c531a chore: expand input dirs in ConvertArscFile 2024-10-25 18:12:48 +01:00
pubiqq 6fa5d247f0 fix(res): update Android attrs to API 35 (PR #2318) 2024-10-25 18:08:36 +01:00
pubiqq 688dea0c50 fix(deobf): update TLDs (PR#2320) 2024-10-25 16:44:07 +01:00
Skylot c0815b12bc chore(gui): add missing ref text in untranslated messages (#2319)
fix
2024-10-24 20:33:52 +03:00
JustFor 233f8692b2 fix(gui): update Messages_zh_CN.properties (PR #2319)
1. New text is translated simultaneously, two untranslated texts remain (no English text)
2. Adjusted and unified part of the symbol, into the full Angle symbol.
2024-10-24 18:31:14 +01:00
Skylot e5be41b9cc fix(gui): use another implementation for font dialog (#2310) 2024-10-22 21:32:14 +01:00
Skylot 3788e4ef3a fix(gui): in settings row reduce space between description and value 2024-10-22 18:52:42 +01:00
Skylot 32855f4511 fix(gui): improve plugins preferences group 2024-10-22 18:51:19 +01:00
Skylot 3d5e225274 fix: clear temp root dir instead delete (#2312) 2024-10-22 18:01:52 +01:00
nitram84 8a34d973ff build: use jadx-gui as a library in plugins (PR #2310)
* fix(gui): fix javadoc issues

* feat(gui): export jadx-gui as a library

* fix(gui): fix javadoc issues

* fix(gui): remove invalid characters for javadoc

* add jadx-library also for jadx-cli and jadx-script-ide modules

---------

Co-authored-by: Skylot <118523+skylot@users.noreply.github.com>
2024-10-22 17:24:57 +01:00
Skylot a43b3282ef fix(gui): update check fixed to match current artifact naming 2024-10-17 21:57:20 +01:00
Skylot 249801880c feat(api): allow to get method code (#2305) 2024-10-17 19:20:07 +01:00
Skylot 742d30d770 chore: resolve warnings in SmaliArea class 2024-10-16 17:29:36 +01:00
Skylot 267413332a chore: resolve KtLint deprecation warning 2024-10-16 17:23:09 +01:00
Skylot f9c94f27f1 chore: update dependencies 2024-10-16 17:01:03 +01:00
Skylot d2131d9640 build: gradle wrapper validation not needed, check done in setup-java action 2024-10-16 17:00:43 +01:00
Skylot 548821c4df build: allow to change java toolchain for build and tests 2024-10-16 16:54:28 +01:00
pubiqq 958ab245ae fix(res): don't rename resource entries when useRawResName = true (PR #2306) 2024-10-14 20:32:12 +01:00
Skylot 8f3cc3e8c1 fix: add missing null check in codegen for classes generated by jadx 2024-10-14 19:19:31 +01:00
Skylot b872ffd1b9 fix: replace patched zip early in input files (#2302) 2024-10-12 20:53:09 +01:00
Skylot c5263f92fe fix(gui): send select tab event before code jump (#2292) 2024-10-12 21:23:53 +03:00
pubiqq 1475e887c8 fix: update reserved keywords (PR #2301) 2024-10-11 20:47:34 +01:00
Skylot c21cabcba7 fix: use temp dir env var only in apps 2024-10-10 22:57:40 +03:00
Skylot 063af8cd62 feat(api): add JadxArgs property to adjust xml security checks (#2291) 2024-10-10 22:57:40 +03:00
Skylot 2d10537050 chore: update dependencies 2024-10-10 22:57:40 +03:00
pubiqq 3814951408 feat: improve better name algorithm (PR #2299) 2024-10-10 20:01:57 +01:00
qfalconer 964bd62d35 feat: adding automatic patching for semi-corrupted APKs (PR #2298)
* feat: patching semi-corrupted APKs

* fix: using secure temp file creation

* fix: using TWR when handling the files
2024-10-10 17:53:26 +01:00
xiaojye 3ed2df828f feat(gui): add button to go to Android Manifest (PR #2296)
* feat: Add button to go to Android Manifest

* fix: Change the text from 'Go to Manifest' to 'Go to AndroidManifest.xml' and replace icon source
2024-10-10 17:11:08 +01:00
Skylot b26abdc851 feat(plugins): get config and cache dirs for plugins 2024-09-29 21:55:05 +01:00
Skylot 90185fd947 feat(plugins): get a main window reference as JFrame 2024-09-29 21:54:24 +01:00
Skylot 681f8a98b5 fix: improve checks for restore new filled array (#2289) 2024-09-28 16:52:08 +01:00
Skylot 0b225238fb feat: support restore of switch over string (basic case)(#2288) 2024-09-27 21:11:38 +01:00
Skylot a7649dda7a chore: update gradle and dependencies 2024-09-27 21:08:38 +01:00
pubiqq b5e3dcf70f feat: add the option to always use source file name as class name alias (PR #2287) 2024-09-23 22:47:08 +01:00
Skylot 7abbc81886 fix: improve switch out block search if all method exits are inside (#2264) 2024-09-22 21:35:40 +01:00
Skylot 9c30aeacdb refactor: split region maker 2024-09-22 20:23:03 +01:00
Skylot 8f27de4d0e chore: update dependencies 2024-09-21 22:00:03 +01:00
Skylot 02b69d2d29 fix(gui): prevent old refs leak in shortcuts controller 2024-09-21 21:53:31 +01:00
Skylot e6fde48b69 fix: don't add same 'loaded from:' comment for inner classes 2024-09-21 20:28:18 +01:00
Skylot 109dea0857 feat: support inner class contruction with outer instance (#2253) 2024-09-21 20:27:16 +01:00
Skylot 1d34328dd3 fix(build): disable cache for core tests to allow reruns (#2283) 2024-09-20 21:44:55 +01:00
Skylot ef4f1d3ed4 fix: ignore debug lines hints if numbers was adjusted for method 2024-09-20 21:36:13 +01:00
Skylot 23696d3971 fix: use type from new-instance if differ from constructor call (#2285) 2024-09-20 21:34:21 +01:00
Andy Smith efa2f5d172 feat(gui): limit search to a package (PR #2284)
* Add isDescendantOf and getJavaPackage helper functions

* Add i18n strings for search package

* Added search package to options in SearchSettings

* Add package limiting to each search provider

* Add package search to dialog and logic to get package by string.

* Added search option to package context menu

* Fix spotlessJavaCheck complaints

* Revert changes to individual search providers and add filter to base provider
2024-09-20 12:31:20 +03:00
Skylot 699ceb197e fix: improve condition branch checks in loops (#2274) 2024-09-16 20:37:34 +01:00
Skylot 5c83c22501 feat(java-input): support StackMapTable to get stack info for unvisited jumps (#2271) 2024-09-14 22:45:31 +01:00
Skylot 7bb5c0a859 fix: protect class deps from loading in different decompilation mode 2024-09-13 21:25:11 +01:00
Skylot 603863403f fix: do not add custom passes for fallback and simple modes (#2276) 2024-09-13 21:01:19 +01:00
Skylot 889a945cf1 fix: additional checks for class signature (#2272) 2024-09-12 20:29:05 +01:00
Skylot fd80e03809 fix: check if debug info offset is invalid (#1653) 2024-09-11 19:54:49 +01:00
Skylot b5807082d9 chore: update gradle and dependencies 2024-09-11 19:54:49 +01:00
Skylot 5d1f0b8cae feat(res): support grammar inflection flag in res config (#2270) 2024-09-08 21:49:13 +01:00
Skylot 3f9aa34057 fix(gui): resolve old objects reference leak in TabsController 2024-09-07 01:21:46 +03:00
Skylot e2c860f260 refactor(res): use list instead map to store entries offsets 2024-09-07 01:21:46 +03:00
Skylot 0938351d97 feat(res): support compact resource entries (#2268) 2024-09-07 01:21:42 +03:00
pubiqq 937dd20794 feat(res): support 16-bit entry offsets (PR #2269) 2024-09-06 23:21:21 +01:00
pubiqq ea5e87560a feat(res): improve error message for unsupported ResTable flags (PR #2266) 2024-09-05 19:13:26 +01:00
Skylot 5fbbf2150e fix(res): prevent duplication of ARSC entries (#2263) 2024-09-04 21:31:37 +01:00
Skylot 0e11bffe82 chore: update dependencies 2024-09-04 20:02:56 +01:00
Skylot ba9af5c288 fix(gui): minor fixes for code jumps 2024-09-04 19:59:57 +01:00
Skylot cca706c94f fix: improve 'continue' insertion for switch in loop (#2249) 2024-09-01 23:02:22 +01:00
Skylot 2df69bbfb4 fix(gui): prevent UI stuck on class load (#2259) 2024-08-31 22:30:18 +01:00
Skylot f5307636ef fix(gui): merge full class name tokens for constructors (#2261) 2024-08-30 22:39:43 +03:00
Mino 9a39b70a46 fix(gui): Quick Tabs Optimization (PR #2242)
* optimize tabs reorder

* restructure based on quick tabs architecture

* code formatting

* log all exceptions from background executor

* various improvements

---------

Co-authored-by: Skylot <118523+skylot@users.noreply.github.com>
2024-08-30 20:33:05 +01:00
pubiqq e63808bc4b fix: improve checking of access modifiers (PR #2255) 2024-08-20 16:45:28 +03:00
Skylot 847225a6a9 fix: improve try/catch temp edges injection (#2247) 2024-08-17 21:09:31 +01:00
Skylot eee354a3ab chore: update gradle and dependencies 2024-08-17 21:01:27 +01:00
Skylot 1cc00a54f3 fix(gui): use correct translation in rename dialog (#2254) 2024-08-17 21:58:39 +03:00
pubiqq ffdad1b652 fix: improve checking of access modifiers for methods (PR #2252) 2024-08-15 23:20:07 +01:00
pubiqq 9a8ec76989 fix: improve checking of access modifiers for classes (PR #2251) 2024-08-15 19:59:44 +01:00
Skylot 0be5b2cea9 refactor(tests): add debug checks switch to jadx args 2024-08-13 22:39:48 +01:00
Skylot c94201be4a fix: improve switch out search in loop (#2246) 2024-08-12 22:06:03 +01:00
Skylot 1051dacb1e refactor(tests): migrate from Hamcrest to AssertJ 2024-08-11 21:55:56 +01:00
Skylot a2bfe9bbe8 chore: add openrewrite gradle plugin to improve code quality 2024-08-11 21:10:01 +01:00
Skylot 8c6ec3bccc fix(gui): trim also leading spaces in paths from file dialog (#2244) 2024-08-10 19:24:05 +01:00
Skylot 015876b790 fix(gui): trim trailing spaces in input files (#2244) 2024-08-10 00:31:25 +01:00
Skylot 280f3870a9 fix: handle quick return on branched constructor (#2240) 2024-08-09 22:54:13 +01:00
Skylot e9d770ae9e fix: use correct approach to get prev block on path (#2239) 2024-08-09 22:51:37 +01:00
Skylot 5f1bd1d9ba chore: migrate gradle shadow plugin 2024-08-08 20:44:34 +01:00
pubiqq 60fb458024 fix: improve inlining synthetic accessors (PR #2243)
* fix: fix inlining synthetic accessors

* add test, undo changes in InsnNode

---------

Co-authored-by: Skylot <118523+skylot@users.noreply.github.com>
2024-08-08 20:27:08 +01:00
Skylot 1b08779536 chore: update dependencies 2024-08-07 19:06:58 +01:00
Skylot c37e39a819 chore: code improvements by cleanthat 2024-08-07 00:34:22 +01:00
Skylot 2d58fbd4b1 chore: forbid use ArrayList as a variable type 2024-08-07 00:28:39 +01:00
Mino ffbf800404 feat(gui): Quick Tabs Overhaul (PR #2241)
* restructure quick tabs code

* code formatting

* display open tabs

* added bookmark tabs feature

* fix tabs pin and bookmark not saved

* fix NPE treeModel not initialized

* Fix hardcoded strings

* remove unused statement

* fix NPE again

* added bookmark overlay

* preserve tabs order

* fix context menu actions

* remove unnecessary public modifier

* save tabs in tabbedpane order

* remove unreferenced tabs

* Update jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java

---------

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
2024-08-06 23:47:25 +01:00
dependabot[bot] 500aa8a68d build(deps): bump gradle/actions from 3 to 4 (PR #2238)
Bumps [gradle/actions](https://github.com/gradle/actions) from 3 to 4.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 18:35:39 +01:00
Skylot 58e8268126 fix: workaround to make method inline deterministic (#1089) 2024-08-04 22:46:46 +01:00
Skylot f9da6e00ed fix(gui): add VM flags to fix UI ghosting (#2225) 2024-08-04 19:40:14 +01:00
Skylot 821cc668c7 fix: don't rerun SSA transform in ConstructorVisitor (#2236) 2024-08-02 21:26:25 +01:00
Skylot 287ba49008 fix(gui): show folding actions in code popup menu (#2234) 2024-08-02 17:13:16 +01:00
pubiqq 115e563a2b fix: improve checking if methods are inline for FixAccessModifiers (PR #2235) 2024-08-01 21:45:52 +01:00
Mino 1669200e62 feat(gui): smali code folding (PR #2233)
* added smali code folding

* remove unnecessary public modifiers

* improve code

* undo method extract

---------

Co-authored-by: Skylot <118523+skylot@users.noreply.github.com>
2024-08-01 19:33:37 +01:00
Mino 6ab224ea0d feat(gui): pin tabs (PR #2230)
* add ability to pin tabs

* save pinned tabs with Save Project action

* further prevent closing pinned tabs

* add translation entries

* prevent pinning start page

* add pinned tabs tree view

* properly dispose of quickTabsTree

* restructure code

* more unpin context menu items
2024-08-01 18:06:45 +01:00
pubiqq 61855a7ea1 fix: make detailed var info deterministic (PR #2231) 2024-07-31 22:16:07 +01:00
Mino bda3119e86 fix(gui): horizontal scrolling in Linux (PR #2229)
* fix horizontal scrolling in linux

* improve code

---------

Co-authored-by: Skylot <118523+skylot@users.noreply.github.com>
2024-07-31 17:02:00 +01:00
Mino b26abed686 feat(gui): export resource/class/package (PR #2228)
* feat: export resource

* feat: export class

* restructure code: introduce enum for exporting classes

* feat: export package

* feat: export resource folder

* check directory exists before creation

* apply code formatting

* fix code formatting

* Apply suggestions from code review

---------

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
2024-07-31 17:00:54 +01:00
Skylot c179beee95 chore: update dependencies 2024-07-28 20:57:37 +01:00
Skylot 04a454094b fix: improve exception handlers checks (#2086) 2024-07-27 22:09:14 +01:00
Skylot 33bcbfec41 chore(docs): minor fixes 2024-07-27 22:09:13 +01:00
Skylot ec645d80b1 fix(mappings): try to prevent mapping file reset on export exception, refactor and fix code to avoid NPE (#2220)(#2226) 2024-07-25 17:44:23 +01:00
Jan S. a8d889de3d fix(launch4j): do not overwrite Java heap configuration from applicationDefaultJvmArgs (PR #2218) 2024-07-20 20:46:45 +01:00
Skylot ec0bf701c8 fix(build): fix hiding console in Windows (#2196)
Regression after migration to shadow jar in #1868,
instead CreateStartScripts task from 'application' plugin
'startShadowScripts' task should be modified.
2024-07-19 00:19:47 +03:00
Skylot ad4dd116be chore: update gradle and dependencies 2024-07-19 00:19:47 +03:00
Skylot ef79f266c8 fix(build): fix gitlab build 2024-07-19 00:19:43 +03:00
Jan S 366225f9be fix(quark): fix automated installation and check exit code of executed external commands (#2119)(PR #2216) 2024-07-12 18:39:17 +01:00
Skylot 730db0d24f fix(build): do not wrap jar in launch4j (#2186) 2024-07-08 22:17:06 +01:00
Iscle 05fb77e9bd feat(gui): add button to go to Application class (#2208)(PR #2213)
* feat: Add button to go to Application class

Icons from: https://intellij-icons.jetbrains.design/

* fix: Rename "goto" to "go_to" to keep things consistent
2024-07-08 18:36:45 +01:00
Iscle bbabfa0354 fix(gui): fix Xposed args code generation (PR #2212)
* Rename .java to .kt

* fix: Fix wrong function arguments when generating kotlin xposed code
2024-07-08 18:34:28 +01:00
Jan S 96bd9f0f17 fix(xml): AXML/Manifest parsing improvements (PR #2211)
* log and ignore decodeValue errors

* skip extra data in package header

* ResourceTypes.h
2024-07-06 19:27:47 +03:00
qfalconer fd5b397b40 fix(xml): allow for non-standard attributes sizes and avoid index exceptions when decoding some strings (PR #2210)
More lenient AXML parsing: allow for non-standard attributes sizes and avoid index exceptions when decoding some strings

* The attributes size of an XML element is now accounted for. This size must be at least 20 (0x14) bytes but can be greater. Extra bytes are just skipped. When decoding a string, if such decoding is impossible a placeholder string is returned instead of throwing an exception. This is necessary because some malware purposely add android:tag attributes with invalid string index to throw parsers off. They also employ non-standard attribute sizes.

* Minor code restyling

---------

Co-authored-by: qfalconer <knm241@gmail.com>
2024-07-03 18:50:15 +01:00
Artem Zhiganov f5e3a261b4 fix(gui): update russian translation (PR #2209)
Co-authored-by: SVolf <dev@thunderdog.ru>
2024-06-28 18:39:23 +01:00
JustFor 52a884608a fix(gui): update Messages_zh_CN.properties (PR #2203)
sync new texts.
2024-06-14 18:52:31 +01:00
Iscle 74ddfde950 feat(gui): allow to check for unstable releases (PR #2200)
* feat: gui: convert JadxUpdate to Kotlin

* feat: gui: allow updater to check for latest unstable artifacts

* fix: remove nullable operator from onUpdate() interface
2024-06-11 22:49:44 +01:00
Skylot 9aacb4f312 fix: config dir was used instead cache dir 2024-06-11 20:55:49 +01:00
Jiaxin Peng 82e2104f3c fix(gui): support filtering files with multiple extensions in file dialog (PR #2185)
* fix(gui): support filtering files with multiple extensions in file dialog

* lint
2024-05-19 17:47:35 +01:00
761 changed files with 30274 additions and 17611 deletions
+20 -14
View File
@@ -16,7 +16,7 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 11
java-version: 21
- name: Set jadx version
run: |
@@ -24,10 +24,13 @@ jobs:
JADX_VERSION="r${JADX_REV}.${GITHUB_SHA:0:7}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- name: Build with Gradle
uses: gradle/actions/setup-gradle@v3
with:
arguments: dist copyExe
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build
run: ./gradlew dist distWin
env:
JADX_BUILD_JAVA_VERSION: 11
- name: Save bundle artifact
uses: actions/upload-artifact@v4
@@ -39,11 +42,12 @@ jobs:
if-no-files-found: error
retention-days: 14
- name: Save exe artifact
- name: Save Windows bundle artifact
uses: actions/upload-artifact@v4
with:
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
path: build/*.exe
name: ${{ format('jadx-gui-{0}-no-jre-win', env.JADX_VERSION) }}
# Upload unpacked files for now
path: jadx-gui/build/jadx-gui-win/*
if-no-files-found: error
retention-days: 14
@@ -70,15 +74,17 @@ jobs:
JADX_VERSION="r${JADX_REV}.${GITHUB_SHA:0:7}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- name: Build with Gradle
uses: gradle/actions/setup-gradle@v3
with:
arguments: dist -PbundleJRE=true
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Save exe bundle artifact
- name: Build
run: ./gradlew dist -PbundleJRE=true
- name: Save Windows with JRE bundle artifact
uses: actions/upload-artifact@v4
with:
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
path: jadx-gui/build/*-with-jre-win/*
# Upload unpacked files for now
path: jadx-gui/build/jadx-gui-with-jre-win/*
if-no-files-found: error
retention-days: 14
+9 -5
View File
@@ -20,9 +20,13 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 11
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build
run: ./gradlew build dist distWin
env:
JADX_BUILD_JAVA_VERSION: 11
- name: Build with Gradle
uses: gradle/actions/setup-gradle@v3
with:
arguments: build dist copyExe
@@ -1,15 +0,0 @@
name: Validate Gradle Wrapper
on:
push:
branches: [ master, build-test ]
pull_request:
branches: [ master ]
jobs:
validation:
name: Validation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/actions/wrapper-validation@v3
+92
View File
@@ -0,0 +1,92 @@
name: Release
on:
push:
tags:
- "v*.*.*"
# additional permissions for provided GitHub token to create new release
permissions:
contents: write
jobs:
build-release-win-bundle:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: oracle-actions/setup-java@v1
with:
release: 23
- name: Set jadx version
uses: actions/github-script@v7
with:
script: |
const jadxVersion = context.ref.split('/').pop().substring(1)
core.exportVariable('JADX_VERSION', jadxVersion);
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build
run: ./gradlew dist -PbundleJRE=true
- name: Save JRE bundle artifact
uses: actions/upload-artifact@v4
with:
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
path: ${{ format('build/distWinWithJre/jadx-gui-{0}-with-jre-win.zip', env.JADX_VERSION) }}
if-no-files-found: error
retention-days: 1
release:
needs: build-release-win-bundle
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Set jadx version and release name
uses: actions/github-script@v7
with:
script: |
const jadxVersion = context.ref.split('/').pop().substring(1)
core.exportVariable('JADX_VERSION', jadxVersion);
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build
run: ./gradlew dist distWin
env:
JADX_BUILD_JAVA_VERSION: 11
- name: Download Windows JRE bundle
uses: actions/download-artifact@v4
with:
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
path: ${{ format('build/jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
- run: |
cd build
pwd
ls -l
ls jadx-gui-*-with-jre-win
mv jadx-gui-*-with-jre-win/jadx-gui-*-with-jre-win.zip .
mv distWin/jadx-gui-*-win.zip .
ls -l *.zip
- name: Release
uses: softprops/action-gh-release@v2
with:
name: ${{ env.JADX_VERSION }}
draft: true
fail_on_unmatched_files: true
files: build/jadx-*.zip
+1
View File
@@ -28,6 +28,7 @@ jadx-output/
*-tmp/
**/tmp/
*.jobf
*.jadx
*.class
*.dump
+3 -3
View File
@@ -11,14 +11,14 @@ stages:
java-11:
stage: test
image: eclipse-temurin:11
script: ./gradlew clean build dist copyExe
script: ./gradlew clean build dist distWin
java-17:
stage: test
image: eclipse-temurin:17
script: ./gradlew clean build dist copyExe
script: ./gradlew clean build dist distWin
java-21:
stage: test
image: eclipse-temurin:21
script: ./gradlew clean build dist copyExe
script: ./gradlew clean build dist distWin
+1 -1
View File
@@ -4,7 +4,7 @@
<module name="jadx.jadx-gui.main"/>
<option name="PROGRAM_PARAMETERS" value="-v"/>
<option name="VM_PARAMETERS"
value="-Xms128M -XX:MaxRAMPercentage=70.0 -Dawt.useSystemAAFontSettings=lcd -Dswing.aatext=true -Djava.util.Arrays.useLegacyMergeSort=true -Djdk.util.zip.disableZip64ExtraFieldValidation=true -XX:+IgnoreUnrecognizedVMOptions --add-opens=java.base/java.lang=ALL-UNNAMED"/>
value="-Xms128M -XX:MaxRAMPercentage=70.0 -Dawt.useSystemAAFontSettings=lcd -Dswing.aatext=true -Djava.util.Arrays.useLegacyMergeSort=true -Djdk.util.zip.disableZip64ExtraFieldValidation=true -XX:+IgnoreUnrecognizedVMOptions --add-opens=java.base/java.lang=ALL-UNNAMED -Dsun.java2d.noddraw=true -Dsun.java2d.d3d=false -Dsun.java2d.ddforcevram=true -Dsun.java2d.ddblit=false -Dswing.useflipBufferStrategy=True"/>
<method v="2">
<option name="Make" enabled="true"/>
</method>
+10 -23
View File
@@ -3,7 +3,7 @@
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
@@ -23,13 +23,13 @@ include:
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
professional setting
## Our Responsibilities
@@ -45,25 +45,12 @@ threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at skylot@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.
## Attribution
+93 -87
View File
@@ -91,95 +91,101 @@ commands (use '<command> --help' for command options):
plugins - manage jadx plugins
options:
-d, --output-dir - output directory
-ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--single-class - decompile a single class, full name, raw or alias
--single-class-output - file or dir for write if decompile a single class
--output-format - can be 'java' or 'json', default: java
-e, --export-gradle - save as android gradle project
-j, --threads-count - processing threads count, default: 4
-m, --decompilation-mode - code output mode:
'auto' - trying best options (default)
'restructure' - restore code structure (normal java code)
'simple' - simplified instructions (linear, with goto's)
'fallback' - raw instructions without modifications
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-xml-pretty-print - do not prettify XML
--no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info parsing and processing
--add-debug-lines - add comments with debug line numbers if available
--no-inline-anonymous - disable anonymous classes inline
--no-inline-methods - disable methods inline
--no-move-inner-classes - disable move inner classes into parent
--no-inline-kotlin-lambda - disable inline for Kotlin lambdas
--no-finally - don't extract finally block
--no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u)
--respect-bytecode-access-modifiers - don't change original access modifiers
--mappings-path - deobfuscation mappings file or directory. Allowed formats: Tiny and Tiny v2 (both '.tiny'), Enigma (.mapping) or Enigma directory
--mappings-mode - set mode for handling the deobfuscation mapping file:
'read' - just read, user can always save manually (default)
'read-and-autosave-every-change' - read and autosave after every change
'read-and-autosave-before-closing' - read and autosave before exiting the app or closing the project
'ignore' - don't read or save (can be used to skip loading mapping files referenced in the project file)
--deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64
--deobf-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.v4.* android.support.v7.* android.support.v4.os.* android.support.annotation.Px androidx.core.os.* androidx.annotation.Px
--deobf-cfg-file - deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format), default: same dir and name as input file with '.jobf' extension
--deobf-cfg-file-mode - set mode for handling the JADX auto-generated names' deobfuscation map file:
'read' - read if found, don't save (default)
'read-or-save' - read if found, save otherwise (don't overwrite)
'overwrite' - don't read, always save
'ignore' - don't read and don't save
--deobf-use-sourcename - use source file name as class name alias
--deobf-res-name-source - better name source for resources:
'auto' - automatically select best name (default)
'resources' - use resources names
'code' - use R class fields names
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
--rename-flags - fix options (comma-separated list of):
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
'valid' - rename java identifiers to make them valid,
'printable' - remove non-printable chars from identifiers,
or single 'none' - to disable all renames
or single 'all' - to enable all (default)
--integer-format - how integers are displayed:
'auto' - automatically select (default)
'decimal' - use decimal
'hexadecimal' - use hexadecimal
--fs-case-sensitive - treat filesystem as case sensitive, false by default
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
--use-dx - use dx/d8 to convert java bytecode
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
--version - print jadx version
-h, --help - print this help
-d, --output-dir - output directory
-ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--single-class - decompile a single class, full name, raw or alias
--single-class-output - file or dir for write if decompile a single class
--output-format - can be 'java' or 'json', default: java
-e, --export-gradle - save as android gradle project
-j, --threads-count - processing threads count, default: 4
-m, --decompilation-mode - code output mode:
'auto' - trying best options (default)
'restructure' - restore code structure (normal java code)
'simple' - simplified instructions (linear, with goto's)
'fallback' - raw instructions without modifications
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-xml-pretty-print - do not prettify XML
--no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info parsing and processing
--add-debug-lines - add comments with debug line numbers if available
--no-inline-anonymous - disable anonymous classes inline
--no-inline-methods - disable methods inline
--no-move-inner-classes - disable move inner classes into parent
--no-inline-kotlin-lambda - disable inline for Kotlin lambdas
--no-finally - don't extract finally block
--no-restore-switch-over-string - don't restore switch over string
--no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u)
--respect-bytecode-access-modifiers - don't change original access modifiers
--mappings-path - deobfuscation mappings file or directory. Allowed formats: Tiny and Tiny v2 (both '.tiny'), Enigma (.mapping) or Enigma directory
--mappings-mode - set mode for handling the deobfuscation mapping file:
'read' - just read, user can always save manually (default)
'read-and-autosave-every-change' - read and autosave after every change
'read-and-autosave-before-closing' - read and autosave before exiting the app or closing the project
'ignore' - don't read or save (can be used to skip loading mapping files referenced in the project file)
--deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64
--deobf-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.v4.* android.support.v7.* android.support.v4.os.* android.support.annotation.Px androidx.core.os.* androidx.annotation.Px
--deobf-cfg-file - deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format), default: same dir and name as input file with '.jobf' extension
--deobf-cfg-file-mode - set mode for handling the JADX auto-generated names' deobfuscation map file:
'read' - read if found, don't save (default)
'read-or-save' - read if found, save otherwise (don't overwrite)
'overwrite' - don't read, always save
'ignore' - don't read and don't save
--deobf-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-source-name-as-class-name-alias - use source name as class name alias:
'always' - always use source name if it's available
'if-better' - use source name if it seems better than the current one
'never' - never use source name, even if it's available
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
--rename-flags - fix options (comma-separated list of):
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
'valid' - rename java identifiers to make them valid,
'printable' - remove non-printable chars from identifiers,
or single 'none' - to disable all renames
or single 'all' - to enable all (default)
--integer-format - how integers are displayed:
'auto' - automatically select (default)
'decimal' - use decimal
'hexadecimal' - use hexadecimal
--fs-case-sensitive - treat filesystem as case sensitive, false by default
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
--use-dx - use dx/d8 to convert java bytecode
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
--version - print jadx version
-h, --help - print this help
Plugin options (-P<name>=<value>):
1) dex-input: Load .dex and .apk files
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
2) java-convert: Convert .class, .jar and .aar files to dex
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
3) kotlin-metadata: Use kotlin.Metadata annotation for code generation
- kotlin-metadata.class-alias - rename class alias, values: [yes, no], default: yes
- kotlin-metadata.method-args - rename function arguments, values: [yes, no], default: yes
- kotlin-metadata.fields - rename fields, values: [yes, no], default: yes
- kotlin-metadata.companion - rename companion object, values: [yes, no], default: yes
- kotlin-metadata.data-class - add data class modifier, values: [yes, no], default: yes
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
4) rename-mappings: various mappings support
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, SRG_FILE, XSRG_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, PROGUARD_FILE], default: AUTO
- rename-mappings.invert - invert mapping on load, values: [yes, no], default: no
dex-input: Load .dex and .apk files
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
java-convert: Convert .class, .jar and .aar files to dex
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
kotlin-metadata: Use kotlin.Metadata annotation for code generation
- kotlin-metadata.class-alias - rename class alias, values: [yes, no], default: yes
- kotlin-metadata.method-args - rename function arguments, values: [yes, no], default: yes
- kotlin-metadata.fields - rename fields, values: [yes, no], default: yes
- kotlin-metadata.companion - rename companion object, values: [yes, no], default: yes
- kotlin-metadata.data-class - add data class modifier, values: [yes, no], default: yes
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
rename-mappings: various mappings support
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, SRG_FILE, XSRG_FILE, JAM_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, PROGUARD_FILE, RECAF_SIMPLE_FILE, JOBF_FILE], default: AUTO
- rename-mappings.invert - invert mapping on load, values: [yes, no], default: no
smali-input: Load .smali files
- smali-input.api-level - Android API level, default: 27
Environment variables:
JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files
+4 -3
View File
@@ -2,6 +2,7 @@
## Reporting a Vulnerability
To report a security issue, please email `skylot@gmail.com` with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
We will check and respond within 3 working days. If the issue is confirmed as a vulnerability, we will apply required mitigations at the next release.
This project follows a 90 day disclosure timeline.
To report a security issue, please open a [new security advisory](https://github.com/skylot/jadx/security/advisories/new).
Please fill the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
We will check and respond within 3 working days.
If the issue is confirmed as a vulnerability, we will apply required mitigations at the next release.
+29 -24
View File
@@ -15,6 +15,18 @@ val jadxVersion by extra { System.getenv("JADX_VERSION") ?: "dev" }
println("jadx version: $jadxVersion")
version = jadxVersion
val jadxBuildJavaVersion by extra { getBuildJavaVersion() }
fun getBuildJavaVersion(): Int? {
val envVarName = "JADX_BUILD_JAVA_VERSION"
val buildJavaVer = System.getenv(envVarName)?.toInt() ?: return null
if (buildJavaVer < 11) {
throw GradleException("'$envVarName' can't be set to lower than 11")
}
println("Set Java toolchain for jadx build to version '$buildJavaVer'")
return buildJavaVer
}
allprojects {
apply(plugin = "java")
apply(plugin = "checkstyle")
@@ -103,40 +115,34 @@ val pack by tasks.registering(Zip::class) {
destinationDirectory.set(layout.buildDirectory)
}
val copyExe by tasks.registering(Copy::class) {
val distWin by tasks.registering(Zip::class) {
group = "jadx"
description = "Copy exe to build dir"
description = "Build Windows bundle"
// next task dependencies not needed, but gradle throws warning because of same output dir
mustRunAfter("jar")
mustRunAfter(pack)
val guiTask = tasks.getByPath("jadx-gui:copyDistWin")
dependsOn(guiTask)
from(guiTask.outputs)
from(tasks.getByPath("jadx-gui:createExe"))
include("*.exe")
into(layout.buildDirectory)
destinationDirectory.set(layout.buildDirectory.dir("distWin"))
archiveFileName.set("jadx-gui-$jadxVersion-win.zip")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
val distWinBundle by tasks.registering(Copy::class) {
group = "jadx"
description = "Copy bundle to build dir"
val distWinWithJre by tasks.registering(Zip::class) {
description = "Build Windows with JRE bundle"
dependsOn(tasks.getByPath(":jadx-gui:distWinWithJre"))
val guiTask = tasks.getByPath(":jadx-gui:copyDistWinWithJre")
dependsOn(guiTask)
from(guiTask.outputs)
// next task dependencies not needed, but gradle throws warning because of same output dir
mustRunAfter("jar")
mustRunAfter(pack)
from(tasks.getByPath("jadx-gui:distWinWithJre").outputs) {
include("*.zip")
}
into(layout.buildDirectory)
destinationDirectory.set(layout.buildDirectory.dir("distWinWithJre"))
archiveFileName.set("jadx-gui-$jadxVersion-with-jre-win.zip")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
val dist by tasks.registering {
group = "jadx"
description = "Build jadx distribution zip"
description = "Build jadx distribution zip bundles"
dependsOn(pack)
@@ -144,15 +150,14 @@ val dist by tasks.registering {
if (os.isWindows) {
if (project.hasProperty("bundleJRE")) {
println("Build win bundle with JRE")
dependsOn(distWinBundle)
dependsOn(distWinWithJre)
} else {
dependsOn(copyExe)
dependsOn(distWin)
}
}
}
val cleanBuildDir by tasks.registering(Delete::class) {
group = "jadx"
delete(layout.buildDirectory)
}
tasks.getByName("clean").dependsOn(cleanBuildDir)
+3 -1
View File
@@ -3,7 +3,9 @@ plugins {
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21")
implementation("org.openrewrite:plugin:6.19.1")
}
repositories {
+14 -8
View File
@@ -3,26 +3,27 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
java
checkstyle
id("jadx-rewrite")
}
val jadxVersion: String by rootProject.extra
val jadxBuildJavaVersion: Int? by rootProject.extra
group = "io.github.skylot"
version = jadxVersion
dependencies {
implementation("org.slf4j:slf4j-api:2.0.13")
compileOnly("org.jetbrains:annotations:24.1.0")
implementation("org.slf4j:slf4j-api:2.0.16")
compileOnly("org.jetbrains:annotations:26.0.1")
testImplementation("ch.qos.logback:logback-classic:1.5.6")
testImplementation("org.hamcrest:hamcrest-library:2.2")
testImplementation("org.mockito:mockito-core:5.11.0")
testImplementation("org.assertj:assertj-core:3.25.3")
testImplementation("ch.qos.logback:logback-classic:1.5.12")
testImplementation("org.assertj:assertj-core:3.26.3")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
testImplementation("org.junit.jupiter:junit-jupiter:5.11.3")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testCompileOnly("org.jetbrains:annotations:24.1.0")
testCompileOnly("org.jetbrains:annotations:26.0.1")
}
repositories {
@@ -32,6 +33,11 @@ repositories {
}
java {
jadxBuildJavaVersion?.let { buildJavaVer ->
toolchain {
languageVersion = JavaLanguageVersion.of(buildJavaVer)
}
}
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
@@ -30,7 +30,7 @@ publishing {
}
pom {
name.set(project.name)
description.set("Dex to Java decompiler")
description.set(project.description ?: "Dex to Java decompiler")
url.set("https://github.com/skylot/jadx")
licenses {
license {
@@ -42,14 +42,14 @@ publishing {
developer {
id.set("skylot")
name.set("Skylot")
email.set("skylot@gmail.com")
email.set(project.properties["libEmail"].toString())
url.set("https://github.com/skylot")
}
}
scm {
connection .set("scm:git:git://github.com/skylot/jadx.git")
connection.set("scm:git:git://github.com/skylot/jadx.git")
developerConnection.set("scm:git:ssh://github.com:skylot/jadx.git")
url .set("https://github.com/skylot/jadx")
url.set("https://github.com/skylot/jadx")
}
}
}
@@ -0,0 +1,37 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
id("org.openrewrite.rewrite")
}
repositories {
mavenCentral()
}
dependencies {
rewrite("org.openrewrite.recipe:rewrite-testing-frameworks:2.21.0")
rewrite("org.openrewrite.recipe:rewrite-logging-frameworks:2.15.1")
rewrite("org.openrewrite.recipe:rewrite-migrate-java:2.28.0")
rewrite("org.openrewrite.recipe:rewrite-static-analysis:1.19.0")
}
tasks {
rewrite {
// exclusion("src/test/java/jadx/tests/integration")
// activeRecipe("org.openrewrite.java.migrate.Java8toJava11")
// checkstyle auto fix
// activeRecipe("org.openrewrite.staticanalysis.CodeCleanup")
// setCheckstyleConfigFile(file("$rootDir/config/checkstyle/checkstyle.xml"))
// logging
// activeRecipe("org.openrewrite.java.logging.slf4j.Slf4jBestPractices")
// activeRecipe("org.openrewrite.java.logging.slf4j.LoggersNamedForEnclosingClass")
// activeRecipe("org.openrewrite.java.logging.slf4j.ParameterizedLogging")
// activeRecipe("org.openrewrite.java.logging.PrintStackTraceToLogError")
// testing
activeRecipe("org.openrewrite.java.testing.assertj.Assertj")
}
}
+6 -2
View File
@@ -120,7 +120,10 @@
<module name="SuppressWarningsHolder"/>
<module name="IllegalType"/>
<module name="IllegalType">
<property name="illegalClassNames" value="java.util.ArrayList, java.util.HashMap, java.util.HashSet,
java.util.LinkedHashMap, java.util.LinkedHashSet, java.util.TreeMap, java.util.TreeSet"/>
</module>
<module name="IllegalImport">
<property name="illegalClasses" value="jadx.core.utils.DebugUtils"/>
</module>
@@ -128,7 +131,8 @@
<property name="id" value="printstacktrace"/>
<property name="format" value="\.printStackTrace\(\)"/>
<property name="ignoreComments" value="true"/>
<property name="message" value="Using Throwable.printStackTrace() is forbidden. Use logger to print exception"/>
<property name="message"
value="Using Throwable.printStackTrace() is forbidden. Use logger to print exception"/>
</module>
</module>
+2 -4
View File
@@ -1,7 +1,7 @@
/*
* Generated on 11/22/21, 8:58 PM
*/
package jadx.gui.ui.codeearea;
package jadx.gui.ui.codearea;
import java.io.*;
import javax.swing.text.Segment;
@@ -9,7 +9,7 @@ import javax.swing.text.Segment;
import org.fife.ui.rsyntaxtextarea.*;
/**
/*
* 用于Smali代码高亮
* MartinKay@qq.com
*/
@@ -173,7 +173,6 @@ import org.fife.ui.rsyntaxtextarea.*;
zzAtEOF = false;
}
%}
Letter = [A-Za-z]
@@ -678,4 +677,3 @@ FLAG_ARRAY = (":array_"{SimpleName})
\n { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
<<EOF>> { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
}
+16 -13
View File
@@ -57,12 +57,12 @@
private int yychar;
/**
* the number of characters from the last newline up to the start of the
* the number of characters from the last newline up to the start of the
* matched text
*/
private int yycolumn;
/**
/**
* zzAtBOL == true <=> the scanner is currently at the beginning of a line
*/
private boolean zzAtBOL = true;
@@ -102,6 +102,9 @@
zzLexicalState = newState;
}
public final int yystate() {
return zzLexicalState;
}
/**
* Returns the text matched by the current regular expression.
@@ -112,12 +115,12 @@
/**
* Returns the character at position <tt>pos</tt> from the
* matched text.
*
* Returns the character at position <tt>pos</tt> from the
* matched text.
*
* It is equivalent to yytext().charAt(pos), but faster
*
* @param pos the position of the character to fetch.
* @param pos the position of the character to fetch.
* A value from 0 to yylength()-1.
*
* @return the character at position pos
@@ -138,8 +141,8 @@
/**
* Reports an error that occured while scanning.
*
* In a wellformed scanner (no or only correct usage of
* yypushback(int) and a match-all fallback rule) this method
* In a wellformed scanner (no or only correct usage of
* yypushback(int) and a match-all fallback rule) this method
* will only be called with things that "Can't Possibly Happen".
* If this method is called, something is seriously wrong
* (e.g. a JFlex bug producing a faulty scanner etc.).
@@ -159,7 +162,7 @@
}
--- throws clause
}
}
/**
@@ -206,12 +209,12 @@
zzAction = -1;
zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
--- start admin (lexstate etc)
zzForAction: {
while (true) {
--- next input, line, col, char count, next transition, isFinal action
zzAction = zzState;
zzMarkedPosL = zzCurrentPosL;
@@ -226,11 +229,11 @@
--- char count update
--- actions
default:
default:
if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
zzAtEOF = true;
--- eofvalue
}
}
else {
--- no match
}
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+5 -2
View File
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
Vendored
+2
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
+5 -3
View File
@@ -1,14 +1,16 @@
plugins {
id("jadx-java")
id("jadx-library")
id("application")
// use shadow only for application scripts, jar will be copied from jadx-gui
id("com.github.johnrengelman.shadow") version "8.1.1"
id("com.gradleup.shadow") version "8.3.5"
}
dependencies {
implementation(project(":jadx-core"))
implementation(project(":jadx-plugins-tools"))
implementation(project(":jadx-commons:jadx-app-commons"))
runtimeOnly(project(":jadx-plugins:jadx-dex-input"))
runtimeOnly(project(":jadx-plugins:jadx-java-input"))
@@ -20,8 +22,8 @@ dependencies {
runtimeOnly(project(":jadx-plugins:jadx-xapk-input"))
runtimeOnly(project(":jadx-plugins:jadx-aab-input"))
implementation("org.jcommander:jcommander:1.83")
implementation("ch.qos.logback:logback-classic:1.5.6")
implementation("org.jcommander:jcommander:2.0")
implementation("ch.qos.logback:logback-classic:1.5.12")
}
application {
@@ -4,7 +4,7 @@ import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -13,6 +13,7 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameterized;
@@ -133,14 +134,16 @@ public class JCommanderWrapper<T> {
out.println("options:");
List<ParameterDescription> params = jc.getParameters();
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
Map<String, ParameterDescription> paramsMap = new HashMap<>(params.size());
int maxNamesLen = 0;
for (ParameterDescription p : params) {
paramsMap.put(p.getParameterized().getName(), p);
int len = p.getNames().length();
if (len > maxNamesLen) {
maxNamesLen = len;
String valueDesc = getValueDesc(p);
if (valueDesc != null) {
len += 1 + valueDesc.length();
}
maxNamesLen = Math.max(maxNamesLen, len);
}
maxNamesLen += 3;
@@ -153,8 +156,12 @@ public class JCommanderWrapper<T> {
}
StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames());
String description = p.getDescription();
String valueDesc = getValueDesc(p);
if (valueDesc != null) {
opt.append(' ').append(valueDesc);
}
addSpaces(opt, maxNamesLen - opt.length());
String description = p.getDescription();
if (description.contains("\n")) {
String[] lines = description.split("\n");
opt.append("- ").append(lines[0]);
@@ -177,6 +184,11 @@ public class JCommanderWrapper<T> {
return maxNamesLen;
}
private static @Nullable String getValueDesc(ParameterDescription p) {
Parameter parameterAnnotation = p.getParameterAnnotation();
return parameterAnnotation == null ? null : parameterAnnotation.defaultValueDescription();
}
/**
* Get all declared fields of the specified class and all super classes
*/
@@ -228,7 +240,7 @@ public class JCommanderWrapper<T> {
for (PluginContext context : pluginManager.getAllPluginContexts()) {
JadxPluginOptions options = context.getOptions();
if (options != null) {
if (appendPlugin(context.getPluginInfo(), context.getOptions(), sb, maxNamesLen, k)) {
if (appendPlugin(context.getPluginInfo(), context.getOptions(), sb, maxNamesLen)) {
k++;
}
}
@@ -240,12 +252,12 @@ public class JCommanderWrapper<T> {
return "\nPlugin options (-P<name>=<value>):" + sb;
}
private boolean appendPlugin(JadxPluginInfo pluginInfo, JadxPluginOptions options, StringBuilder out, int maxNamesLen, int k) {
private boolean appendPlugin(JadxPluginInfo pluginInfo, JadxPluginOptions options, StringBuilder out, int maxNamesLen) {
List<OptionDescription> descs = options.getOptionsDescriptions();
if (descs.isEmpty()) {
return false;
}
out.append("\n ").append(k).append(") ");
out.append("\n ");
out.append(pluginInfo.getPluginId()).append(": ").append(pluginInfo.getDescription());
for (OptionDescription desc : descs) {
StringBuilder opt = new StringBuilder();
+40 -16
View File
@@ -1,5 +1,7 @@
package jadx.cli;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -8,36 +10,40 @@ import jadx.api.JadxDecompiler;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.NoOpCodeCache;
import jadx.api.impl.SimpleCodeWriter;
import jadx.api.security.JadxSecurityFlag;
import jadx.api.security.impl.JadxSecurity;
import jadx.cli.LogHelper.LogLevelEnum;
import jadx.cli.plugins.JadxFilesGetter;
import jadx.commons.app.JadxCommonEnv;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.files.FileUtils;
import jadx.plugins.tools.JadxExternalPluginsLoader;
public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
public static void main(String[] args) {
int result = 0;
int result = 1;
try {
result = execute(args);
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
result = 1;
} catch (Throwable e) {
LOG.error("Process error:", e);
result = 1;
} finally {
FileUtils.deleteTempRootDir();
System.exit(result);
}
}
public static int execute(String[] args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) {
return processAndSave(jadxArgs);
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) {
return processAndSave(jadxArgs);
}
return 0;
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
return 1;
} catch (Throwable e) {
LOG.error("Process error:", e);
return 1;
}
return 0;
}
private static int processAndSave(JadxCLIArgs cliArgs) {
@@ -46,7 +52,9 @@ public class JadxCLI {
JadxArgs jadxArgs = cliArgs.toJadxArgs();
jadxArgs.setCodeCache(new NoOpCodeCache());
jadxArgs.setPluginLoader(new JadxExternalPluginsLoader());
jadxArgs.setFilesGetter(JadxFilesGetter.INSTANCE);
initCodeWriterProvider(jadxArgs);
applyEnvVars(jadxArgs);
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load();
if (checkForErrors(jadx)) {
@@ -60,11 +68,11 @@ public class JadxCLI {
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors, count: {}", errorsCount);
} else {
LOG.info("done");
return 1;
}
LOG.info("done");
return 0;
}
return 0;
}
private static void initCodeWriterProvider(JadxArgs jadxArgs) {
@@ -79,6 +87,22 @@ public class JadxCLI {
}
}
private static void applyEnvVars(JadxArgs jadxArgs) {
Set<JadxSecurityFlag> flags = JadxSecurityFlag.all();
boolean modified = false;
boolean disableXmlSecurity = JadxCommonEnv.getBool("JADX_DISABLE_XML_SECURITY", false);
if (disableXmlSecurity) {
flags.remove(JadxSecurityFlag.SECURE_XML_PARSER);
// TODO: not related to 'xml security', but kept for compatibility
flags.remove(JadxSecurityFlag.VERIFY_APP_PACKAGE);
modified = true;
}
// TODO: migrate 'ZipSecurity'
if (modified) {
jadxArgs.setSecurity(new JadxSecurity(flags));
}
}
private static boolean checkForErrors(JadxDecompiler jadx) {
if (jadx.getRoot().getClasses().isEmpty()) {
if (jadx.getArgs().isSkipResources()) {
@@ -27,6 +27,7 @@ import jadx.api.JadxDecompiler;
import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.args.IntegerFormat;
import jadx.api.args.ResourceNameSource;
import jadx.api.args.UseSourceNameAsClassNameAlias;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.core.deobf.conditions.DeobfWhitelist;
import jadx.core.utils.exceptions.JadxArgsValidateException;
@@ -108,6 +109,9 @@ public class JadxCLIArgs {
@Parameter(names = "--no-finally", description = "don't extract finally block")
protected boolean extractFinally = true;
@Parameter(names = "--no-restore-switch-over-string", description = "don't restore switch over string")
protected boolean restoreSwitchOverString = true;
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
protected boolean replaceConsts = true;
@@ -166,8 +170,15 @@ public class JadxCLIArgs {
)
protected GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = false;
@SuppressWarnings("DeprecatedIsStillUsed")
@Parameter(
names = { "--deobf-use-sourcename" },
description = "use source file name as class name alias."
+ "\nDEPRECATED, use \"--use-source-name-as-class-name-alias\" instead",
hidden = true
)
@Deprecated
protected Boolean deobfuscationUseSourceNameAsAlias = null;
@Parameter(
names = { "--deobf-res-name-source" },
@@ -179,6 +190,16 @@ public class JadxCLIArgs {
)
protected ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
@Parameter(
names = { "--use-source-name-as-class-name-alias" },
description = "use source name as class name alias:"
+ "\n 'always' - always use source name if it's available"
+ "\n 'if-better' - use source name if it seems better than the current one"
+ "\n 'never' - never use source name, even if it's available",
converter = UseSourceNameAsClassNameConverter.class
)
protected UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias = null;
@Parameter(
names = { "--use-kotlin-methods-for-var-names" },
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
@@ -243,6 +264,9 @@ public class JadxCLIArgs {
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
protected boolean quiet = false;
@Parameter(names = { "--disable-plugins" }, description = "comma separated list of plugin ids to disable")
protected String disablePlugins = "";
@Parameter(names = { "--version" }, description = "print jadx version")
protected boolean printVersion = false;
@@ -327,7 +351,7 @@ public class JadxCLIArgs {
args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setDeobfuscationWhitelist(Arrays.asList(deobfuscationWhitelistStr.split(" ")));
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setUseSourceNameAsClassNameAlias(getUseSourceNameAsClassNameAlias());
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
args.setResourceNameSource(resourceNameSource);
args.setEscapeUnicode(escapeUnicode);
@@ -342,12 +366,14 @@ public class JadxCLIArgs {
args.setMoveInnerClasses(moveInnerClasses);
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
args.setExtractFinally(extractFinally);
args.setRestoreSwitchOverString(restoreSwitchOverString);
args.setRenameFlags(renameFlags);
args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel);
args.setIntegerFormat(integerFormat);
args.setUseDxInput(useDx);
args.setPluginOptions(pluginOptions);
args.setDisabledPlugins(Arrays.stream(disablePlugins.split(",")).map(String::trim).collect(Collectors.toSet()));
return args;
}
@@ -435,6 +461,10 @@ public class JadxCLIArgs {
return extractFinally;
}
public boolean isRestoreSwitchOverString() {
return restoreSwitchOverString;
}
public Path getUserRenamesMappingsPath() {
return userRenamesMappingsPath;
}
@@ -467,8 +497,23 @@ public class JadxCLIArgs {
return generatedRenamesMappingFileMode;
}
public UseSourceNameAsClassNameAlias getUseSourceNameAsClassNameAlias() {
if (useSourceNameAsClassNameAlias != null) {
return useSourceNameAsClassNameAlias;
} else if (deobfuscationUseSourceNameAsAlias != null) {
// noinspection deprecation
return UseSourceNameAsClassNameAlias.create(deobfuscationUseSourceNameAsAlias);
} else {
return UseSourceNameAsClassNameAlias.getDefault();
}
}
/**
* @deprecated Use {@link #getUseSourceNameAsClassNameAlias()} instead.
*/
@Deprecated
public boolean isDeobfuscationUseSourceNameAsAlias() {
return deobfuscationUseSourceNameAsAlias;
return getUseSourceNameAsClassNameAlias().toBoolean();
}
public ResourceNameSource getResourceNameSource() {
@@ -539,6 +584,10 @@ public class JadxCLIArgs {
return pluginOptions;
}
public String getDisablePlugins() {
return disablePlugins;
}
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
private final String paramName;
@@ -592,6 +641,12 @@ public class JadxCLIArgs {
}
}
public static class UseSourceNameAsClassNameConverter extends BaseEnumConverter<UseSourceNameAsClassNameAlias> {
public UseSourceNameAsClassNameConverter() {
super(UseSourceNameAsClassNameAlias::valueOf, UseSourceNameAsClassNameAlias::values);
}
}
public static class DecompilationModeConverter extends BaseEnumConverter<DecompilationMode> {
public DecompilationModeConverter() {
super(DecompilationMode::valueOf, DecompilationMode::values);
@@ -1,7 +1,7 @@
package jadx.cli;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import com.beust.jcommander.JCommander;
@@ -10,7 +10,7 @@ import jadx.cli.commands.ICommand;
import jadx.core.utils.exceptions.JadxArgsValidateException;
public class JadxCLICommands {
private static final Map<String, ICommand> COMMANDS_MAP = new TreeMap<>();
private static final Map<String, ICommand> COMMANDS_MAP = new LinkedHashMap<>();
static {
JadxCLICommands.register(new CommandPlugins());
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.args.UseSourceNameAsClassNameAlias;
import jadx.core.clsp.ClsSet;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.files.FileUtils;
@@ -47,7 +48,7 @@ public class ConvertToClsSet {
// disable not needed passes executed at prepare stage
jadxArgs.setDeobfuscationOn(false);
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
jadxArgs.setUseSourceNameAsClassAlias(false);
jadxArgs.setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias.NEVER);
jadxArgs.setMoveInnerClasses(false);
jadxArgs.setInlineAnonymousClasses(false);
jadxArgs.setInlineMethods(false);
@@ -1,12 +1,18 @@
package jadx.cli.commands;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import jadx.api.plugins.JadxPluginInfo;
import jadx.cli.JCommanderWrapper;
import jadx.cli.LogHelper;
import jadx.core.utils.StringUtils;
import jadx.plugins.tools.JadxPluginsList;
import jadx.plugins.tools.JadxPluginsTools;
import jadx.plugins.tools.data.JadxPluginMetadata;
@@ -15,24 +21,40 @@ import jadx.plugins.tools.data.JadxPluginUpdate;
@Parameters(commandDescription = "manage jadx plugins")
public class CommandPlugins implements ICommand {
@Parameter(names = { "-i", "--install" }, description = "install plugin with locationId")
@Parameter(names = { "-i", "--install" }, description = "install plugin with locationId", defaultValueDescription = "<locationId>")
protected String install;
@Parameter(names = { "-j", "--install-jar" }, description = "install plugin from jar file")
@Parameter(names = { "-j", "--install-jar" }, description = "install plugin from jar file", defaultValueDescription = "<path-to.jar>")
protected String installJar;
@Parameter(names = { "-l", "--list" }, description = "list installed plugins")
protected boolean list;
@Parameter(names = { "-a", "--available" }, description = "list available plugins")
@Parameter(names = { "-a", "--available" }, description = "list available plugins from jadx-plugins-list (aka marketplace)")
protected boolean available;
@Parameter(names = { "-u", "--update" }, description = "update installed plugins")
protected boolean update;
@Parameter(names = { "--uninstall" }, description = "uninstall plugin with pluginId")
@Parameter(names = { "--uninstall" }, description = "uninstall plugin with pluginId", defaultValueDescription = "<pluginId>")
protected String uninstall;
@Parameter(names = { "--disable" }, description = "disable plugin with pluginId", defaultValueDescription = "<pluginId>")
protected String disable;
@Parameter(names = { "--enable" }, description = "enable plugin with pluginId", defaultValueDescription = "<pluginId>")
protected String enable;
@Parameter(names = { "--list-all" }, description = "list all plugins including bundled and dropins")
protected boolean listAll;
@Parameter(
names = { "--list-versions" },
description = "fetch latest versions of plugin from locationId (will download all artefacts, limited to 10)",
defaultValueDescription = "<locationId>"
)
protected String listVersions;
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
protected boolean printHelp = false;
@@ -41,21 +63,33 @@ public class CommandPlugins implements ICommand {
return "plugins";
}
@SuppressWarnings("UnnecessaryReturnStatement")
@Override
public void process(JCommanderWrapper<?> jcw, JCommander subCommander) {
if (printHelp) {
jcw.printUsage(subCommander);
return;
}
Set<String> unknownOptions = new HashSet<>(subCommander.getUnknownOptions());
boolean verbose = unknownOptions.remove("-v") || unknownOptions.remove("--verbose");
LogHelper.setLogLevel(verbose ? LogHelper.LogLevelEnum.DEBUG : LogHelper.LogLevelEnum.INFO);
if (!unknownOptions.isEmpty()) {
System.out.println("Error: found unknown options: " + unknownOptions);
}
if (install != null) {
installPlugin(install);
return;
}
if (installJar != null) {
installPlugin("file:" + installJar);
return;
}
if (uninstall != null) {
boolean uninstalled = JadxPluginsTools.getInstance().uninstall(uninstall);
System.out.println(uninstalled ? "Uninstalled" : "Plugin not found");
return;
}
if (update) {
List<JadxPluginUpdate> updates = JadxPluginsTools.getInstance().updateAll();
@@ -67,27 +101,99 @@ public class CommandPlugins implements ICommand {
System.out.println(" " + update.getPluginId() + ": " + update.getOldVersion() + " -> " + update.getNewVersion());
}
}
return;
}
if (list) {
List<JadxPluginMetadata> installed = JadxPluginsTools.getInstance().getInstalled();
System.out.println("Installed plugins: " + installed.size());
int i = 1;
for (JadxPluginMetadata plugin : installed) {
System.out.println(" " + (i++) + ") "
+ plugin.getPluginId() + " (" + plugin.getVersion() + ") - "
+ plugin.getName() + ": " + plugin.getDescription());
}
printPlugins(JadxPluginsTools.getInstance().getInstalled());
return;
}
if (listAll) {
printAllPlugins();
return;
}
if (listVersions != null) {
printVersions(listVersions, 10);
return;
}
if (available) {
List<JadxPluginMetadata> availableList = JadxPluginsList.getInstance().get();
System.out.println("Available plugins: " + availableList.size());
int i = 1;
for (JadxPluginMetadata plugin : availableList) {
System.out.println(" " + (i++) + ") "
+ plugin.getName() + ": " + plugin.getDescription()
System.out.println(" - " + plugin.getName() + ": " + plugin.getDescription()
+ " (" + plugin.getLocationId() + ")");
}
return;
}
if (disable != null) {
if (JadxPluginsTools.getInstance().changeDisabledStatus(disable, true)) {
System.out.println("Plugin '" + disable + "' disabled.");
} else {
System.out.println("Plugin '" + disable + "' already disabled.");
}
return;
}
if (enable != null) {
if (JadxPluginsTools.getInstance().changeDisabledStatus(enable, false)) {
System.out.println("Plugin '" + enable + "' enabled.");
} else {
System.out.println("Plugin '" + enable + "' already enabled.");
}
return;
}
}
private static void printPlugins(List<JadxPluginMetadata> installed) {
System.out.println("Installed plugins: " + installed.size());
for (JadxPluginMetadata plugin : installed) {
StringBuilder sb = new StringBuilder();
sb.append(" - ").append(plugin.getPluginId());
String version = plugin.getVersion();
if (version != null) {
sb.append(" (").append(version).append(')');
}
if (plugin.isDisabled()) {
sb.append(" (disabled)");
}
sb.append(" - ").append(plugin.getName());
sb.append(": ").append(plugin.getDescription());
System.out.println(sb);
}
}
private void printVersions(String locationId, int limit) {
System.out.println("Loading ...");
List<JadxPluginMetadata> versions = JadxPluginsTools.getInstance().getVersionsByLocation(locationId, 1, limit);
if (versions.isEmpty()) {
System.out.println("No versions found");
return;
}
JadxPluginMetadata plugin = versions.get(0);
System.out.println("Versions for plugin id: " + plugin.getPluginId());
for (JadxPluginMetadata version : versions) {
StringBuilder sb = new StringBuilder();
sb.append(" - ").append(version.getVersion());
String reqVer = version.getRequiredJadxVersion();
if (StringUtils.notBlank(reqVer)) {
sb.append(", require jadx: ").append(reqVer);
}
System.out.println(sb);
}
}
private static void printAllPlugins() {
List<JadxPluginMetadata> installed = JadxPluginsTools.getInstance().getInstalled();
printPlugins(installed);
Set<String> installedSet = installed.stream().map(JadxPluginMetadata::getPluginId).collect(Collectors.toSet());
List<JadxPluginInfo> plugins = JadxPluginsTools.getInstance().getAllPluginsInfo();
System.out.println("Other plugins: " + plugins.size());
for (JadxPluginInfo plugin : plugins) {
if (!installedSet.contains(plugin.getPluginId())) {
System.out.println(" - " + plugin.getPluginId()
+ " - " + plugin.getName()
+ ": " + plugin.getDescription());
}
}
}
@@ -0,0 +1,31 @@
package jadx.cli.plugins;
import java.nio.file.Path;
import jadx.commons.app.JadxCommonFiles;
import jadx.commons.app.JadxTempFiles;
import jadx.core.plugins.files.IJadxFilesGetter;
public class JadxFilesGetter implements IJadxFilesGetter {
public static final JadxFilesGetter INSTANCE = new JadxFilesGetter();
@Override
public Path getConfigDir() {
return JadxCommonFiles.getConfigDir();
}
@Override
public Path getCacheDir() {
return JadxCommonFiles.getCacheDir();
}
@Override
public Path getTempDir() {
return JadxTempFiles.getTempRootDir();
}
private JadxFilesGetter() {
// singleton
}
}
@@ -12,7 +12,6 @@ import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,8 +19,11 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.android.TextResMapFile;
import jadx.core.utils.files.ZipFile;
import jadx.core.xmlgen.ResTableBinaryParser;
import static jadx.core.utils.files.FileUtils.expandDirs;
/**
* Utility class for convert '.arsc' to simple text file with mapping id to resource name
*/
@@ -30,7 +32,7 @@ public class ConvertArscFile {
private static int rewritesCount;
public static void usage() {
LOG.info("<res-map file> <input .arsc files>");
LOG.info("<res-map file> <input .arsc/android.jar files or dir>");
LOG.info("");
LOG.info("Note: If res-map already exists - it will be merged and updated");
}
@@ -42,6 +44,7 @@ public class ConvertArscFile {
}
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
Path resMapFile = inputPaths.remove(0);
List<Path> inputResFiles = filterAndSort(expandDirs(inputPaths));
Map<Integer, String> resMap;
if (Files.isReadable(resMapFile)) {
resMap = TextResMapFile.read(resMapFile);
@@ -52,8 +55,7 @@ public class ConvertArscFile {
RootNode root = new RootNode(new JadxArgs()); // not really needed
rewritesCount = 0;
for (Path resFile : inputPaths) {
LOG.info("Processing {}", resFile);
for (Path resFile : inputResFiles) {
ResTableBinaryParser resTableParser = new ResTableBinaryParser(root, true);
if (resFile.getFileName().toString().endsWith(".jar")) {
// Load resources.arsc from android.jar
@@ -84,6 +86,16 @@ public class ConvertArscFile {
LOG.info("done");
}
private static List<Path> filterAndSort(List<Path> inputPaths) {
return inputPaths.stream()
.filter(p -> {
String fileName = p.getFileName().toString();
return fileName.endsWith(".arsc") || fileName.endsWith(".jar");
})
.sorted()
.collect(Collectors.toList());
}
private static void mergeResMaps(Map<Integer, String> mainResMap, Map<Integer, String> newResMap) {
for (Map.Entry<Integer, String> entry : newResMap.entrySet()) {
Integer id = entry.getKey();
@@ -3,14 +3,12 @@ package jadx.cli;
import java.util.Collections;
import java.util.Map;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.utils.Utils.newConstStringMap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.assertj.core.api.Assertions.assertThat;
public class JadxCLIArgsTest {
@@ -18,38 +16,38 @@ public class JadxCLIArgsTest {
@Test
public void testInvertedBooleanOption() {
assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false));
assertThat(parse("").isReplaceConsts(), is(true));
assertThat(parse("--no-replace-consts").isReplaceConsts()).isFalse();
assertThat(parse("").isReplaceConsts()).isTrue();
}
@Test
public void testEscapeUnicodeOption() {
assertThat(parse("--escape-unicode").isEscapeUnicode(), is(true));
assertThat(parse("").isEscapeUnicode(), is(false));
assertThat(parse("--escape-unicode").isEscapeUnicode()).isTrue();
assertThat(parse("").isEscapeUnicode()).isFalse();
}
@Test
public void testSrcOption() {
assertThat(parse("--no-src").isSkipSources(), is(true));
assertThat(parse("-s").isSkipSources(), is(true));
assertThat(parse("").isSkipSources(), is(false));
assertThat(parse("--no-src").isSkipSources()).isTrue();
assertThat(parse("-s").isSkipSources()).isTrue();
assertThat(parse("").isSkipSources()).isFalse();
}
@Test
public void testOptionsOverride() {
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports(), is(false));
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo(), is(false));
assertThat(override(new JadxCLIArgs(), "").isUseImports(), is(true));
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports()).isFalse();
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo()).isFalse();
assertThat(override(new JadxCLIArgs(), "").isUseImports()).isTrue();
JadxCLIArgs args = new JadxCLIArgs();
args.useImports = false;
assertThat(override(args, "--no-imports").isUseImports(), is(false));
assertThat(override(args, "--no-imports").isUseImports()).isFalse();
args.debugInfo = false;
assertThat(override(args, "--no-debug-info").isDebugInfo(), is(false));
assertThat(override(args, "--no-debug-info").isDebugInfo()).isFalse();
args = new JadxCLIArgs();
args.useImports = false;
assertThat(override(args, "").isUseImports(), is(false));
assertThat(override(args, "").isUseImports()).isFalse();
}
@Test
@@ -83,7 +81,7 @@ public class JadxCLIArgsTest {
JadxCLIArgs args = new JadxCLIArgs();
args.pluginOptions = baseMap;
Map<String, String> resultMap = override(args, providedArgs).getPluginOptions();
assertThat(resultMap, Matchers.equalTo(expectedMap));
assertThat(resultMap).isEqualTo(expectedMap);
}
private JadxCLIArgs parse(String... args) {
@@ -91,15 +89,15 @@ public class JadxCLIArgsTest {
}
private JadxCLIArgs parse(JadxCLIArgs jadxArgs, String... args) {
boolean res = jadxArgs.processArgs(args);
assertThat(res, is(true));
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
return jadxArgs;
return check(jadxArgs, jadxArgs.processArgs(args));
}
private JadxCLIArgs override(JadxCLIArgs jadxArgs, String... args) {
boolean res = jadxArgs.overrideProvided(args);
assertThat(res, is(true));
return check(jadxArgs, jadxArgs.overrideProvided(args));
}
private static JadxCLIArgs check(JadxCLIArgs jadxArgs, boolean res) {
assertThat(res).isTrue();
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
return jadxArgs;
}
@@ -9,9 +9,8 @@ import jadx.api.JadxArgs.RenameEnum;
import jadx.cli.JadxCLIArgs.RenameConverter;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class RenameConverterTest {
@@ -25,16 +24,16 @@ public class RenameConverterTest {
@Test
public void all() {
Set<RenameEnum> set = converter.convert("all");
assertEquals(3, set.size());
assertTrue(set.contains(RenameEnum.CASE));
assertTrue(set.contains(RenameEnum.VALID));
assertTrue(set.contains(RenameEnum.PRINTABLE));
assertThat(set).hasSize(3);
assertThat(set).contains(RenameEnum.CASE);
assertThat(set).contains(RenameEnum.VALID);
assertThat(set).contains(RenameEnum.PRINTABLE);
}
@Test
public void none() {
Set<RenameEnum> set = converter.convert("none");
assertTrue(set.isEmpty());
assertThat(set).isEmpty();
}
@Test
@@ -43,7 +42,6 @@ public class RenameConverterTest {
() -> converter.convert("wrong"),
"Expected convert() to throw, but it didn't");
assertEquals("'wrong' is unknown for parameter someParam, possible values are case, valid, printable",
thrown.getMessage());
assertThat(thrown.getMessage()).isEqualTo("'wrong' is unknown for parameter someParam, possible values are case, valid, printable");
}
}
+87 -52
View File
@@ -12,71 +12,111 @@ import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterAll;
import org.assertj.core.api.Condition;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.utils.files.FileUtils;
import static org.assertj.core.api.Assertions.assertThat;
public class TestInput {
private static final Logger LOG = LoggerFactory.getLogger(TestInput.class);
private static final PathMatcher LOG_ALL_FILES = path -> {
LOG.debug("File in result dir: {}", path);
return true;
};
@TempDir
Path testDir;
@Test
public void testHelp() {
int result = JadxCLI.execute(new String[] { "--help" });
assertThat(result).isEqualTo(0);
}
@Test
public void testApkInput() throws Exception {
int result = JadxCLI.execute(buildArgs(List.of(), "samples/small.apk"));
assertThat(result).isEqualTo(0);
List<Path> resultFiles = collectAllFilesInDir(testDir);
printFiles(resultFiles);
assertThat(resultFiles)
.describedAs("check output files")
.map(p -> p.getFileName().toString())
.haveExactly(2, new Condition<>(f -> f.endsWith(".java"), "java classes"))
.haveExactly(9, new Condition<>(f -> f.endsWith(".xml"), "xml resources"))
.haveExactly(1, new Condition<>(f -> f.equals("classes.dex"), "dex"))
.haveExactly(1, new Condition<>(f -> f.equals("AndroidManifest.xml"), "manifest"))
.hasSize(13);
}
@Test
public void testDexInput() throws Exception {
decompile("dex", "samples/hello.dex");
decompile("samples/hello.dex");
}
@Test
public void testSmaliInput() throws Exception {
decompile("smali", "samples/HelloWorld.smali");
decompile("samples/HelloWorld.smali");
}
@Test
public void testClassInput() throws Exception {
decompile("class", "samples/HelloWorld.class");
decompile("samples/HelloWorld.class");
}
@Test
public void testMultipleInput() throws Exception {
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
decompile("samples/hello.dex", "samples/HelloWorld.smali");
}
@Test
public void testFallbackMode() throws Exception {
int result = JadxCLI.execute(buildArgs(List.of("-f"), "samples/hello.dex"));
assertThat(result).isEqualTo(0);
List<Path> files = collectJavaFilesInDir(testDir);
assertThat(files).hasSize(1);
}
@Test
public void testSimpleMode() throws Exception {
int result = JadxCLI.execute(buildArgs(List.of("--decompilation-mode", "simple"), "samples/hello.dex"));
assertThat(result).isEqualTo(0);
List<Path> files = collectJavaFilesInDir(testDir);
assertThat(files).hasSize(1);
}
@Test
public void testResourceOnly() throws Exception {
decode("resourceOnly", "samples/resources-only.apk");
}
private void decode(String tmpDirName, String apkSample) throws URISyntaxException, IOException {
List<String> args = new ArrayList<>();
Path tempDir = FileUtils.createTempDir(tmpDirName);
args.add("-v");
args.add("-d");
args.add(tempDir.toAbsolutePath().toString());
URL resource = getClass().getClassLoader().getResource(apkSample);
assertThat(resource).isNotNull();
String sampleFile = resource.toURI().getRawPath();
args.add(sampleFile);
int result = JadxCLI.execute(args.toArray(new String[0]));
int result = JadxCLI.execute(buildArgs(List.of(), "samples/resources-only.apk"));
assertThat(result).isEqualTo(0);
List<Path> files = Files.find(
tempDir,
3,
(file, attr) -> file.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"))
.collect(Collectors.toList());
assertThat(files.isEmpty()).isFalse();
List<Path> files = collectFilesInDir(testDir,
path -> path.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"));
assertThat(files).isNotEmpty();
}
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
List<String> args = new ArrayList<>();
Path tempDir = FileUtils.createTempDir(tmpDirName);
private void decompile(String... inputSamples) throws URISyntaxException, IOException {
int result = JadxCLI.execute(buildArgs(List.of(), inputSamples));
assertThat(result).isEqualTo(0);
List<Path> resultJavaFiles = collectJavaFilesInDir(testDir);
assertThat(resultJavaFiles).isNotEmpty();
// do not copy input files as resources
for (Path path : collectFilesInDir(testDir, LOG_ALL_FILES)) {
for (String inputSample : inputSamples) {
assertThat(path.toAbsolutePath().toString()).doesNotContain(inputSample);
}
}
}
private String[] buildArgs(List<String> options, String... inputSamples) throws URISyntaxException {
List<String> args = new ArrayList<>(options);
args.add("-v");
args.add("-d");
args.add(tempDir.toAbsolutePath().toString());
args.add(testDir.toAbsolutePath().toString());
for (String inputSample : inputSamples) {
URL resource = getClass().getClassLoader().getResource(inputSample);
@@ -84,21 +124,13 @@ public class TestInput {
String sampleFile = resource.toURI().getRawPath();
args.add(sampleFile);
}
return args.toArray(new String[0]);
}
int result = JadxCLI.execute(args.toArray(new String[0]));
assertThat(result).isEqualTo(0);
List<Path> resultJavaFiles = collectJavaFilesInDir(tempDir);
assertThat(resultJavaFiles).isNotEmpty();
// do not copy input files as resources
PathMatcher logAllFiles = path -> {
LOG.debug("File in result dir: {}", path);
return true;
};
for (Path path : collectFilesInDir(tempDir, logAllFiles)) {
for (String inputSample : inputSamples) {
assertThat(path.toAbsolutePath().toString()).doesNotContain(inputSample);
}
private void printFiles(List<Path> files) {
LOG.info("Output files (count: {}):", files.size());
for (Path file : files) {
LOG.info(" {}", testDir.relativize(file));
}
}
@@ -107,6 +139,14 @@ public class TestInput {
return collectFilesInDir(dir, javaMatcher);
}
private static List<Path> collectAllFilesInDir(Path dir) throws IOException {
try (Stream<Path> pathStream = Files.walk(dir)) {
return pathStream
.filter(Files::isRegularFile)
.collect(Collectors.toList());
}
}
private static List<Path> collectFilesInDir(Path dir, PathMatcher matcher) throws IOException {
try (Stream<Path> pathStream = Files.walk(dir)) {
return pathStream
@@ -115,9 +155,4 @@ public class TestInput {
.collect(Collectors.toList());
}
}
@AfterAll
public static void cleanup() {
FileUtils.clearTempRootDir();
}
}
Binary file not shown.
@@ -3,6 +3,7 @@ package jadx.commons.app;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
@@ -35,20 +36,20 @@ public class JadxCommonFiles {
public void init() {
try {
configDir = loadEnvDir("JADX_CONFIG_DIR");
cacheDir = loadEnvDir("JADX_CACHE_DIR");
configDir = loadEnvDir("JADX_CONFIG_DIR", pd -> pd.configDir);
cacheDir = loadEnvDir("JADX_CACHE_DIR", pd -> pd.cacheDir);
} catch (Exception e) {
throw new RuntimeException("Failed to init common directories", e);
}
}
private Path loadEnvDir(String envVar) throws IOException {
private Path loadEnvDir(String envVar, Function<ProjectDirectories, String> dirFunc) throws IOException {
String envDir = JadxCommonEnv.get(envVar, null);
String dirStr;
if (envDir != null) {
dirStr = envDir;
} else {
dirStr = loadDirs().configDir;
dirStr = dirFunc.apply(loadDirs());
}
Path path = Path.of(dirStr).toAbsolutePath();
Files.createDirectories(path);
@@ -0,0 +1,31 @@
package jadx.commons.app;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class JadxTempFiles {
private static final String JADX_TMP_INSTANCE_PREFIX = "jadx-instance-";
private static final Path TEMP_ROOT_DIR = createTempRootDir();
public static Path getTempRootDir() {
return TEMP_ROOT_DIR;
}
private static Path createTempRootDir() {
try {
String jadxTmpDir = System.getenv("JADX_TMP_DIR");
Path dir;
if (jadxTmpDir != null) {
dir = Files.createTempDirectory(Paths.get(jadxTmpDir), JADX_TMP_INSTANCE_PREFIX);
} else {
dir = Files.createTempDirectory(JADX_TMP_INSTANCE_PREFIX);
}
dir.toFile().deleteOnExit();
return dir;
} catch (Exception e) {
throw new RuntimeException("Failed to create temp root directory", e);
}
}
}
+28 -3
View File
@@ -5,9 +5,9 @@ plugins {
dependencies {
api(project(":jadx-plugins:jadx-input-api"))
implementation("com.google.code.gson:gson:2.10.1")
implementation("com.google.code.gson:gson:2.11.0")
testImplementation("org.apache.commons:commons-lang3:3.14.0")
testImplementation("org.apache.commons:commons-lang3:3.17.0")
testImplementation(project(":jadx-plugins:jadx-dex-input"))
testRuntimeOnly(project(":jadx-plugins:jadx-smali-input"))
@@ -24,6 +24,31 @@ dependencies {
testImplementation("tools.profiler:async-profiler:3.0")
}
tasks.test {
val jadxTestJavaVersion = getTestJavaVersion()
fun getTestJavaVersion(): Int? {
val envVarName = "JADX_TEST_JAVA_VERSION"
val testJavaVer = System.getenv(envVarName)?.toInt() ?: return null
val currentJavaVer = java.toolchain.languageVersion.get().asInt()
if (testJavaVer < currentJavaVer) {
throw GradleException("'$envVarName' can't be set to lower version than $currentJavaVer")
}
println("Set Java toolchain for core tests to version '$testJavaVer'")
return testJavaVer
}
tasks.named<Test>("test") {
jadxTestJavaVersion?.let { testJavaVer ->
javaLauncher =
javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(testJavaVer)
}
}
// disable cache to allow test's rerun,
// because most tests are integration and depends on plugins and environment
outputs.cacheIf { false }
// exclude temp tests
exclude("**/tmp/*")
}
+92 -6
View File
@@ -7,6 +7,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -21,6 +22,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.args.IntegerFormat;
import jadx.api.args.ResourceNameSource;
import jadx.api.args.UseSourceNameAsClassNameAlias;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.api.data.ICodeData;
import jadx.api.deobf.IAliasProvider;
@@ -29,12 +31,17 @@ import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache;
import jadx.api.plugins.loader.JadxBasePluginLoader;
import jadx.api.plugins.loader.JadxPluginLoader;
import jadx.api.security.IJadxSecurity;
import jadx.api.security.JadxSecurityFlag;
import jadx.api.security.impl.JadxSecurity;
import jadx.api.usage.IUsageInfoCache;
import jadx.api.usage.impl.InMemoryUsageInfoCache;
import jadx.core.deobf.DeobfAliasProvider;
import jadx.core.deobf.conditions.DeobfWhitelist;
import jadx.core.deobf.conditions.JadxRenameConditions;
import jadx.core.plugins.PluginContext;
import jadx.core.plugins.files.IJadxFilesGetter;
import jadx.core.plugins.files.TempFilesGetter;
import jadx.core.utils.files.FileUtils;
public class JadxArgs implements Closeable {
@@ -98,7 +105,7 @@ public class JadxArgs implements Closeable {
private UserRenamesMappingsMode userRenamesMappingsMode = UserRenamesMappingsMode.getDefault();
private boolean deobfuscationOn = false;
private boolean useSourceNameAsClassAlias = false;
private UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias = UseSourceNameAsClassNameAlias.getDefault();
private File generatedRenamesMappingFile = null;
private GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
@@ -127,6 +134,8 @@ public class JadxArgs implements Closeable {
private boolean respectBytecodeAccModifiers = false;
private boolean exportAsGradleProject = false;
private boolean restoreSwitchOverString = true;
private boolean skipXmlPrettyPrint = false;
private boolean fsCaseSensitive;
@@ -163,13 +172,31 @@ public class JadxArgs implements Closeable {
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
/**
* Additional files structure info.
* Defaults to tmp dirs.
*/
private IJadxFilesGetter filesGetter = TempFilesGetter.INSTANCE;
/**
* Additional data validation and security checks
*/
private IJadxSecurity security = new JadxSecurity(JadxSecurityFlag.all());
/**
* Don't save files (can be using for performance testing)
*/
private boolean skipFilesSave = false;
/**
* Run additional expensive checks to verify internal invariants and info integrity
*/
private boolean runDebugChecks = false;
private Map<String, String> pluginOptions = new HashMap<>();
private Set<String> disabledPlugins = new HashSet<>();
private JadxPluginLoader pluginLoader = new JadxBasePluginLoader();
private boolean loadJadxClsSetFile = true;
@@ -425,12 +452,29 @@ public class JadxArgs implements Closeable {
this.generatedRenamesMappingFileMode = mode;
}
public boolean isUseSourceNameAsClassAlias() {
return useSourceNameAsClassAlias;
public UseSourceNameAsClassNameAlias getUseSourceNameAsClassNameAlias() {
return useSourceNameAsClassNameAlias;
}
public void setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias) {
this.useSourceNameAsClassNameAlias = useSourceNameAsClassNameAlias;
}
/**
* @deprecated Use {@link #getUseSourceNameAsClassNameAlias()} instead.
*/
@Deprecated
public boolean isUseSourceNameAsClassAlias() {
return getUseSourceNameAsClassNameAlias().toBoolean();
}
/**
* @deprecated Use {@link #setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias)} instead.
*/
@Deprecated
public void setUseSourceNameAsClassAlias(boolean useSourceNameAsClassAlias) {
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
final var useSourceNameAsClassNameAlias = UseSourceNameAsClassNameAlias.create(useSourceNameAsClassAlias);
setUseSourceNameAsClassNameAlias(useSourceNameAsClassNameAlias);
}
public int getDeobfuscationMinLength() {
@@ -521,6 +565,14 @@ public class JadxArgs implements Closeable {
this.exportAsGradleProject = exportAsGradleProject;
}
public boolean isRestoreSwitchOverString() {
return restoreSwitchOverString;
}
public void setRestoreSwitchOverString(boolean restoreSwitchOverString) {
this.restoreSwitchOverString = restoreSwitchOverString;
}
public boolean isSkipXmlPrettyPrint() {
return skipXmlPrettyPrint;
}
@@ -677,6 +729,22 @@ public class JadxArgs implements Closeable {
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
}
public IJadxFilesGetter getFilesGetter() {
return filesGetter;
}
public void setFilesGetter(IJadxFilesGetter filesGetter) {
this.filesGetter = filesGetter;
}
public IJadxSecurity getSecurity() {
return security;
}
public void setSecurity(IJadxSecurity security) {
this.security = security;
}
public boolean isSkipFilesSave() {
return skipFilesSave;
}
@@ -685,6 +753,14 @@ public class JadxArgs implements Closeable {
this.skipFilesSave = skipFilesSave;
}
public boolean isRunDebugChecks() {
return runDebugChecks;
}
public void setRunDebugChecks(boolean runDebugChecks) {
this.runDebugChecks = runDebugChecks;
}
public Map<String, String> getPluginOptions() {
return pluginOptions;
}
@@ -693,6 +769,14 @@ public class JadxArgs implements Closeable {
this.pluginOptions = pluginOptions;
}
public Set<String> getDisabledPlugins() {
return disabledPlugins;
}
public void setDisabledPlugins(Set<String> disabledPlugins) {
this.disabledPlugins = disabledPlugins;
}
public JadxPluginLoader getPluginLoader() {
return pluginLoader;
}
@@ -716,10 +800,11 @@ public class JadxArgs implements Closeable {
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
+ inlineAnonymousClasses + inlineMethods + moveInnerClasses + allowInlineKotlinLambda
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength + deobfuscationWhitelist
+ useSourceNameAsClassNameAlias
+ resourceNameSource
+ useKotlinMethodsForVarNames
+ insertDebugLines + extractFinally
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
+ debugInfo + escapeUnicode + replaceConsts + restoreSwitchOverString
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
+ commentsLevel + useDxInput + integerFormat
+ "|" + buildPluginsHash(decompiler);
@@ -755,7 +840,7 @@ public class JadxArgs implements Closeable {
+ ", generatedRenamesMappingFile=" + generatedRenamesMappingFile
+ ", generatedRenamesMappingFileMode=" + generatedRenamesMappingFileMode
+ ", resourceNameSource=" + resourceNameSource
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", useSourceNameAsClassNameAlias=" + useSourceNameAsClassNameAlias
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
+ ", insertDebugLines=" + insertDebugLines
+ ", extractFinally=" + extractFinally
@@ -764,6 +849,7 @@ public class JadxArgs implements Closeable {
+ ", deobfuscationWhitelist=" + deobfuscationWhitelist
+ ", escapeUnicode=" + escapeUnicode
+ ", replaceConsts=" + replaceConsts
+ ", restoreSwitchOverString=" + restoreSwitchOverString
+ ", respectBytecodeAccModifiers=" + respectBytecodeAccModifiers
+ ", exportAsGradleProject=" + exportAsGradleProject
+ ", skipXmlPrettyPrint=" + skipXmlPrettyPrint
@@ -50,8 +50,8 @@ import jadx.core.utils.DecompilerScheduler;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipPatch;
import jadx.core.utils.tasks.TaskExecutor;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
/**
@@ -85,35 +85,37 @@ public final class JadxDecompiler implements Closeable {
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
private final JadxArgs args;
private final JadxPluginManager pluginManager = new JadxPluginManager(this);
private final JadxPluginManager pluginManager;
private final List<ICodeLoader> loadedInputs = new ArrayList<>();
private RootNode root;
private List<JavaClass> classes;
private List<ResourceFile> resources;
private BinaryXMLParser binaryXmlParser;
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
private final JadxEventsImpl events = new JadxEventsImpl();
private final ResourcesLoader resourcesLoader = new ResourcesLoader(this);
private final ResourcesLoader resourcesLoader;
private final List<ICodeLoader> customCodeLoaders = new ArrayList<>();
private final List<CustomResourcesLoader> customResourcesLoaders = new ArrayList<>();
private final Map<JadxPassType, List<JadxPass>> customPasses = new HashMap<>();
private IJadxEvents events = new JadxEventsImpl();
public JadxDecompiler() {
this(new JadxArgs());
}
public JadxDecompiler(JadxArgs args) {
this.args = args;
this.args = Objects.requireNonNull(args);
this.pluginManager = new JadxPluginManager(this);
this.resourcesLoader = new ResourcesLoader(this);
}
public void load() {
reset();
JadxArgsValidator.validate(this);
LOG.info("loading ...");
FileUtils.updateTempRootDir(args.getFilesGetter().getTempDir());
loadPlugins();
loadInputFiles();
@@ -143,7 +145,9 @@ public final class JadxDecompiler implements Closeable {
private void loadInputFiles() {
loadedInputs.clear();
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
List<File> inputs = ZipPatch.patchZipFiles(args.getInputFiles());
args.setInputFiles(inputs);
List<Path> inputPaths = Utils.collectionMap(inputs, File::toPath);
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
long start = System.currentTimeMillis();
for (PluginContext plugin : pluginManager.getResolvedPluginContexts()) {
@@ -168,7 +172,6 @@ public final class JadxDecompiler implements Closeable {
root = null;
classes = null;
resources = null;
binaryXmlParser = null;
events.reset();
}
@@ -178,6 +181,7 @@ public final class JadxDecompiler implements Closeable {
closeInputs();
closeLoaders();
args.close();
FileUtils.clearTempRootDir();
}
private void closeInputs() {
@@ -196,7 +200,7 @@ public final class JadxDecompiler implements Closeable {
try {
resourcesLoader.close();
} catch (Exception e) {
LOG.error("Failed to close resource loader: " + resourcesLoader, e);
LOG.error("Failed to close resource loader: {}", resourcesLoader, e);
}
}
customResourcesLoaders.clear();
@@ -467,13 +471,6 @@ public final class JadxDecompiler implements Closeable {
return root;
}
synchronized BinaryXMLParser getBinaryXmlParser() {
if (binaryXmlParser == null) {
binaryXmlParser = new BinaryXMLParser(root);
}
return binaryXmlParser;
}
/**
* Get JavaClass by ClassNode without loading and decompilation
*/
@@ -672,6 +669,10 @@ public final class JadxDecompiler implements Closeable {
return events;
}
public void setEventsImpl(IJadxEvents eventsImpl) {
this.events = eventsImpl;
}
public void addCustomCodeLoader(ICodeLoader customCodeLoader) {
customCodeLoaders.add(customCodeLoader);
}
@@ -252,6 +252,10 @@ public final class JavaClass implements JavaNode {
return cls.getPackage();
}
public JavaPackage getJavaPackage() {
return cls.getPackageNode().getJavaNode();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
@@ -2,7 +2,6 @@ package jadx.api;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.jetbrains.annotations.ApiStatus;
@@ -79,15 +78,9 @@ public final class JavaMethod implements JavaNode {
return Collections.emptyList();
}
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
return ovrdAttr.getRelatedMthNodes().stream()
.map(m -> {
JavaMethod javaMth = decompiler.convertMethodNode(m);
if (javaMth == null) {
LOG.warn("Failed convert to java method: {}", m);
}
return javaMth;
})
.filter(Objects::nonNull)
return ovrdAttr.getRelatedMthNodes()
.stream()
.map(decompiler::convertMethodNode)
.collect(Collectors.toList());
}
@@ -104,6 +97,10 @@ public final class JavaMethod implements JavaNode {
return mth.getDefPosition();
}
public String getCodeStr() {
return mth.getCodeStr();
}
@Override
public void removeAlias() {
this.mth.getMethodInfo().removeAlias();
@@ -76,6 +76,22 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return !Objects.equals(parent, aliasParent);
}
public boolean isDescendantOf(JavaPackage ancestor) {
JavaPackage current = this;
while (current != null) {
if (ancestor.equals(current)) {
return true;
}
if (current.getPkgNode().getParentPkg() == null) {
current = null;
} else {
current = current.getPkgNode().getParentPkg().getJavaNode();
}
}
return false;
}
@Override
public ICodeNodeRef getCodeNodeRef() {
return pkgNode;
@@ -9,7 +9,6 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -27,6 +26,7 @@ import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipFile;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.IResTableParser;
import jadx.core.xmlgen.ResContainer;
@@ -0,0 +1,38 @@
package jadx.api.args;
import jadx.core.utils.exceptions.JadxRuntimeException;
public enum UseSourceNameAsClassNameAlias {
ALWAYS,
IF_BETTER,
NEVER;
public static UseSourceNameAsClassNameAlias getDefault() {
return NEVER;
}
/**
* @deprecated Use {@link UseSourceNameAsClassNameAlias} directly.
*/
@Deprecated
public boolean toBoolean() {
switch (this) {
case IF_BETTER:
return true;
case NEVER:
return false;
case ALWAYS:
throw new JadxRuntimeException("No match between " + this + " and boolean");
default:
throw new JadxRuntimeException("Unhandled strategy: " + this);
}
}
/**
* @deprecated Use {@link UseSourceNameAsClassNameAlias} directly.
*/
@Deprecated
public static UseSourceNameAsClassNameAlias create(boolean useSourceNameAsAlias) {
return useSourceNameAsAlias ? IF_BETTER : NEVER;
}
}
@@ -62,7 +62,7 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
buf.append(cw.getCodeStr());
return this;
}
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
AnnotatedCodeWriter code = (AnnotatedCodeWriter) cw;
line--;
int startPos = getLength();
for (Map.Entry<Integer, ICodeAnnotation> entry : code.annotations.entrySet()) {
@@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.plugins.data.IJadxFiles;
import jadx.api.plugins.data.IJadxPlugins;
import jadx.api.plugins.events.IJadxEvents;
import jadx.api.plugins.gui.JadxGuiContext;
@@ -53,4 +54,9 @@ public interface JadxPluginContext {
* Access to registered plugins and runtime data
*/
IJadxPlugins plugins();
/**
* Access to plugin specific files and directories
*/
IJadxFiles files();
}
@@ -1,15 +1,29 @@
package jadx.api.plugins;
import org.jetbrains.annotations.Nullable;
public class JadxPluginInfo {
private final String pluginId;
private final String name;
private final String description;
private final String homepage;
private String homepage;
/**
* Conflicting plugins should have the same 'provides' property; only one will be loaded
*/
private final String provides;
private String provides;
/**
* Minimum required jadx version to run this plugin.
* <br>
* Format: "<stable version>, r<revision number of unstable version>".
* Example: "1.5.1, r2305"
*
* @see <a href="https://github.com/skylot/jadx/wiki/Jadx-plugins-guide#required-jadx-version">wiki
* page</a>
* for details.
*/
private @Nullable String requiredJadxVersion;
public JadxPluginInfo(String id, String name, String description) {
this(id, name, description, "", id);
@@ -43,10 +57,26 @@ public class JadxPluginInfo {
return homepage;
}
public void setHomepage(String homepage) {
this.homepage = homepage;
}
public String getProvides() {
return provides;
}
public void setProvides(String provides) {
this.provides = provides;
}
public @Nullable String getRequiredJadxVersion() {
return requiredJadxVersion;
}
public void setRequiredJadxVersion(@Nullable String requiredJadxVersion) {
this.requiredJadxVersion = requiredJadxVersion;
}
@Override
public String toString() {
return pluginId + ": " + name + " - '" + description + '\'';
@@ -4,11 +4,14 @@ import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import jadx.core.plugins.versions.VerifyRequiredVersion;
public class JadxPluginInfoBuilder {
private String pluginId;
private String name;
private String description;
private String homepage = "";
private @Nullable String requiredJadxVersion;
private @Nullable String provides;
/**
@@ -43,6 +46,11 @@ public class JadxPluginInfoBuilder {
return this;
}
public JadxPluginInfoBuilder requiredJadxVersion(String versions) {
this.requiredJadxVersion = versions;
return this;
}
public JadxPluginInfo build() {
Objects.requireNonNull(pluginId, "PluginId is required");
Objects.requireNonNull(name, "Name is required");
@@ -50,6 +58,11 @@ public class JadxPluginInfoBuilder {
if (provides == null) {
provides = pluginId;
}
return new JadxPluginInfo(pluginId, name, description, homepage, provides);
if (requiredJadxVersion != null) {
VerifyRequiredVersion.verify(requiredJadxVersion);
}
JadxPluginInfo pluginInfo = new JadxPluginInfo(pluginId, name, description, homepage, provides);
pluginInfo.setRequiredJadxVersion(requiredJadxVersion);
return pluginInfo;
}
}
@@ -0,0 +1,21 @@
package jadx.api.plugins.data;
import java.nio.file.Path;
public interface IJadxFiles {
/**
* Plugin cache directory.
*/
Path getPluginCacheDir();
/**
* Plugin config directory.
*/
Path getPluginConfigDir();
/**
* Plugin temp directory.
*/
Path getPluginTempDir();
}
@@ -15,4 +15,15 @@ public interface IJadxEvents {
* For public event types check {@link JadxEvents} class.
*/
<E extends IJadxEvent> void addListener(JadxEventType<E> eventType, Consumer<E> listener);
/**
* Remove listener for specific event.
* Listener should be same or equal object.
*/
<E extends IJadxEvent> void removeListener(JadxEventType<E> eventType, Consumer<E> listener);
/**
* Clear all listeners.
*/
void reset();
}
@@ -6,4 +6,13 @@ public abstract class JadxEventType<T extends IJadxEvent> {
return new JadxEventType<>() {
};
}
public static <E extends IJadxEvent> JadxEventType<E> create(String name) {
return new JadxEventType<>() {
@Override
public String toString() {
return name;
}
};
}
}
@@ -16,17 +16,17 @@ public class JadxEvents {
/**
* Notify about renaming done by user (GUI only).
*/
public static final JadxEventType<NodeRenamedByUser> NODE_RENAMED_BY_USER = create();
public static final JadxEventType<NodeRenamedByUser> NODE_RENAMED_BY_USER = create("NODE_RENAMED_BY_USER");
/**
* Request reload of a current project (GUI only).
*/
public static final JadxEventType<ReloadProject> RELOAD_PROJECT = create();
public static final JadxEventType<ReloadProject> RELOAD_PROJECT = create("RELOAD_PROJECT");
/**
* Request reload of a settings window (GUI only).
* Useful for a reload custom settings group which was set with
* {@link JadxGuiSettings#setCustomSettingsGroup(ISettingsGroup)}.
*/
public static final JadxEventType<ReloadSettingsWindow> RELOAD_SETTINGS_WINDOW = create();
public static final JadxEventType<ReloadSettingsWindow> RELOAD_SETTINGS_WINDOW = create("RELOAD_SETTINGS_WINDOW");
}
@@ -3,6 +3,7 @@ package jadx.api.plugins.gui;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.swing.JFrame;
import javax.swing.KeyStroke;
import org.jetbrains.annotations.Nullable;
@@ -50,6 +51,12 @@ public interface JadxGuiContext {
*/
JadxGuiSettings settings();
/**
* Main window component.
* Can be used as a parent for creating new windows or dialogs.
*/
JFrame getMainFrame();
ICodeNodeRef getNodeUnderCaret();
ICodeNodeRef getNodeUnderMouse();
@@ -8,7 +8,6 @@ import java.util.Enumeration;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -16,6 +15,7 @@ import org.slf4j.LoggerFactory;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.ZipFile;
public class ZipSecurity {
private static final Logger LOG = LoggerFactory.getLogger(ZipSecurity.class);
@@ -0,0 +1,20 @@
package jadx.api.security;
import java.io.InputStream;
import org.w3c.dom.Document;
public interface IJadxSecurity {
/**
* Check if application package is safe
*
* @return normalized/sanitized string or same string if safe
*/
String verifyAppPackage(String appPackage);
/**
* XML document parser
*/
Document parseXml(InputStream in);
}
@@ -0,0 +1,18 @@
package jadx.api.security;
import java.util.EnumSet;
import java.util.Set;
public enum JadxSecurityFlag {
VERIFY_APP_PACKAGE,
SECURE_XML_PARSER;
public static Set<JadxSecurityFlag> all() {
return EnumSet.allOf(JadxSecurityFlag.class);
}
public static Set<JadxSecurityFlag> none() {
return EnumSet.noneOf(JadxSecurityFlag.class);
}
}
@@ -0,0 +1,73 @@
package jadx.api.security.impl;
import java.io.InputStream;
import java.util.Set;
import javax.xml.parsers.DocumentBuilderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import jadx.api.security.IJadxSecurity;
import jadx.api.security.JadxSecurityFlag;
import jadx.core.deobf.NameMapper;
public class JadxSecurity implements IJadxSecurity {
private static final Logger LOG = LoggerFactory.getLogger(JadxSecurity.class);
private final Set<JadxSecurityFlag> flags;
public JadxSecurity(Set<JadxSecurityFlag> flags) {
this.flags = flags;
}
@Override
public String verifyAppPackage(String appPackage) {
if (flags.contains(JadxSecurityFlag.VERIFY_APP_PACKAGE)
&& !NameMapper.isValidFullIdentifier(appPackage)) {
LOG.warn("App package '{}' has invalid format and will be ignored", appPackage);
return "INVALID_PACKAGE";
}
return appPackage;
}
@Override
public Document parseXml(InputStream in) {
DocumentBuilderFactory dbf;
if (flags.contains(JadxSecurityFlag.SECURE_XML_PARSER)) {
dbf = SecureDBFHolder.INSTANCE;
} else {
dbf = SimpleDBFHolder.INSTANCE;
}
try {
return dbf.newDocumentBuilder().parse(in);
} catch (Exception e) {
throw new RuntimeException("Failed to parse xml", e);
}
}
private static final class SimpleDBFHolder {
private static final DocumentBuilderFactory INSTANCE = DocumentBuilderFactory.newInstance();
}
private static final class SecureDBFHolder {
private static final DocumentBuilderFactory INSTANCE = buildSecureDBF();
private static DocumentBuilderFactory buildSecureDBF() {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature("http://apache.org/xml/features/dom/create-entity-ref-nodes", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
return dbf;
} catch (Exception e) {
throw new RuntimeException("Fail to build secure XML DocumentBuilderFactory", e);
}
}
}
}
@@ -1,5 +1,13 @@
package jadx.api.utils;
import java.util.function.BiFunction;
import jadx.api.ICodeInfo;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.core.dex.nodes.MethodNode;
public class CodeUtils {
public static String getLineForPos(String code, int pos) {
@@ -47,4 +55,71 @@ public class CodeUtils {
line++;
}
}
/**
* Cut method code (including comments and annotations) from class code.
*
* @return method code or empty string if metadata is not available
*/
public static String extractMethodCode(MethodNode mth, ICodeInfo codeInfo) {
int end = getMethodEnd(mth, codeInfo);
if (end == -1) {
return "";
}
int start = getMethodStart(mth, codeInfo);
if (end < start) {
return "";
}
return codeInfo.getCodeStr().substring(start, end);
}
/**
* Search first empty line before method definition to include comments and annotations
*/
private static int getMethodStart(MethodNode mth, ICodeInfo codeInfo) {
int pos = mth.getDefPosition();
String newLineStr = mth.root().getArgs().getCodeNewLineStr();
String emptyLine = newLineStr + newLineStr;
int emptyLinePos = codeInfo.getCodeStr().lastIndexOf(emptyLine, pos);
return emptyLinePos == -1 ? pos : emptyLinePos + emptyLine.length();
}
/**
* Search method end position in provided class code info.
*
* @return end pos or -1 if metadata not available
*/
public static int getMethodEnd(MethodNode mth, ICodeInfo codeInfo) {
if (!codeInfo.hasMetadata()) {
return -1;
}
// skip nested nodes DEF/END until first unpaired END annotation (end of this method)
Integer end = codeInfo.getCodeMetadata().searchDown(mth.getDefPosition() + 1, new BiFunction<>() {
int nested = 0;
@Override
public Integer apply(Integer pos, ICodeAnnotation ann) {
switch (ann.getAnnType()) {
case DECLARATION:
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
switch (node.getAnnType()) {
case CLASS:
case METHOD:
nested++;
break;
}
break;
case END:
if (nested == 0) {
return pos;
}
nested--;
break;
}
return null;
}
});
return end == null ? -1 : end;
}
}
@@ -10,7 +10,7 @@ public class Consts {
public static final boolean DEBUG_FINALLY = false;
public static final boolean DEBUG_ATTRIBUTES = false;
public static final boolean DEBUG_RESTRUCTURE = false;
public static final boolean DEBUG_EVENTS = true;
public static final boolean DEBUG_EVENTS = Jadx.isDevVersion();
public static final String CLASS_OBJECT = "java.lang.Object";
public static final String CLASS_STRING = "java.lang.String";
+7 -1
View File
@@ -28,7 +28,6 @@ import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.ExtractFieldInit;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.FixAccessModifiers;
import jadx.core.dex.visitors.FixSwitchOverEnum;
import jadx.core.dex.visitors.GenericTypesVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor;
@@ -48,11 +47,13 @@ import jadx.core.dex.visitors.ReplaceNewArray;
import jadx.core.dex.visitors.ShadowFieldVisitor;
import jadx.core.dex.visitors.SignatureProcessor;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.blocks.BlockFinisher;
import jadx.core.dex.visitors.blocks.BlockProcessor;
import jadx.core.dex.visitors.blocks.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
import jadx.core.dex.visitors.fixaccessmodifiers.FixAccessModifiers;
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
import jadx.core.dex.visitors.prepare.AddAndroidConstants;
import jadx.core.dex.visitors.prepare.CollectConstValues;
@@ -62,6 +63,7 @@ import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.regions.SwitchOverStringVisitor;
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
import jadx.core.dex.visitors.rename.CodeRenameVisitor;
import jadx.core.dex.visitors.rename.RenameVisitor;
@@ -130,6 +132,7 @@ public class Jadx {
// blocks IR
passes.add(new BlockSplitter());
passes.add(new BlockProcessor());
passes.add(new BlockFinisher());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
@@ -170,6 +173,9 @@ public class Jadx {
// regions IR
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
if (args.isRestoreSwitchOverString()) {
passes.add(new SwitchOverStringVisitor());
}
passes.add(new ReturnVisitor());
passes.add(new CleanRegions());
@@ -8,7 +8,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.api.impl.SimpleCodeInfo;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.attributes.AFlag;
@@ -32,8 +31,8 @@ public class ProcessClass {
private final List<IDexTreeVisitor> passes;
public ProcessClass(JadxArgs args) {
this.passes = Jadx.getPassesList(args);
public ProcessClass(List<IDexTreeVisitor> passesList) {
this.passes = passesList;
}
@Nullable
@@ -125,6 +124,33 @@ public class ProcessClass {
}
}
/**
* Load and process class without its deps
*/
public void forceProcess(ClassNode cls) {
ClassNode topParentClass = cls.getTopParentClass();
if (topParentClass != cls) {
forceProcess(topParentClass);
return;
}
try {
process(cls, false);
} catch (Throwable e) {
throw new JadxRuntimeException("Failed to process class: " + cls.getFullName(), e);
}
}
/**
* Generate code for class without processing its deps
*/
public @Nullable ICodeInfo forceGenerateCode(ClassNode cls) {
try {
return process(cls, true);
} catch (Throwable e) {
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
}
}
public void initPasses(RootNode root) {
for (IDexTreeVisitor pass : passes) {
try {
@@ -24,7 +24,7 @@ public class ClspClass {
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
private List<ArgType> typeParameters = Collections.emptyList();
private ClspClassSource source;
private final ClspClassSource source;
public ClspClass(ArgType clsType, int id, int accFlags, ClspClassSource source) {
this.clsType = clsType;
@@ -652,6 +652,13 @@ public class ClassGen {
code.add(clsName);
}
public void addClsShortNameForced(ICodeWriter code, ClassInfo classInfo) {
code.add(classInfo.getAliasShortName());
if (!isBothClassesInOneTopClass(cls.getClassInfo(), classInfo)) {
addImport(classInfo);
}
}
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
String fullName = extClsInfo.getAliasFullName();
if (fallback || !useImports) {
@@ -434,7 +434,7 @@ public class InsnGen {
code.add(']');
}
int dim = arrayType.getArrayDimension();
for (; k < dim - 1; k++) {
for (; k < dim; k++) {
code.add("[]");
}
break;
@@ -749,6 +749,7 @@ public class InsnGen {
code.attachAnnotation(refMth);
code.add("this");
} else {
boolean forceShortName = addOuterClassInstance(insn, code, callMth);
code.add("new ");
if (refMth == null || refMth.contains(AFlag.DONT_GENERATE)) {
// use class reference if constructor method is missing (default constructor)
@@ -756,7 +757,11 @@ public class InsnGen {
} else {
code.attachAnnotation(refMth);
}
mgen.getClassGen().addClsName(code, insn.getClassType());
if (forceShortName) {
mgen.getClassGen().addClsShortNameForced(code, insn.getClassType());
} else {
mgen.getClassGen().addClsName(code, insn.getClassType());
}
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
if (genericInfoAttr != null) {
code.add('<');
@@ -777,6 +782,27 @@ public class InsnGen {
generateMethodArguments(code, insn, 0, callMth);
}
private boolean addOuterClassInstance(ConstructorInsn insn, ICodeWriter code, MethodNode callMth) throws CodegenException {
if (callMth == null || !callMth.contains(AFlag.SKIP_FIRST_ARG)) {
return false;
}
ClassNode ctrCls = callMth.getDeclaringClass();
if (!ctrCls.isInner() || insn.getArgsCount() == 0) {
return false;
}
InsnArg instArg = insn.getArg(0);
if (instArg.isThis()) {
return false;
}
// instance arg should be of an outer class type
if (!instArg.getType().equals(ctrCls.getDeclaringClass().getType())) {
return false;
}
addArgDot(code, instArg);
// can't use another dot, force short name of class
return true;
}
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
cls.ensureProcessed();
if (this.mth.getParentClass() == cls) {
@@ -269,7 +269,7 @@ public class MethodGen {
JadxArgs args = mth.root().getArgs();
switch (args.getDecompilationMode()) {
case AUTO:
if (classGen.isFallbackMode()) {
if (classGen.isFallbackMode() || mth.getRegion() == null) {
// TODO: try simple mode first
dumpInstructions(code);
} else {
@@ -278,6 +278,8 @@ public class RegionGen extends InsnGen {
useField(code, (FieldInfo) k, null);
} else if (k instanceof Integer) {
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
} else if (k instanceof String) {
code.add('\"').add((String) k).add('\"');
} else {
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
}
@@ -135,10 +135,17 @@ public class CodeGenUtils {
}
}
public static void addInputFileInfo(ICodeWriter code, ClassNode node) {
if (node.getClsData() != null && node.checkCommentsLevel(CommentsLevel.INFO)) {
String inputFileName = node.getClsData().getInputFileName();
public static void addInputFileInfo(ICodeWriter code, ClassNode cls) {
if (cls.checkCommentsLevel(CommentsLevel.INFO) && cls.getClsData() != null) {
String inputFileName = cls.getClsData().getInputFileName();
if (inputFileName != null) {
ClassNode declCls = cls.getDeclaringClass();
if (declCls != null
&& declCls.getClsData() != null
&& inputFileName.equals(declCls.getClsData().getInputFileName())) {
// don't add same comment for inner classes
return;
}
code.startLine("/* loaded from: ").add(inputFileName).add(" */");
}
}
@@ -19,6 +19,7 @@ public class NameMapper {
private static final Set<String> RESERVED_NAMES = new HashSet<>(
Arrays.asList(
"_",
"abstract",
"assert",
"boolean",
@@ -9,8 +9,7 @@ import jadx.core.dex.nodes.PackageNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Provides a list of all top level domains with 3 characters and less,
* so we can exclude them from deobfuscation.
* Provides a list of all top level domains, so we can exclude them from deobfuscation.
*/
public class ExcludePackageWithTLDNames extends AbstractDeobfCondition {
@@ -18,23 +17,22 @@ public class ExcludePackageWithTLDNames extends AbstractDeobfCondition {
* Lazy load TLD set
*/
private static class TldHolder {
private static final Set<String> TLD_SET = loadTldFile();
private static final Set<String> TLD_SET = loadTldSet();
}
private static Set<String> loadTldFile() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(TldHolder.class.getResourceAsStream("tld_3.txt")))) {
private static Set<String> loadTldSet() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(TldHolder.class.getResourceAsStream("tlds.txt")))) {
return reader.lines()
.map(String::trim)
.filter(line -> !line.startsWith("#") && !line.isEmpty())
.collect(Collectors.toSet());
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load top level domain list file: tld_3.txt", e);
throw new JadxRuntimeException("Failed to load top level domain list file: tlds.txt", e);
}
}
@Override
public Action check(PackageNode pkg) {
if (TldHolder.TLD_SET.contains(pkg.getName())) {
if (pkg.isRoot() && TldHolder.TLD_SET.contains(pkg.getName())) {
return Action.FORBID_RENAME;
}
return Action.NO_ACTION;
@@ -90,7 +90,6 @@ public enum AFlag {
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
REQUEST_CODE_SHRINK,
RERUN_SSA_TRANSFORM,
METHOD_CANDIDATE_FOR_INLINE,
USE_LINES_HINTS, // source lines info in methods can be trusted
@@ -5,6 +5,7 @@ import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.codegen.utils.CodeComment;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
@@ -74,6 +75,7 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>();
public static final AType<CodeFeaturesAttr> METHOD_CODE_FEATURES = new AType<>();
// region
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
@@ -0,0 +1,56 @@
package jadx.core.dex.attributes.nodes;
import java.util.EnumSet;
import java.util.Set;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.MethodNode;
public class CodeFeaturesAttr implements IJadxAttribute {
public enum CodeFeature {
/**
* Code contains switch instruction
*/
SWITCH,
/**
* Code contains new-array instruction
*/
NEW_ARRAY,
}
public static boolean contains(MethodNode mth, CodeFeature feature) {
CodeFeaturesAttr codeFeaturesAttr = mth.get(AType.METHOD_CODE_FEATURES);
if (codeFeaturesAttr == null) {
return false;
}
return codeFeaturesAttr.getCodeFeatures().contains(feature);
}
public static void add(MethodNode mth, CodeFeature feature) {
CodeFeaturesAttr codeFeaturesAttr = mth.get(AType.METHOD_CODE_FEATURES);
if (codeFeaturesAttr == null) {
codeFeaturesAttr = new CodeFeaturesAttr();
mth.addAttr(codeFeaturesAttr);
}
codeFeaturesAttr.getCodeFeatures().add(feature);
}
private final Set<CodeFeature> codeFeatures = EnumSet.noneOf(CodeFeature.class);
public Set<CodeFeature> getCodeFeatures() {
return codeFeatures;
}
@Override
public AType<CodeFeaturesAttr> getAttrType() {
return AType.METHOD_CODE_FEATURES;
}
@Override
public String toAttrString() {
return "CodeFeatures{" + codeFeatures + '}';
}
}
@@ -33,7 +33,7 @@ public class JadxCommentsAttr implements IJadxAttribute {
private final Map<CommentsLevel, List<String>> comments = new EnumMap<>(CommentsLevel.class);
public void add(CommentsLevel level, String comment) {
comments.computeIfAbsent(level, (l) -> new ArrayList<>()).add(comment);
comments.computeIfAbsent(level, l -> new ArrayList<>()).add(comment);
}
public List<String> formatAndFilter(CommentsLevel level) {
@@ -14,14 +14,14 @@ public class MethodOverrideAttr extends PinnedAttribute {
/**
* All methods overridden by current method. Current method excluded, empty for base method.
*/
private List<IMethodDetails> overrideList;
private final List<IMethodDetails> overrideList;
/**
* All method nodes from override hierarchy. Current method included.
*/
private SortedSet<MethodNode> relatedMthNodes;
private Set<IMethodDetails> baseMethods;
private final Set<IMethodDetails> baseMethods;
public MethodOverrideAttr(List<IMethodDetails> overrideList, SortedSet<MethodNode> relatedMthNodes, Set<IMethodDetails> baseMethods) {
this.overrideList = overrideList;
@@ -3,10 +3,6 @@ package jadx.core.dex.instructions;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.IMethodProto;
import jadx.api.plugins.input.data.IMethodRef;
@@ -17,6 +13,8 @@ import jadx.api.plugins.input.insns.custom.ISwitchPayload;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr;
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr.CodeFeature;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
@@ -35,8 +33,6 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.input.InsnDataUtils;
public class InsnDecoder {
private static final Logger LOG = LoggerFactory.getLogger(InsnDecoder.class);
private final MethodNode method;
private final RootNode root;
@@ -54,7 +50,7 @@ public class InsnDecoder {
rawInsn.decode();
insn = decode(rawInsn);
} catch (Exception e) {
method.addError("Failed to decode insn: " + rawInsn + ", method: " + method, e);
method.addError("Failed to decode insn: " + rawInsn, e);
insn = new InsnNode(InsnType.NOP, 0);
insn.addAttr(AType.JADX_ERROR, new JadxError("decode failed: " + e.getMessage(), e));
}
@@ -64,7 +60,6 @@ public class InsnDecoder {
return instructions;
}
@NotNull
protected InsnNode decode(InsnData insn) throws DecodeException {
switch (insn.getOpcode()) {
case NOP:
@@ -514,7 +509,6 @@ public class InsnDecoder {
}
}
@NotNull
private SwitchInsn makeSwitch(InsnData insn, boolean packed) {
SwitchInsn swInsn = new SwitchInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN), insn.getTarget(), packed);
ICustomPayload payload = insn.getPayload();
@@ -522,6 +516,7 @@ public class InsnDecoder {
swInsn.attachSwitchData(new SwitchData((ISwitchPayload) payload), insn.getTarget());
}
method.add(AFlag.COMPUTE_POST_DOM);
CodeFeaturesAttr.add(method, CodeFeature.SWITCH);
return swInsn;
}
@@ -545,6 +540,7 @@ public class InsnDecoder {
for (int i = 1; i < regsCount; i++) {
newArr.addArg(InsnArg.typeImmutableReg(insn, i, ArgType.INT));
}
CodeFeaturesAttr.add(method, CodeFeature.NEW_ARRAY);
return newArr;
}
@@ -43,6 +43,9 @@ public final class PhiInsn extends InsnNode {
if (blockBinds.contains(pred)) {
throw new JadxRuntimeException("Duplicate predecessors in PHI insn: " + pred + ", " + this);
}
if (pred == null) {
throw new JadxRuntimeException("Null bind block in PHI insn: " + this);
}
super.addArg(arg);
blockBinds.add(pred);
}
@@ -13,6 +13,7 @@ import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
@@ -291,6 +292,20 @@ public abstract class InsnArg extends Typed {
return false;
}
public boolean isSameCodeVar(RegisterArg arg) {
if (arg == null) {
return false;
}
if (isRegister()) {
return ((RegisterArg) this).sameCodeVar(arg);
}
return false;
}
public boolean isUseVar(RegisterArg arg) {
return InsnUtils.containsVar(this, arg);
}
protected final <T extends InsnArg> T copyCommonParams(T copy) {
copy.copyAttributesFrom(this);
copy.setParentInsn(parentInsn);
@@ -6,6 +6,7 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -292,10 +293,19 @@ public class SSAVar implements Comparable<SSAVar> {
StringBuilder sb = new StringBuilder();
sb.append('r').append(regNum).append('v').append(version);
if (!names.isEmpty()) {
sb.append(", names: ").append(names);
String orderedNames = names.stream()
.sorted()
.collect(Collectors.joining(", ", "[", "]"));
sb.append(", names: ").append(orderedNames);
}
if (!types.isEmpty()) {
sb.append(", types: ").append(types);
String orderedTypes = types.stream()
.map(String::valueOf)
.sorted()
.collect(Collectors.joining(", ", "[", "]"));
sb.append(", types: ").append(orderedTypes);
}
return sb.toString();
}
@@ -25,8 +25,12 @@ public final class ConstructorInsn extends BaseInvokeNode {
}
public ConstructorInsn(MethodNode mth, InvokeNode invoke) {
this(mth, invoke, invoke.getCallMth());
}
public ConstructorInsn(MethodNode mth, InvokeNode invoke, MethodInfo callMth) {
super(InsnType.CONSTRUCTOR, invoke.getArgsCount() - 1);
this.callMth = invoke.getCallMth();
this.callMth = callMth;
this.callType = getCallType(mth, callMth.getDeclClass(), invoke.getArg(0));
int argsCount = invoke.getArgsCount();
for (int i = 1; i < argsCount; i++) {
@@ -226,6 +226,10 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
return contains(AFlag.RETURN);
}
public boolean isMthExitBlock() {
return contains(AFlag.MTH_EXIT_BLOCK);
}
public boolean isEmpty() {
return instructions.isEmpty();
}
@@ -37,6 +37,7 @@ import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.api.plugins.input.data.impl.ListConsumer;
import jadx.api.usage.IUsageInfoData;
import jadx.core.Consts;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
@@ -324,9 +325,10 @@ public class ClassNode extends NotificationAttrNode
try {
unload();
args.setDecompilationMode(mode);
ProcessClass process = new ProcessClass(args);
ProcessClass process = new ProcessClass(Jadx.getPassesList(args));
process.initPasses(root);
return process.generateCode(this);
ICodeInfo code = process.forceGenerateCode(this);
return Utils.getOrElse(code, ICodeInfo.EMPTY);
} finally {
args.setDecompilationMode(baseMode);
unload();
@@ -584,6 +586,11 @@ public class ClassNode extends NotificationAttrNode
return null;
}
@Override
public ClassNode getDeclaringClass() {
return isInner() ? parentClass : null;
}
public ClassNode getParentClass() {
return parentClass;
}
@@ -857,7 +864,12 @@ public class ClassNode extends NotificationAttrNode
}
}
public IClassData getClsData() {
/**
* Low level class data access.
*
* @return null for classes generated by jadx
*/
public @Nullable IClassData getClsData() {
return clsData;
}
@@ -86,6 +86,11 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode, IField
return type;
}
@Override
public ClassNode getDeclaringClass() {
return parentClass;
}
public ClassNode getParentClass() {
return parentClass;
}
@@ -5,6 +5,9 @@ import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.info.AccessInfo;
public interface ICodeNode extends IDexNode, IAttributeNode, IUsageInfoNode, ICodeNodeRef {
ClassNode getDeclaringClass();
AccessInfo getAccessFlags();
void setAccessFlags(AccessInfo newAccessFlags);
@@ -95,6 +95,10 @@ public class InsnNode extends LineAttrNode {
return arguments;
}
public List<InsnArg> getArgList() {
return arguments;
}
public int getArgsCount() {
return arguments.size();
}
@@ -5,6 +5,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -324,6 +325,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return mthInfo.getAlias();
}
@Override
public ClassNode getDeclaringClass() {
return parentClass;
}
public ClassNode getParentClass() {
return parentClass;
}
@@ -402,6 +408,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return exitBlock.getPredecessors().contains(block);
}
public void resetLoops() {
this.loops = new ArrayList<>();
}
public void registerLoop(LoopInfo loop) {
if (loops.isEmpty()) {
loops = new ArrayList<>(5);
@@ -663,6 +673,13 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return insnsCount;
}
/**
* Returns method code with comments and annotations
*/
public String getCodeStr() {
return CodeUtils.extractMethodCode(this, getTopParentClass().getCode());
}
@Override
public boolean isVarArg() {
return accFlags.isVarArgs();
@@ -688,6 +705,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return javaNode;
}
@ApiStatus.Internal
public void setJavaNode(JavaMethod javaNode) {
this.javaNode = javaNode;
}
@@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.DecompilationMode;
import jadx.api.ICodeCache;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
@@ -48,6 +49,7 @@ import jadx.core.dex.visitors.typeinference.TypeCompare;
import jadx.core.dex.visitors.typeinference.TypeUpdate;
import jadx.core.export.GradleInfoStorage;
import jadx.core.utils.CacheStorage;
import jadx.core.utils.DebugChecks;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.PassMerge;
import jadx.core.utils.StringUtils;
@@ -64,10 +66,6 @@ public class RootNode {
private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);
private final JadxArgs args;
private final List<IDexTreeVisitor> preDecompilePasses;
private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>();
private final ProcessClass processClasses;
private final ErrorsCounter errorsCounter = new ErrorsCounter();
private final StringUtils stringUtils;
private final ConstStorage constValues;
@@ -78,6 +76,7 @@ public class RootNode {
private final TypeUtils typeUtils;
private final AttributeStorage attributes = new AttributeStorage();
private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>();
private final GradleInfoStorage gradleInfoStorage = new GradleInfoStorage();
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<>();
@@ -87,21 +86,24 @@ public class RootNode {
private final Map<String, PackageNode> pkgMap = new HashMap<>();
private final List<PackageNode> packages = new ArrayList<>();
private List<IDexTreeVisitor> preDecompilePasses;
private ProcessClass processClasses;
private ClspGraph clsp;
@Nullable
private String appPackage;
@Nullable
private ClassNode appResClass;
private @Nullable String appPackage;
private @Nullable ClassNode appResClass;
/**
* Optional decompiler reference
*/
private @Nullable JadxDecompiler decompiler;
private @Nullable ManifestAttributes manifestAttributes;
public RootNode(JadxArgs args) {
this.args = args;
this.preDecompilePasses = Jadx.getPreDecompilePassesList();
this.processClasses = new ProcessClass(args);
this.processClasses = new ProcessClass(Jadx.getPassesList(args));
this.stringUtils = new StringUtils(args);
this.constValues = new ConstStorage(args);
this.typeUpdate = new TypeUpdate(this);
@@ -211,18 +213,13 @@ public class RootNode {
if (parser != null) {
processResources(parser.getResStorage());
updateObfuscatedFiles(parser, resources);
updateManifestAttribMap(parser);
initManifestAttributes().updateAttributes(parser);
}
} catch (Exception e) {
LOG.error("Failed to parse 'resources.pb'/'.arsc' file", e);
}
}
private void updateManifestAttribMap(IResTableParser parser) {
ManifestAttributes manifestAttributes = ManifestAttributes.getInstance();
manifestAttributes.updateAttributes(parser);
}
private @Nullable ResourceFile getResourceFile(List<ResourceFile> resources) {
for (ResourceFile rf : resources) {
if (rf.getType() == ResourceType.ARSC) {
@@ -316,10 +313,21 @@ public class RootNode {
}
public void mergePasses(Map<JadxPassType, List<JadxPass>> customPasses) {
DecompilationMode mode = args.getDecompilationMode();
if (mode == DecompilationMode.FALLBACK || mode == DecompilationMode.SIMPLE) {
// for predefined modes ignore custom (and plugin) passes
return;
}
new PassMerge(preDecompilePasses)
.merge(customPasses.get(JadxPreparePass.TYPE), p -> new PreparePassWrapper((JadxPreparePass) p));
new PassMerge(processClasses.getPasses())
.merge(customPasses.get(JadxDecompilePass.TYPE), p -> new DecompilePassWrapper((JadxDecompilePass) p));
if (args.isRunDebugChecks()) {
preDecompilePasses = DebugChecks.insertPasses(preDecompilePasses);
processClasses = new ProcessClass(DebugChecks.insertPasses(processClasses.getPasses()));
}
}
public void runPreDecompileStage() {
@@ -715,4 +723,13 @@ public class RootNode {
public GradleInfoStorage getGradleInfoStorage() {
return gradleInfoStorage;
}
public synchronized ManifestAttributes initManifestAttributes() {
ManifestAttributes attrs = manifestAttributes;
if (attrs == null) {
attrs = new ManifestAttributes(args.getSecurity());
manifestAttributes = attrs;
}
return attrs;
}
}
@@ -169,6 +169,24 @@ public class SignatureParser {
throw new JadxRuntimeException("Can't parse type: " + debugString() + ", unexpected: " + ch);
}
public List<ArgType> consumeTypeList() {
List<ArgType> list = null;
while (true) {
ArgType type = consumeType();
if (type == null) {
break;
}
if (list == null) {
list = new ArrayList<>();
}
list.add(type);
}
if (list == null) {
return Collections.emptyList();
}
return list;
}
private ArgType consumeObjectType(boolean innerType) {
mark();
int ch;
@@ -51,7 +51,7 @@ public class MethodUtils {
public MethodNode resolveMethod(BaseInvokeNode invokeNode) {
IMethodDetails methodDetails = getMethodDetails(invokeNode);
if (methodDetails instanceof MethodNode) {
return ((MethodNode) methodDetails);
return (MethodNode) methodDetails;
}
return null;
}
@@ -71,7 +71,11 @@ public class CheckCode extends AbstractVisitor {
}
insnNode.getRegisterArgs(list);
for (RegisterArg arg : list) {
if (arg.getRegNum() >= regsCount) {
int regNum = arg.getRegNum();
if (regNum < 0) {
throw new JadxRuntimeException("Incorrect negative register number in instruction: " + insnNode);
}
if (regNum >= regsCount) {
throw new JadxRuntimeException("Incorrect register number in instruction: " + insnNode
+ ", expected to be less than " + regsCount);
}
@@ -32,6 +32,7 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.fixaccessmodifiers.FixAccessModifiers;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnRemover;
@@ -1,11 +1,18 @@
package jadx.core.dex.visitors;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
@@ -18,6 +25,7 @@ import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.exceptions.JadxRuntimeException;
@JadxVisitor(
name = "ConstructorVisitor",
@@ -34,9 +42,6 @@ public class ConstructorVisitor extends AbstractVisitor {
if (replaceInvoke(mth)) {
MoveInlineVisitor.moveInline(mth);
}
if (mth.contains(AFlag.RERUN_SSA_TRANSFORM)) {
SSATransform.rerun(mth);
}
}
private static boolean replaceInvoke(MethodNode mth) {
@@ -58,24 +63,32 @@ public class ConstructorVisitor extends AbstractVisitor {
private static boolean processInvoke(MethodNode mth, BlockNode block, int indexInBlock, InsnRemover remover) {
InvokeNode inv = (InvokeNode) block.getInstructions().get(indexInBlock);
if (!inv.getCallMth().isConstructor()) {
MethodInfo callMth = inv.getCallMth();
if (!callMth.isConstructor()) {
return false;
}
ConstructorInsn co = new ConstructorInsn(mth, inv);
ArgType instType = searchInstanceType(inv);
if (instType != null && !instType.equals(callMth.getDeclClass().getType())) {
ClassInfo instCls = ClassInfo.fromType(mth.root(), instType);
callMth = MethodInfo.fromDetails(mth.root(), instCls, callMth.getName(),
callMth.getArgumentsTypes(), callMth.getReturnType());
}
ConstructorInsn co = new ConstructorInsn(mth, inv, callMth);
if (canRemoveConstructor(mth, co)) {
remover.addAndUnbind(inv);
return false;
}
co.inheritMetadata(inv);
RegisterArg instanceArg = ((RegisterArg) inv.getArg(0));
RegisterArg instanceArg = (RegisterArg) inv.getArg(0);
instanceArg.getSVar().removeUse(instanceArg);
if (co.isNewInstance()) {
InsnNode assignInsn = instanceArg.getAssignInsn();
if (assignInsn != null) {
if (assignInsn.getType() == InsnType.CONSTRUCTOR) {
// arg already used in another constructor instruction
mth.add(AFlag.RERUN_SSA_TRANSFORM);
// insert new PHI insn to merge these branched constructors results
instanceArg = insertPhiInsn(mth, block, instanceArg, ((ConstructorInsn) assignInsn));
} else {
InsnNode newInstInsn = removeAssignChain(mth, assignInsn, remover, InsnType.NEW_INSTANCE);
if (newInstInsn != null) {
@@ -99,6 +112,56 @@ public class ConstructorVisitor extends AbstractVisitor {
return true;
}
private static @Nullable ArgType searchInstanceType(InvokeNode inv) {
InsnArg instanceArg = inv.getInstanceArg();
if (instanceArg == null || !instanceArg.isRegister()) {
return null;
}
InsnNode assignInsn = ((RegisterArg) instanceArg).getSVar().getAssignInsn();
if (assignInsn == null || assignInsn.getType() != InsnType.NEW_INSTANCE) {
return null;
}
return ((IndexInsnNode) assignInsn).getIndexAsType();
}
private static RegisterArg insertPhiInsn(MethodNode mth, BlockNode curBlock,
RegisterArg instArg, ConstructorInsn otherCtr) {
BlockNode otherBlock = BlockUtils.getBlockByInsn(mth, otherCtr);
if (otherBlock == null) {
throw new JadxRuntimeException("Block not found by insn: " + otherCtr);
}
BlockNode crossBlock = BlockUtils.getPathCross(mth, curBlock, otherBlock);
if (crossBlock == null) {
// no path cross => PHI insn not needed
// use new SSA var on usage from current path
RegisterArg newResArg = instArg.duplicateWithNewSSAVar(mth);
List<BlockNode> pathBlocks = BlockUtils.collectAllSuccessors(mth, curBlock, true);
for (RegisterArg useReg : instArg.getSVar().getUseList()) {
InsnNode parentInsn = useReg.getParentInsn();
if (parentInsn != null) {
BlockNode useBlock = BlockUtils.getBlockByInsn(mth, parentInsn, pathBlocks);
if (useBlock != null) {
parentInsn.replaceArg(useReg, newResArg.duplicate());
}
}
}
return newResArg;
}
RegisterArg newResArg = instArg.duplicateWithNewSSAVar(mth);
RegisterArg useArg = otherCtr.getResult();
RegisterArg otherResArg = useArg.duplicateWithNewSSAVar(mth);
PhiInsn phiInsn = SSATransform.addPhi(mth, crossBlock, useArg.getRegNum());
phiInsn.setResult(useArg.duplicate());
phiInsn.bindArg(newResArg.duplicate(), BlockUtils.getPrevBlockOnPath(mth, crossBlock, curBlock));
phiInsn.bindArg(otherResArg.duplicate(), BlockUtils.getPrevBlockOnPath(mth, crossBlock, otherBlock));
phiInsn.rebindArgs();
otherCtr.setResult(otherResArg.duplicate());
otherCtr.rebindArgs();
return newResArg;
}
private static boolean canRemoveConstructor(MethodNode mth, ConstructorInsn co) {
ClassNode parentClass = mth.getParentClass();
if (co.isSuper() && (co.getArgsCount() == 0 || parentClass.isEnum())) {
@@ -3,7 +3,6 @@ package jadx.core.dex.visitors;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.DebugChecks;
public class DepthTraversal {
@@ -24,9 +23,6 @@ public class DepthTraversal {
return;
}
visitor.visit(mth);
if (DebugChecks.checksEnabled) {
DebugChecks.runChecksAfterVisitor(mth, visitor);
}
} catch (StackOverflowError | Exception e) {
mth.addError(e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
}
@@ -57,6 +57,11 @@ public class DotGraphVisitor extends AbstractVisitor {
this.rawInsn = rawInsn;
}
@Override
public String getName() {
return "DotGraphVisitor";
}
@Override
public void visit(MethodNode mth) {
if (mth.isNoCode()) {
@@ -59,12 +59,17 @@ public class InlineMethods extends AbstractVisitor {
}
MethodNode callMth = (MethodNode) callMthDetails;
try {
// TODO: sort inner classes process order by dependencies!
MethodInlineAttr mia = MarkMethodsForInline.process(callMth);
if (mia == null) {
// method not yet loaded => will retry at codegen stage
callMth.getParentClass().reloadAtCodegenStage();
return;
// method is not yet loaded => force process
mth.addDebugComment("Class process forced to load method for inline: " + callMth);
mth.root().getProcessClasses().forceProcess(callMth.getParentClass());
// run check again
mia = MarkMethodsForInline.process(callMth);
if (mia == null) {
mth.addWarnComment("Failed to check method for inline after forced process" + callMth);
return;
}
}
if (mia.notNeeded()) {
return;
@@ -19,7 +19,9 @@ import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.fixaccessmodifiers.FixAccessModifiers;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
@@ -42,18 +44,22 @@ public class MarkMethodsForInline extends AbstractVisitor {
*/
@Nullable
public static MethodInlineAttr process(MethodNode mth) {
MethodInlineAttr mia = mth.get(AType.METHOD_INLINE);
if (mia != null) {
return mia;
}
if (mth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE)) {
if (mth.getBasicBlocks() == null) {
return null;
try {
MethodInlineAttr mia = mth.get(AType.METHOD_INLINE);
if (mia != null) {
return mia;
}
MethodInlineAttr inlined = inlineMth(mth);
if (inlined != null) {
return inlined;
if (mth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE)) {
if (mth.getBasicBlocks() == null) {
return null;
}
MethodInlineAttr inlined = inlineMth(mth);
if (inlined != null) {
return inlined;
}
}
} catch (Exception e) {
mth.addWarnComment("Method inline analysis failed", e);
}
return MethodInlineAttr.inlineNotNeeded(mth);
}
@@ -113,9 +119,12 @@ public class MarkMethodsForInline extends AbstractVisitor {
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
case INVOKE:
return !mthRegs.isEmpty()
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0))
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
if (!retInsn.getArg(0).isSameVar(firstInsn.getResult())) {
return false;
}
return ListUtils.orderedEquals(
mth.getArgRegs(), firstInsn.getArgList(),
(mthArg, insnArg) -> insnArg.isSameVar(mthArg));
default:
return false;
}
@@ -187,7 +187,7 @@ public class MethodInvokeVisitor extends AbstractVisitor {
arg.setType(castType);
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
} else if (InsnUtils.isWrapped(arg, InsnType.CHECK_CAST)) {
IndexInsnNode wrapInsn = ((IndexInsnNode) ((InsnWrapArg) arg).getWrapInsn());
IndexInsnNode wrapInsn = (IndexInsnNode) ((InsnWrapArg) arg).getWrapInsn();
wrapInsn.updateIndex(castType);
} else {
if (Consts.DEBUG_TYPE_INFERENCE) {
@@ -61,6 +61,11 @@ import jadx.core.utils.exceptions.JadxException;
)
public class PrepareForCodeGen extends AbstractVisitor {
@Override
public String getName() {
return "PrepareForCodeGen";
}
@Override
public boolean visit(ClassNode cls) throws JadxException {
if (cls.root().getArgs().isDebugInfo()) {
@@ -1,12 +1,17 @@
package jadx.core.dex.visitors;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr;
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr.CodeFeature;
import jadx.core.dex.instructions.FilledNewArrayNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
@@ -20,7 +25,6 @@ import jadx.core.dex.nodes.IFieldInfoRef;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnList;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.InsnUtils;
@@ -35,21 +39,20 @@ public class ReplaceNewArray extends AbstractVisitor {
@Override
public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode()) {
if (!CodeFeaturesAttr.contains(mth, CodeFeature.NEW_ARRAY)) {
return;
}
InsnRemover remover = new InsnRemover(mth);
int k = 0;
while (true) {
boolean changed = false;
InsnRemover remover = new InsnRemover(mth);
for (BlockNode block : mth.getBasicBlocks()) {
remover.setBlock(block);
List<InsnNode> instructions = block.getInstructions();
int size = instructions.size();
List<InsnNode> insnList = block.getInstructions();
int size = insnList.size();
for (int i = 0; i < size; i++) {
changed |= processInsn(mth, instructions, i, remover);
changed |= processInsn(mth, insnList, i, remover);
}
remover.perform();
remover.performForBlock(block);
}
if (changed) {
CodeShrinkVisitor.shrinkMethod(mth);
@@ -105,14 +108,17 @@ public class ReplaceNewArray extends AbstractVisitor {
}
// collect put instructions sorted by array index
SortedMap<Long, InsnNode> arrPuts = new TreeMap<>();
InsnNode firstNotAPutUsage = null;
for (RegisterArg registerArg : useList) {
InsnNode parentInsn = registerArg.getParentInsn();
if (parentInsn == null || parentInsn.getType() != InsnType.APUT) {
if (parentInsn == null
|| parentInsn.getType() != InsnType.APUT
|| !arrArg.sameRegAndSVar(parentInsn.getArg(0))) {
if (firstNotAPutUsage == null) {
firstNotAPutUsage = parentInsn;
}
continue;
}
if (!arrArg.sameRegAndSVar(parentInsn.getArg(0))) {
return false;
}
Object constVal = InsnUtils.getConstValueByArg(mth.root(), parentInsn.getArg(1));
if (!(constVal instanceof LiteralArg)) {
return false;
@@ -130,8 +136,7 @@ public class ReplaceNewArray extends AbstractVisitor {
if (arrPuts.size() < minLen) {
return false;
}
// expect all puts to be in same block
if (!new HashSet<>(instructions).containsAll(arrPuts.values())) {
if (!verifyPutInsns(arrArg, instructions, arrPuts)) {
return false;
}
@@ -156,12 +161,45 @@ public class ReplaceNewArray extends AbstractVisitor {
remover.addAndUnbind(put);
prevIndex = index;
}
// add missing trailing zeros
for (long i = prevIndex + 1; i < len; i++) {
filledArr.addArg(InsnArg.lit(0, elemType));
}
remover.addAndUnbind(newArrayInsn);
// place new insn at last array put or before first usage
InsnNode lastPut = arrPuts.get(arrPuts.lastKey());
int replaceIndex = InsnList.getIndex(instructions, lastPut);
instructions.set(replaceIndex, filledArr);
BlockUtils.replaceInsn(mth, lastPut, filledArr);
int newInsnPos = InsnList.getIndex(instructions, lastPut);
if (firstNotAPutUsage != null) {
int idx = InsnList.getIndex(instructions, firstNotAPutUsage);
if (idx != -1) {
// TODO: check that all args already assigned
newInsnPos = Math.min(idx, newInsnPos);
}
}
instructions.add(newInsnPos, filledArr);
return true;
}
private static boolean verifyPutInsns(RegisterArg arrReg, List<InsnNode> insnList, SortedMap<Long, InsnNode> arrPuts) {
List<InsnNode> puts = new ArrayList<>(arrPuts.values());
int putsCount = puts.size();
// expect all puts to be in the same block
if (insnList.size() < putsCount) {
return false;
}
Set<InsnNode> insnSet = Collections.newSetFromMap(new IdentityHashMap<>());
insnSet.addAll(insnList);
if (!insnSet.containsAll(puts)) {
return false;
}
// array arg shouldn't be used in puts insns
for (InsnNode put : puts) {
InsnArg putArg = put.getArg(2);
if (putArg.isUseVar(arrReg)) {
return false;
}
}
return true;
}
@@ -165,7 +165,7 @@ public class ShadowFieldVisitor extends AbstractVisitor {
switch (insn.getType()) {
case IPUT:
case IGET:
return ((FieldInfo) ((IndexInsnNode) insn).getIndex());
return (FieldInfo) ((IndexInsnNode) insn).getIndex();
default:
return null;
}

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