Compare commits
136 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c4c3d42d16 | |||
| 58c36de8c2 | |||
| b4ca566a19 | |||
| e644bad758 | |||
| dd86abcc5e | |||
| f0513a1a55 | |||
| be6cb573b1 | |||
| 5d064d3e50 | |||
| 15b6309e2c | |||
| 417bb7a7e9 | |||
| 4c74e8e300 | |||
| 57238de6ff | |||
| 313c4a121a | |||
| 39912398fc | |||
| 7544d1a113 | |||
| cfbe5ab672 | |||
| 2661b91a6f | |||
| 61578e8793 | |||
| cc6a893402 | |||
| 4d8a5d6671 | |||
| 982307b1ac | |||
| 37054dc84e | |||
| 343e2c531a | |||
| 6fa5d247f0 | |||
| 688dea0c50 | |||
| c0815b12bc | |||
| 233f8692b2 | |||
| e5be41b9cc | |||
| 3788e4ef3a | |||
| 32855f4511 | |||
| 3d5e225274 | |||
| 8a34d973ff | |||
| a43b3282ef | |||
| 249801880c | |||
| 742d30d770 | |||
| 267413332a | |||
| f9c94f27f1 | |||
| d2131d9640 | |||
| 548821c4df | |||
| 958ab245ae | |||
| 8f3cc3e8c1 | |||
| b872ffd1b9 | |||
| c5263f92fe | |||
| 1475e887c8 | |||
| c21cabcba7 | |||
| 063af8cd62 | |||
| 2d10537050 | |||
| 3814951408 | |||
| 964bd62d35 | |||
| 3ed2df828f | |||
| b26abdc851 | |||
| 90185fd947 | |||
| 681f8a98b5 | |||
| 0b225238fb | |||
| a7649dda7a | |||
| b5e3dcf70f | |||
| 7abbc81886 | |||
| 9c30aeacdb | |||
| 8f27de4d0e | |||
| 02b69d2d29 | |||
| e6fde48b69 | |||
| 109dea0857 | |||
| 1d34328dd3 | |||
| ef4f1d3ed4 | |||
| 23696d3971 | |||
| efa2f5d172 | |||
| 699ceb197e | |||
| 5c83c22501 | |||
| 7bb5c0a859 | |||
| 603863403f | |||
| 889a945cf1 | |||
| fd80e03809 | |||
| b5807082d9 | |||
| 5d1f0b8cae | |||
| 3f9aa34057 | |||
| e2c860f260 | |||
| 0938351d97 | |||
| 937dd20794 | |||
| ea5e87560a | |||
| 5fbbf2150e | |||
| 0e11bffe82 | |||
| ba9af5c288 | |||
| cca706c94f | |||
| 2df69bbfb4 | |||
| f5307636ef | |||
| 9a39b70a46 | |||
| e63808bc4b | |||
| 847225a6a9 | |||
| eee354a3ab | |||
| 1cc00a54f3 | |||
| ffdad1b652 | |||
| 9a8ec76989 | |||
| 0be5b2cea9 | |||
| c94201be4a | |||
| 1051dacb1e | |||
| a2bfe9bbe8 | |||
| 8c6ec3bccc | |||
| 015876b790 | |||
| 280f3870a9 | |||
| e9d770ae9e | |||
| 5f1bd1d9ba | |||
| 60fb458024 | |||
| 1b08779536 | |||
| c37e39a819 | |||
| 2d58fbd4b1 | |||
| ffbf800404 | |||
| 500aa8a68d | |||
| 58e8268126 | |||
| f9da6e00ed | |||
| 821cc668c7 | |||
| 287ba49008 | |||
| 115e563a2b | |||
| 1669200e62 | |||
| 6ab224ea0d | |||
| 61855a7ea1 | |||
| bda3119e86 | |||
| b26abed686 | |||
| c179beee95 | |||
| 04a454094b | |||
| 33bcbfec41 | |||
| ec645d80b1 | |||
| a8d889de3d | |||
| ec0bf701c8 | |||
| ad4dd116be | |||
| ef79f266c8 | |||
| 366225f9be | |||
| 730db0d24f | |||
| 05fb77e9bd | |||
| bbabfa0354 | |||
| 96bd9f0f17 | |||
| fd5b397b40 | |||
| f5e3a261b4 | |||
| 52a884608a | |||
| 74ddfde950 | |||
| 9aacb4f312 | |||
| 82e2104f3c |
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -28,6 +28,7 @@ jadx-output/
|
||||
*-tmp/
|
||||
**/tmp/
|
||||
*.jobf
|
||||
*.jadx
|
||||
|
||||
*.class
|
||||
*.dump
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -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
|
||||
|
||||
@@ -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
@@ -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 ##########################################################################
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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/*")
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user