Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5640f3b931 | |||
| 02bc27e887 | |||
| 794e5adb7f | |||
| a81cec7701 |
@@ -16,7 +16,7 @@ jobs:
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
java-version: 11
|
||||
|
||||
- name: Set jadx version
|
||||
run: |
|
||||
@@ -24,13 +24,10 @@ jobs:
|
||||
JADX_VERSION="r${JADX_REV}.${GITHUB_SHA:0:7}"
|
||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Build
|
||||
run: ./gradlew dist distWin
|
||||
env:
|
||||
JADX_BUILD_JAVA_VERSION: 11
|
||||
- name: Build with Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
arguments: dist copyExe
|
||||
|
||||
- name: Save bundle artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -42,12 +39,11 @@ jobs:
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
- name: Save Windows bundle artifact
|
||||
- name: Save exe artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-no-jre-win', env.JADX_VERSION) }}
|
||||
# Upload unpacked files for now
|
||||
path: jadx-gui/build/jadx-gui-win/*
|
||||
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
|
||||
path: build/*.exe
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
@@ -74,17 +70,15 @@ jobs:
|
||||
JADX_VERSION="r${JADX_REV}.${GITHUB_SHA:0:7}"
|
||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Build with Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
arguments: dist -PbundleJRE=true
|
||||
|
||||
- name: Build
|
||||
run: ./gradlew dist -PbundleJRE=true
|
||||
|
||||
- name: Save Windows with JRE bundle artifact
|
||||
- name: Save exe bundle artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
||||
# Upload unpacked files for now
|
||||
path: jadx-gui/build/jadx-gui-with-jre-win/*
|
||||
path: jadx-gui/build/*-with-jre-win/*
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
@@ -20,13 +20,9 @@ jobs:
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
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
|
||||
java-version: 11
|
||||
|
||||
- name: Build with Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
arguments: build dist copyExe
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
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
|
||||
@@ -1,92 +0,0 @@
|
||||
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,7 +28,6 @@ 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 distWin
|
||||
script: ./gradlew clean build dist copyExe
|
||||
|
||||
java-17:
|
||||
stage: test
|
||||
image: eclipse-temurin:17
|
||||
script: ./gradlew clean build dist distWin
|
||||
script: ./gradlew clean build dist copyExe
|
||||
|
||||
java-21:
|
||||
stage: test
|
||||
image: eclipse-temurin:21
|
||||
script: ./gradlew clean build dist distWin
|
||||
script: ./gradlew clean build dist copyExe
|
||||
|
||||
@@ -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 -Dsun.java2d.noddraw=true -Dsun.java2d.d3d=false -Dsun.java2d.ddforcevram=true -Dsun.java2d.ddblit=false -Dswing.useflipBufferStrategy=True"/>
|
||||
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"/>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true"/>
|
||||
</method>
|
||||
|
||||
+23
-10
@@ -3,7 +3,7 @@
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
@@ -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,12 +45,25 @@ threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
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.
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at skylot@gmail.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
|
||||
@@ -91,101 +91,95 @@ 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-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
|
||||
-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.* android.os.* androidx.core.os.* androidx.annotation.*
|
||||
--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
|
||||
|
||||
Plugin options (-P<name>=<value>):
|
||||
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
|
||||
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
|
||||
|
||||
Environment variables:
|
||||
JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files
|
||||
|
||||
+3
-4
@@ -2,7 +2,6 @@
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
+24
-29
@@ -15,18 +15,6 @@ 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")
|
||||
@@ -115,34 +103,40 @@ val pack by tasks.registering(Zip::class) {
|
||||
destinationDirectory.set(layout.buildDirectory)
|
||||
}
|
||||
|
||||
val distWin by tasks.registering(Zip::class) {
|
||||
val copyExe by tasks.registering(Copy::class) {
|
||||
group = "jadx"
|
||||
description = "Build Windows bundle"
|
||||
description = "Copy exe to build dir"
|
||||
|
||||
val guiTask = tasks.getByPath("jadx-gui:copyDistWin")
|
||||
dependsOn(guiTask)
|
||||
from(guiTask.outputs)
|
||||
// next task dependencies not needed, but gradle throws warning because of same output dir
|
||||
mustRunAfter("jar")
|
||||
mustRunAfter(pack)
|
||||
|
||||
destinationDirectory.set(layout.buildDirectory.dir("distWin"))
|
||||
archiveFileName.set("jadx-gui-$jadxVersion-win.zip")
|
||||
from(tasks.getByPath("jadx-gui:createExe"))
|
||||
include("*.exe")
|
||||
into(layout.buildDirectory)
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
val distWinWithJre by tasks.registering(Zip::class) {
|
||||
description = "Build Windows with JRE bundle"
|
||||
val distWinBundle by tasks.registering(Copy::class) {
|
||||
group = "jadx"
|
||||
description = "Copy bundle to build dir"
|
||||
|
||||
val guiTask = tasks.getByPath(":jadx-gui:copyDistWinWithJre")
|
||||
dependsOn(guiTask)
|
||||
from(guiTask.outputs)
|
||||
dependsOn(tasks.getByPath(":jadx-gui:distWinWithJre"))
|
||||
|
||||
destinationDirectory.set(layout.buildDirectory.dir("distWinWithJre"))
|
||||
archiveFileName.set("jadx-gui-$jadxVersion-with-jre-win.zip")
|
||||
// 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)
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
val dist by tasks.registering {
|
||||
group = "jadx"
|
||||
description = "Build jadx distribution zip bundles"
|
||||
description = "Build jadx distribution zip"
|
||||
|
||||
dependsOn(pack)
|
||||
|
||||
@@ -150,14 +144,15 @@ val dist by tasks.registering {
|
||||
if (os.isWindows) {
|
||||
if (project.hasProperty("bundleJRE")) {
|
||||
println("Build win bundle with JRE")
|
||||
dependsOn(distWinWithJre)
|
||||
dependsOn(distWinBundle)
|
||||
} else {
|
||||
dependsOn(distWin)
|
||||
dependsOn(copyExe)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val cleanBuildDir by tasks.registering(Delete::class) {
|
||||
group = "jadx"
|
||||
delete(layout.buildDirectory)
|
||||
}
|
||||
tasks.getByName("clean").dependsOn(cleanBuildDir)
|
||||
|
||||
@@ -3,9 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21")
|
||||
|
||||
implementation("org.openrewrite:plugin:6.19.1")
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23")
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
@@ -3,27 +3,26 @@ 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.16")
|
||||
compileOnly("org.jetbrains:annotations:26.0.1")
|
||||
implementation("org.slf4j:slf4j-api:2.0.13")
|
||||
compileOnly("org.jetbrains:annotations:24.1.0")
|
||||
|
||||
testImplementation("ch.qos.logback:logback-classic:1.5.12")
|
||||
testImplementation("org.assertj:assertj-core:3.26.3")
|
||||
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("org.junit.jupiter:junit-jupiter:5.11.3")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
|
||||
testCompileOnly("org.jetbrains:annotations:26.0.1")
|
||||
testCompileOnly("org.jetbrains:annotations:24.1.0")
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -33,11 +32,6 @@ 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(project.description ?: "Dex to Java decompiler")
|
||||
description.set("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(project.properties["libEmail"].toString())
|
||||
email.set("skylot@gmail.com")
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
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,10 +120,7 @@
|
||||
|
||||
<module name="SuppressWarningsHolder"/>
|
||||
|
||||
<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="IllegalType"/>
|
||||
<module name="IllegalImport">
|
||||
<property name="illegalClasses" value="jadx.core.utils.DebugUtils"/>
|
||||
</module>
|
||||
@@ -131,8 +128,7 @@
|
||||
<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.codearea;
|
||||
package jadx.gui.ui.codeearea;
|
||||
|
||||
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,6 +173,7 @@ import org.fife.ui.rsyntaxtextarea.*;
|
||||
zzAtEOF = false;
|
||||
}
|
||||
|
||||
|
||||
%}
|
||||
|
||||
Letter = [A-Za-z]
|
||||
@@ -677,3 +678,4 @@ 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,9 +102,6 @@
|
||||
zzLexicalState = newState;
|
||||
}
|
||||
|
||||
public final int yystate() {
|
||||
return zzLexicalState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text matched by the current regular expression.
|
||||
@@ -115,12 +112,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
|
||||
@@ -141,8 +138,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.).
|
||||
@@ -162,7 +159,7 @@
|
||||
}
|
||||
|
||||
--- throws clause
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -209,12 +206,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;
|
||||
@@ -229,11 +226,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=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -57,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -86,8 +84,7 @@ 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 -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
Vendored
-2
@@ -13,8 +13,6 @@
|
||||
@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,16 +1,14 @@
|
||||
plugins {
|
||||
id("jadx-java")
|
||||
id("jadx-library")
|
||||
id("application")
|
||||
|
||||
// use shadow only for application scripts, jar will be copied from jadx-gui
|
||||
id("com.gradleup.shadow") version "8.3.5"
|
||||
id("com.github.johnrengelman.shadow") version "8.1.1"
|
||||
}
|
||||
|
||||
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"))
|
||||
@@ -22,8 +20,8 @@ dependencies {
|
||||
runtimeOnly(project(":jadx-plugins:jadx-xapk-input"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-aab-input"))
|
||||
|
||||
implementation("org.jcommander:jcommander:2.0")
|
||||
implementation("ch.qos.logback:logback-classic:1.5.12")
|
||||
implementation("org.jcommander:jcommander:1.83")
|
||||
implementation("ch.qos.logback:logback-classic:1.5.6")
|
||||
}
|
||||
|
||||
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.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -13,7 +13,6 @@ 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;
|
||||
@@ -134,16 +133,14 @@ public class JCommanderWrapper<T> {
|
||||
out.println("options:");
|
||||
|
||||
List<ParameterDescription> params = jc.getParameters();
|
||||
Map<String, ParameterDescription> paramsMap = new HashMap<>(params.size());
|
||||
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
|
||||
int maxNamesLen = 0;
|
||||
for (ParameterDescription p : params) {
|
||||
paramsMap.put(p.getParameterized().getName(), p);
|
||||
int len = p.getNames().length();
|
||||
String valueDesc = getValueDesc(p);
|
||||
if (valueDesc != null) {
|
||||
len += 1 + valueDesc.length();
|
||||
if (len > maxNamesLen) {
|
||||
maxNamesLen = len;
|
||||
}
|
||||
maxNamesLen = Math.max(maxNamesLen, len);
|
||||
}
|
||||
maxNamesLen += 3;
|
||||
|
||||
@@ -156,12 +153,8 @@ public class JCommanderWrapper<T> {
|
||||
}
|
||||
StringBuilder opt = new StringBuilder();
|
||||
opt.append(" ").append(p.getNames());
|
||||
String valueDesc = getValueDesc(p);
|
||||
if (valueDesc != null) {
|
||||
opt.append(' ').append(valueDesc);
|
||||
}
|
||||
addSpaces(opt, maxNamesLen - opt.length());
|
||||
String description = p.getDescription();
|
||||
addSpaces(opt, maxNamesLen - opt.length());
|
||||
if (description.contains("\n")) {
|
||||
String[] lines = description.split("\n");
|
||||
opt.append("- ").append(lines[0]);
|
||||
@@ -184,11 +177,6 @@ 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
|
||||
*/
|
||||
@@ -240,7 +228,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)) {
|
||||
if (appendPlugin(context.getPluginInfo(), context.getOptions(), sb, maxNamesLen, k)) {
|
||||
k++;
|
||||
}
|
||||
}
|
||||
@@ -252,12 +240,12 @@ public class JCommanderWrapper<T> {
|
||||
return "\nPlugin options (-P<name>=<value>):" + sb;
|
||||
}
|
||||
|
||||
private boolean appendPlugin(JadxPluginInfo pluginInfo, JadxPluginOptions options, StringBuilder out, int maxNamesLen) {
|
||||
private boolean appendPlugin(JadxPluginInfo pluginInfo, JadxPluginOptions options, StringBuilder out, int maxNamesLen, int k) {
|
||||
List<OptionDescription> descs = options.getOptionsDescriptions();
|
||||
if (descs.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
out.append("\n ");
|
||||
out.append("\n ").append(k).append(") ");
|
||||
out.append(pluginInfo.getPluginId()).append(": ").append(pluginInfo.getDescription());
|
||||
for (OptionDescription desc : descs) {
|
||||
StringBuilder opt = new StringBuilder();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -10,40 +8,36 @@ 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 = 1;
|
||||
int result = 0;
|
||||
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) {
|
||||
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;
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
if (jadxArgs.processArgs(args)) {
|
||||
return processAndSave(jadxArgs);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int processAndSave(JadxCLIArgs cliArgs) {
|
||||
@@ -52,9 +46,7 @@ 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)) {
|
||||
@@ -68,11 +60,11 @@ public class JadxCLI {
|
||||
if (errorsCount != 0) {
|
||||
jadx.printErrorsReport();
|
||||
LOG.error("finished with errors, count: {}", errorsCount);
|
||||
return 1;
|
||||
} else {
|
||||
LOG.info("done");
|
||||
}
|
||||
LOG.info("done");
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static void initCodeWriterProvider(JadxArgs jadxArgs) {
|
||||
@@ -87,22 +79,6 @@ 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,7 +27,6 @@ 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;
|
||||
@@ -109,9 +108,6 @@ 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;
|
||||
|
||||
@@ -170,15 +166,8 @@ public class JadxCLIArgs {
|
||||
)
|
||||
protected GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
|
||||
|
||||
@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-use-sourcename" }, description = "use source file name as class name alias")
|
||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
||||
|
||||
@Parameter(
|
||||
names = { "--deobf-res-name-source" },
|
||||
@@ -190,16 +179,6 @@ 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",
|
||||
@@ -264,9 +243,6 @@ 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;
|
||||
|
||||
@@ -351,7 +327,7 @@ public class JadxCLIArgs {
|
||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||
args.setDeobfuscationWhitelist(Arrays.asList(deobfuscationWhitelistStr.split(" ")));
|
||||
args.setUseSourceNameAsClassNameAlias(getUseSourceNameAsClassNameAlias());
|
||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
|
||||
args.setResourceNameSource(resourceNameSource);
|
||||
args.setEscapeUnicode(escapeUnicode);
|
||||
@@ -366,14 +342,12 @@ 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;
|
||||
}
|
||||
|
||||
@@ -461,10 +435,6 @@ public class JadxCLIArgs {
|
||||
return extractFinally;
|
||||
}
|
||||
|
||||
public boolean isRestoreSwitchOverString() {
|
||||
return restoreSwitchOverString;
|
||||
}
|
||||
|
||||
public Path getUserRenamesMappingsPath() {
|
||||
return userRenamesMappingsPath;
|
||||
}
|
||||
@@ -497,23 +467,8 @@ 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 getUseSourceNameAsClassNameAlias().toBoolean();
|
||||
return deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
public ResourceNameSource getResourceNameSource() {
|
||||
@@ -584,10 +539,6 @@ public class JadxCLIArgs {
|
||||
return pluginOptions;
|
||||
}
|
||||
|
||||
public String getDisablePlugins() {
|
||||
return disablePlugins;
|
||||
}
|
||||
|
||||
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
||||
private final String paramName;
|
||||
|
||||
@@ -641,12 +592,6 @@ 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 LinkedHashMap<>();
|
||||
private static final Map<String, ICommand> COMMANDS_MAP = new TreeMap<>();
|
||||
|
||||
static {
|
||||
JadxCLICommands.register(new CommandPlugins());
|
||||
|
||||
@@ -12,7 +12,6 @@ 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;
|
||||
@@ -48,7 +47,7 @@ public class ConvertToClsSet {
|
||||
// disable not needed passes executed at prepare stage
|
||||
jadxArgs.setDeobfuscationOn(false);
|
||||
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
|
||||
jadxArgs.setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias.NEVER);
|
||||
jadxArgs.setUseSourceNameAsClassAlias(false);
|
||||
jadxArgs.setMoveInnerClasses(false);
|
||||
jadxArgs.setInlineAnonymousClasses(false);
|
||||
jadxArgs.setInlineMethods(false);
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
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;
|
||||
@@ -21,40 +15,24 @@ 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", defaultValueDescription = "<locationId>")
|
||||
@Parameter(names = { "-i", "--install" }, description = "install plugin with locationId")
|
||||
protected String install;
|
||||
|
||||
@Parameter(names = { "-j", "--install-jar" }, description = "install plugin from jar file", defaultValueDescription = "<path-to.jar>")
|
||||
@Parameter(names = { "-j", "--install-jar" }, description = "install plugin from jar file")
|
||||
protected String installJar;
|
||||
|
||||
@Parameter(names = { "-l", "--list" }, description = "list installed plugins")
|
||||
protected boolean list;
|
||||
|
||||
@Parameter(names = { "-a", "--available" }, description = "list available plugins from jadx-plugins-list (aka marketplace)")
|
||||
@Parameter(names = { "-a", "--available" }, description = "list available plugins")
|
||||
protected boolean available;
|
||||
|
||||
@Parameter(names = { "-u", "--update" }, description = "update installed plugins")
|
||||
protected boolean update;
|
||||
|
||||
@Parameter(names = { "--uninstall" }, description = "uninstall plugin with pluginId", defaultValueDescription = "<pluginId>")
|
||||
@Parameter(names = { "--uninstall" }, description = "uninstall plugin with 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;
|
||||
|
||||
@@ -63,33 +41,21 @@ 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();
|
||||
@@ -101,99 +67,27 @@ public class CommandPlugins implements ICommand {
|
||||
System.out.println(" " + update.getPluginId() + ": " + update.getOldVersion() + " -> " + update.getNewVersion());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (list) {
|
||||
printPlugins(JadxPluginsTools.getInstance().getInstalled());
|
||||
return;
|
||||
}
|
||||
if (listAll) {
|
||||
printAllPlugins();
|
||||
return;
|
||||
}
|
||||
if (listVersions != null) {
|
||||
printVersions(listVersions, 10);
|
||||
return;
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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(" - " + plugin.getName() + ": " + plugin.getDescription()
|
||||
System.out.println(" " + (i++) + ") "
|
||||
+ 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
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,6 +12,7 @@ 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;
|
||||
@@ -19,11 +20,8 @@ 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
|
||||
*/
|
||||
@@ -32,7 +30,7 @@ public class ConvertArscFile {
|
||||
private static int rewritesCount;
|
||||
|
||||
public static void usage() {
|
||||
LOG.info("<res-map file> <input .arsc/android.jar files or dir>");
|
||||
LOG.info("<res-map file> <input .arsc files>");
|
||||
LOG.info("");
|
||||
LOG.info("Note: If res-map already exists - it will be merged and updated");
|
||||
}
|
||||
@@ -44,7 +42,6 @@ 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);
|
||||
@@ -55,7 +52,8 @@ public class ConvertArscFile {
|
||||
|
||||
RootNode root = new RootNode(new JadxArgs()); // not really needed
|
||||
rewritesCount = 0;
|
||||
for (Path resFile : inputResFiles) {
|
||||
for (Path resFile : inputPaths) {
|
||||
LOG.info("Processing {}", resFile);
|
||||
ResTableBinaryParser resTableParser = new ResTableBinaryParser(root, true);
|
||||
if (resFile.getFileName().toString().endsWith(".jar")) {
|
||||
// Load resources.arsc from android.jar
|
||||
@@ -86,16 +84,6 @@ 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,12 +3,14 @@ package jadx.cli;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static jadx.core.utils.Utils.newConstStringMap;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class JadxCLIArgsTest {
|
||||
|
||||
@@ -16,38 +18,38 @@ public class JadxCLIArgsTest {
|
||||
|
||||
@Test
|
||||
public void testInvertedBooleanOption() {
|
||||
assertThat(parse("--no-replace-consts").isReplaceConsts()).isFalse();
|
||||
assertThat(parse("").isReplaceConsts()).isTrue();
|
||||
assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false));
|
||||
assertThat(parse("").isReplaceConsts(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapeUnicodeOption() {
|
||||
assertThat(parse("--escape-unicode").isEscapeUnicode()).isTrue();
|
||||
assertThat(parse("").isEscapeUnicode()).isFalse();
|
||||
assertThat(parse("--escape-unicode").isEscapeUnicode(), is(true));
|
||||
assertThat(parse("").isEscapeUnicode(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSrcOption() {
|
||||
assertThat(parse("--no-src").isSkipSources()).isTrue();
|
||||
assertThat(parse("-s").isSkipSources()).isTrue();
|
||||
assertThat(parse("").isSkipSources()).isFalse();
|
||||
assertThat(parse("--no-src").isSkipSources(), is(true));
|
||||
assertThat(parse("-s").isSkipSources(), is(true));
|
||||
assertThat(parse("").isSkipSources(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionsOverride() {
|
||||
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports()).isFalse();
|
||||
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo()).isFalse();
|
||||
assertThat(override(new JadxCLIArgs(), "").isUseImports()).isTrue();
|
||||
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));
|
||||
|
||||
JadxCLIArgs args = new JadxCLIArgs();
|
||||
args.useImports = false;
|
||||
assertThat(override(args, "--no-imports").isUseImports()).isFalse();
|
||||
assertThat(override(args, "--no-imports").isUseImports(), is(false));
|
||||
args.debugInfo = false;
|
||||
assertThat(override(args, "--no-debug-info").isDebugInfo()).isFalse();
|
||||
assertThat(override(args, "--no-debug-info").isDebugInfo(), is(false));
|
||||
|
||||
args = new JadxCLIArgs();
|
||||
args.useImports = false;
|
||||
assertThat(override(args, "").isUseImports()).isFalse();
|
||||
assertThat(override(args, "").isUseImports(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -81,7 +83,7 @@ public class JadxCLIArgsTest {
|
||||
JadxCLIArgs args = new JadxCLIArgs();
|
||||
args.pluginOptions = baseMap;
|
||||
Map<String, String> resultMap = override(args, providedArgs).getPluginOptions();
|
||||
assertThat(resultMap).isEqualTo(expectedMap);
|
||||
assertThat(resultMap, Matchers.equalTo(expectedMap));
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(String... args) {
|
||||
@@ -89,15 +91,15 @@ public class JadxCLIArgsTest {
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(JadxCLIArgs jadxArgs, String... args) {
|
||||
return check(jadxArgs, jadxArgs.processArgs(args));
|
||||
boolean res = jadxArgs.processArgs(args);
|
||||
assertThat(res, is(true));
|
||||
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
|
||||
return jadxArgs;
|
||||
}
|
||||
|
||||
private JadxCLIArgs override(JadxCLIArgs jadxArgs, String... args) {
|
||||
return check(jadxArgs, jadxArgs.overrideProvided(args));
|
||||
}
|
||||
|
||||
private static JadxCLIArgs check(JadxCLIArgs jadxArgs, boolean res) {
|
||||
assertThat(res).isTrue();
|
||||
boolean res = jadxArgs.overrideProvided(args);
|
||||
assertThat(res, is(true));
|
||||
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
|
||||
return jadxArgs;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ import jadx.api.JadxArgs.RenameEnum;
|
||||
import jadx.cli.JadxCLIArgs.RenameConverter;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class RenameConverterTest {
|
||||
|
||||
@@ -24,16 +25,16 @@ public class RenameConverterTest {
|
||||
@Test
|
||||
public void all() {
|
||||
Set<RenameEnum> set = converter.convert("all");
|
||||
assertThat(set).hasSize(3);
|
||||
assertThat(set).contains(RenameEnum.CASE);
|
||||
assertThat(set).contains(RenameEnum.VALID);
|
||||
assertThat(set).contains(RenameEnum.PRINTABLE);
|
||||
assertEquals(3, set.size());
|
||||
assertTrue(set.contains(RenameEnum.CASE));
|
||||
assertTrue(set.contains(RenameEnum.VALID));
|
||||
assertTrue(set.contains(RenameEnum.PRINTABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void none() {
|
||||
Set<RenameEnum> set = converter.convert("none");
|
||||
assertThat(set).isEmpty();
|
||||
assertTrue(set.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -42,6 +43,7 @@ public class RenameConverterTest {
|
||||
() -> converter.convert("wrong"),
|
||||
"Expected convert() to throw, but it didn't");
|
||||
|
||||
assertThat(thrown.getMessage()).isEqualTo("'wrong' is unknown for parameter someParam, possible values are case, valid, printable");
|
||||
assertEquals("'wrong' is unknown for parameter someParam, possible values are case, valid, printable",
|
||||
thrown.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,111 +12,71 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
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("samples/hello.dex");
|
||||
decompile("dex", "samples/hello.dex");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmaliInput() throws Exception {
|
||||
decompile("samples/HelloWorld.smali");
|
||||
decompile("smali", "samples/HelloWorld.smali");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassInput() throws Exception {
|
||||
decompile("samples/HelloWorld.class");
|
||||
decompile("class", "samples/HelloWorld.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleInput() throws Exception {
|
||||
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);
|
||||
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceOnly() throws Exception {
|
||||
int result = JadxCLI.execute(buildArgs(List.of(), "samples/resources-only.apk"));
|
||||
assertThat(result).isEqualTo(0);
|
||||
List<Path> files = collectFilesInDir(testDir,
|
||||
path -> path.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"));
|
||||
assertThat(files).isNotEmpty();
|
||||
decode("resourceOnly", "samples/resources-only.apk");
|
||||
}
|
||||
|
||||
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);
|
||||
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(testDir.toAbsolutePath().toString());
|
||||
args.add(tempDir.toAbsolutePath().toString());
|
||||
|
||||
URL resource = getClass().getClassLoader().getResource(apkSample);
|
||||
assertThat(resource).isNotNull();
|
||||
String sampleFile = resource.toURI().getRawPath();
|
||||
args.add(sampleFile);
|
||||
|
||||
int result = JadxCLI.execute(args.toArray(new String[0]));
|
||||
assertThat(result).isEqualTo(0);
|
||||
List<Path> files = Files.find(
|
||||
tempDir,
|
||||
3,
|
||||
(file, attr) -> file.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"))
|
||||
.collect(Collectors.toList());
|
||||
assertThat(files.isEmpty()).isFalse();
|
||||
}
|
||||
|
||||
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
|
||||
List<String> args = new ArrayList<>();
|
||||
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
||||
args.add("-v");
|
||||
args.add("-d");
|
||||
args.add(tempDir.toAbsolutePath().toString());
|
||||
|
||||
for (String inputSample : inputSamples) {
|
||||
URL resource = getClass().getClassLoader().getResource(inputSample);
|
||||
@@ -124,13 +84,21 @@ public class TestInput {
|
||||
String sampleFile = resource.toURI().getRawPath();
|
||||
args.add(sampleFile);
|
||||
}
|
||||
return args.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private void printFiles(List<Path> files) {
|
||||
LOG.info("Output files (count: {}):", files.size());
|
||||
for (Path file : files) {
|
||||
LOG.info(" {}", testDir.relativize(file));
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,14 +107,6 @@ 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
|
||||
@@ -155,4 +115,9 @@ public class TestInput {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void cleanup() {
|
||||
FileUtils.clearTempRootDir();
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -3,7 +3,6 @@ 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;
|
||||
|
||||
@@ -36,20 +35,20 @@ public class JadxCommonFiles {
|
||||
|
||||
public void init() {
|
||||
try {
|
||||
configDir = loadEnvDir("JADX_CONFIG_DIR", pd -> pd.configDir);
|
||||
cacheDir = loadEnvDir("JADX_CACHE_DIR", pd -> pd.cacheDir);
|
||||
configDir = loadEnvDir("JADX_CONFIG_DIR");
|
||||
cacheDir = loadEnvDir("JADX_CACHE_DIR");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to init common directories", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Path loadEnvDir(String envVar, Function<ProjectDirectories, String> dirFunc) throws IOException {
|
||||
private Path loadEnvDir(String envVar) throws IOException {
|
||||
String envDir = JadxCommonEnv.get(envVar, null);
|
||||
String dirStr;
|
||||
if (envDir != null) {
|
||||
dirStr = envDir;
|
||||
} else {
|
||||
dirStr = dirFunc.apply(loadDirs());
|
||||
dirStr = loadDirs().configDir;
|
||||
}
|
||||
Path path = Path.of(dirStr).toAbsolutePath();
|
||||
Files.createDirectories(path);
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
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.11.0")
|
||||
implementation("com.google.code.gson:gson:2.10.1")
|
||||
|
||||
testImplementation("org.apache.commons:commons-lang3:3.17.0")
|
||||
testImplementation("org.apache.commons:commons-lang3:3.14.0")
|
||||
|
||||
testImplementation(project(":jadx-plugins:jadx-dex-input"))
|
||||
testRuntimeOnly(project(":jadx-plugins:jadx-smali-input"))
|
||||
@@ -24,31 +24,6 @@ dependencies {
|
||||
testImplementation("tools.profiler:async-profiler:3.0")
|
||||
}
|
||||
|
||||
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
|
||||
tasks.test {
|
||||
exclude("**/tmp/*")
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ 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;
|
||||
@@ -22,7 +21,6 @@ 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;
|
||||
@@ -31,17 +29,12 @@ 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 {
|
||||
@@ -105,7 +98,7 @@ public class JadxArgs implements Closeable {
|
||||
private UserRenamesMappingsMode userRenamesMappingsMode = UserRenamesMappingsMode.getDefault();
|
||||
|
||||
private boolean deobfuscationOn = false;
|
||||
private UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias = UseSourceNameAsClassNameAlias.getDefault();
|
||||
private boolean useSourceNameAsClassAlias = false;
|
||||
|
||||
private File generatedRenamesMappingFile = null;
|
||||
private GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
|
||||
@@ -117,7 +110,7 @@ public class JadxArgs implements Closeable {
|
||||
/**
|
||||
* List of classes and packages (ends with '.*') to exclude from deobfuscation
|
||||
*/
|
||||
private List<String> deobfuscationWhitelist = DeobfWhitelist.DEFAULT_LIST;
|
||||
private List<String> deobfuscationWhitelist = new ArrayList<>(DeobfWhitelist.DEFAULT_LIST);
|
||||
|
||||
/**
|
||||
* Nodes alias provider for deobfuscator and rename visitor
|
||||
@@ -134,8 +127,6 @@ public class JadxArgs implements Closeable {
|
||||
private boolean respectBytecodeAccModifiers = false;
|
||||
private boolean exportAsGradleProject = false;
|
||||
|
||||
private boolean restoreSwitchOverString = true;
|
||||
|
||||
private boolean skipXmlPrettyPrint = false;
|
||||
|
||||
private boolean fsCaseSensitive;
|
||||
@@ -172,31 +163,13 @@ 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;
|
||||
@@ -452,29 +425,12 @@ public class JadxArgs implements Closeable {
|
||||
this.generatedRenamesMappingFileMode = mode;
|
||||
}
|
||||
|
||||
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();
|
||||
return useSourceNameAsClassAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setUseSourceNameAsClassAlias(boolean useSourceNameAsClassAlias) {
|
||||
final var useSourceNameAsClassNameAlias = UseSourceNameAsClassNameAlias.create(useSourceNameAsClassAlias);
|
||||
setUseSourceNameAsClassNameAlias(useSourceNameAsClassNameAlias);
|
||||
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
|
||||
}
|
||||
|
||||
public int getDeobfuscationMinLength() {
|
||||
@@ -565,14 +521,6 @@ 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;
|
||||
}
|
||||
@@ -729,22 +677,6 @@ 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;
|
||||
}
|
||||
@@ -753,14 +685,6 @@ 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;
|
||||
}
|
||||
@@ -769,14 +693,6 @@ 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;
|
||||
}
|
||||
@@ -800,11 +716,10 @@ 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 + escapeUnicode + replaceConsts + restoreSwitchOverString
|
||||
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
|
||||
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
|
||||
+ commentsLevel + useDxInput + integerFormat
|
||||
+ "|" + buildPluginsHash(decompiler);
|
||||
@@ -840,7 +755,7 @@ public class JadxArgs implements Closeable {
|
||||
+ ", generatedRenamesMappingFile=" + generatedRenamesMappingFile
|
||||
+ ", generatedRenamesMappingFileMode=" + generatedRenamesMappingFileMode
|
||||
+ ", resourceNameSource=" + resourceNameSource
|
||||
+ ", useSourceNameAsClassNameAlias=" + useSourceNameAsClassNameAlias
|
||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||
+ ", insertDebugLines=" + insertDebugLines
|
||||
+ ", extractFinally=" + extractFinally
|
||||
@@ -849,7 +764,6 @@ 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,42 +85,39 @@ public final class JadxDecompiler implements Closeable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
||||
|
||||
private final JadxArgs args;
|
||||
private final JadxPluginManager pluginManager;
|
||||
private final JadxPluginManager pluginManager = new JadxPluginManager(this);
|
||||
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 ResourcesLoader resourcesLoader;
|
||||
private final JadxEventsImpl events = new JadxEventsImpl();
|
||||
private final ResourcesLoader resourcesLoader = new ResourcesLoader(this);
|
||||
|
||||
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 = Objects.requireNonNull(args);
|
||||
this.pluginManager = new JadxPluginManager(this);
|
||||
this.resourcesLoader = new ResourcesLoader(this);
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public void load() {
|
||||
reset();
|
||||
JadxArgsValidator.validate(this);
|
||||
LOG.info("loading ...");
|
||||
FileUtils.updateTempRootDir(args.getFilesGetter().getTempDir());
|
||||
loadPlugins();
|
||||
loadInputFiles();
|
||||
|
||||
root = new RootNode(args);
|
||||
root.init();
|
||||
root.setDecompilerRef(this);
|
||||
root.mergePasses(customPasses);
|
||||
root.loadClasses(loadedInputs);
|
||||
@@ -145,9 +142,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
|
||||
private void loadInputFiles() {
|
||||
loadedInputs.clear();
|
||||
List<File> inputs = ZipPatch.patchZipFiles(args.getInputFiles());
|
||||
args.setInputFiles(inputs);
|
||||
List<Path> inputPaths = Utils.collectionMap(inputs, File::toPath);
|
||||
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
||||
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
|
||||
long start = System.currentTimeMillis();
|
||||
for (PluginContext plugin : pluginManager.getResolvedPluginContexts()) {
|
||||
@@ -172,6 +167,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
root = null;
|
||||
classes = null;
|
||||
resources = null;
|
||||
binaryXmlParser = null;
|
||||
events.reset();
|
||||
}
|
||||
|
||||
@@ -181,7 +177,6 @@ public final class JadxDecompiler implements Closeable {
|
||||
closeInputs();
|
||||
closeLoaders();
|
||||
args.close();
|
||||
FileUtils.clearTempRootDir();
|
||||
}
|
||||
|
||||
private void closeInputs() {
|
||||
@@ -200,7 +195,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();
|
||||
@@ -471,6 +466,13 @@ 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
|
||||
*/
|
||||
@@ -669,10 +671,6 @@ 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,10 +252,6 @@ public final class JavaClass implements JavaNode {
|
||||
return cls.getPackage();
|
||||
}
|
||||
|
||||
public JavaPackage getJavaPackage() {
|
||||
return cls.getPackageNode().getJavaNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getDeclaringClass() {
|
||||
return parent;
|
||||
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -78,9 +79,15 @@ public final class JavaMethod implements JavaNode {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
||||
return ovrdAttr.getRelatedMthNodes()
|
||||
.stream()
|
||||
.map(decompiler::convertMethodNode)
|
||||
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)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -97,10 +104,6 @@ public final class JavaMethod implements JavaNode {
|
||||
return mth.getDefPosition();
|
||||
}
|
||||
|
||||
public String getCodeStr() {
|
||||
return mth.getCodeStr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAlias() {
|
||||
this.mth.getMethodInfo().removeAlias();
|
||||
|
||||
@@ -76,22 +76,6 @@ 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,6 +9,7 @@ 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;
|
||||
@@ -26,7 +27,6 @@ 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;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
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,7 +6,6 @@ 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;
|
||||
@@ -54,9 +53,4 @@ public interface JadxPluginContext {
|
||||
* Access to registered plugins and runtime data
|
||||
*/
|
||||
IJadxPlugins plugins();
|
||||
|
||||
/**
|
||||
* Access to plugin specific files and directories
|
||||
*/
|
||||
IJadxFiles files();
|
||||
}
|
||||
|
||||
@@ -1,29 +1,15 @@
|
||||
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 String homepage;
|
||||
private final String homepage;
|
||||
|
||||
/**
|
||||
* Conflicting plugins should have the same 'provides' property; only one will be loaded
|
||||
*/
|
||||
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;
|
||||
private final String provides;
|
||||
|
||||
public JadxPluginInfo(String id, String name, String description) {
|
||||
this(id, name, description, "", id);
|
||||
@@ -57,26 +43,10 @@ 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,14 +4,11 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -46,11 +43,6 @@ 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");
|
||||
@@ -58,11 +50,6 @@ public class JadxPluginInfoBuilder {
|
||||
if (provides == null) {
|
||||
provides = pluginId;
|
||||
}
|
||||
if (requiredJadxVersion != null) {
|
||||
VerifyRequiredVersion.verify(requiredJadxVersion);
|
||||
}
|
||||
JadxPluginInfo pluginInfo = new JadxPluginInfo(pluginId, name, description, homepage, provides);
|
||||
pluginInfo.setRequiredJadxVersion(requiredJadxVersion);
|
||||
return pluginInfo;
|
||||
return new JadxPluginInfo(pluginId, name, description, homepage, provides);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
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,15 +15,4 @@ 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,13 +6,4 @@ 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("NODE_RENAMED_BY_USER");
|
||||
public static final JadxEventType<NodeRenamedByUser> NODE_RENAMED_BY_USER = create();
|
||||
|
||||
/**
|
||||
* Request reload of a current project (GUI only).
|
||||
*/
|
||||
public static final JadxEventType<ReloadProject> RELOAD_PROJECT = create("RELOAD_PROJECT");
|
||||
public static final JadxEventType<ReloadProject> RELOAD_PROJECT = create();
|
||||
|
||||
/**
|
||||
* 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("RELOAD_SETTINGS_WINDOW");
|
||||
public static final JadxEventType<ReloadSettingsWindow> RELOAD_SETTINGS_WINDOW = create();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ 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;
|
||||
@@ -51,12 +50,6 @@ 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,6 +8,7 @@ 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;
|
||||
@@ -15,7 +16,6 @@ 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);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
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,13 +1,5 @@
|
||||
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) {
|
||||
@@ -55,71 +47,4 @@ 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 = Jadx.isDevVersion();
|
||||
public static final boolean DEBUG_EVENTS = true;
|
||||
|
||||
public static final String CLASS_OBJECT = "java.lang.Object";
|
||||
public static final String CLASS_STRING = "java.lang.String";
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.deobf.DeobfuscatorVisitor;
|
||||
import jadx.core.deobf.InitRenameProviders;
|
||||
import jadx.core.deobf.SaveDeobfMapping;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.visitors.AnonymousClassVisitor;
|
||||
@@ -28,6 +29,7 @@ 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;
|
||||
@@ -47,13 +49,11 @@ 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;
|
||||
@@ -63,7 +63,6 @@ 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;
|
||||
@@ -104,6 +103,7 @@ public class Jadx {
|
||||
passes.add(new CollectConstValues());
|
||||
|
||||
// rename and deobfuscation
|
||||
passes.add(new InitRenameProviders());
|
||||
passes.add(new DeobfuscatorVisitor());
|
||||
passes.add(new SourceFileRename());
|
||||
passes.add(new RenameVisitor());
|
||||
@@ -132,7 +132,6 @@ public class Jadx {
|
||||
// blocks IR
|
||||
passes.add(new BlockSplitter());
|
||||
passes.add(new BlockProcessor());
|
||||
passes.add(new BlockFinisher());
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
}
|
||||
@@ -173,9 +172,6 @@ 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,6 +8,7 @@ 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;
|
||||
@@ -31,8 +32,8 @@ public class ProcessClass {
|
||||
|
||||
private final List<IDexTreeVisitor> passes;
|
||||
|
||||
public ProcessClass(List<IDexTreeVisitor> passesList) {
|
||||
this.passes = passesList;
|
||||
public ProcessClass(JadxArgs args) {
|
||||
this.passes = Jadx.getPassesList(args);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -124,33 +125,6 @@ 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 final ClspClassSource source;
|
||||
private ClspClassSource source;
|
||||
|
||||
public ClspClass(ArgType clsType, int id, int accFlags, ClspClassSource source) {
|
||||
this.clsType = clsType;
|
||||
|
||||
@@ -652,13 +652,6 @@ 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; k++) {
|
||||
for (; k < dim - 1; k++) {
|
||||
code.add("[]");
|
||||
}
|
||||
break;
|
||||
@@ -749,7 +749,6 @@ 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)
|
||||
@@ -757,11 +756,7 @@ public class InsnGen {
|
||||
} else {
|
||||
code.attachAnnotation(refMth);
|
||||
}
|
||||
if (forceShortName) {
|
||||
mgen.getClassGen().addClsShortNameForced(code, insn.getClassType());
|
||||
} else {
|
||||
mgen.getClassGen().addClsName(code, insn.getClassType());
|
||||
}
|
||||
mgen.getClassGen().addClsName(code, insn.getClassType());
|
||||
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
|
||||
if (genericInfoAttr != null) {
|
||||
code.add('<');
|
||||
@@ -782,27 +777,6 @@ 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() || mth.getRegion() == null) {
|
||||
if (classGen.isFallbackMode()) {
|
||||
// TODO: try simple mode first
|
||||
dumpInstructions(code);
|
||||
} else {
|
||||
|
||||
@@ -278,8 +278,6 @@ 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,17 +135,10 @@ public class CodeGenUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void addInputFileInfo(ICodeWriter code, ClassNode cls) {
|
||||
if (cls.checkCommentsLevel(CommentsLevel.INFO) && cls.getClsData() != null) {
|
||||
String inputFileName = cls.getClsData().getInputFileName();
|
||||
public static void addInputFileInfo(ICodeWriter code, ClassNode node) {
|
||||
if (node.getClsData() != null && node.checkCommentsLevel(CommentsLevel.INFO)) {
|
||||
String inputFileName = node.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(" */");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
public class InitRenameProviders extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) throws JadxException {
|
||||
JadxArgs args = root.getArgs();
|
||||
if (args.isDeobfuscationOn() || !args.getRenameFlags().isEmpty()) {
|
||||
args.getAliasProvider().init(root);
|
||||
}
|
||||
if (args.isDeobfuscationOn()) {
|
||||
args.getRenameCondition().init(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ public class NameMapper {
|
||||
|
||||
private static final Set<String> RESERVED_NAMES = new HashSet<>(
|
||||
Arrays.asList(
|
||||
"_",
|
||||
"abstract",
|
||||
"assert",
|
||||
"boolean",
|
||||
|
||||
@@ -5,39 +5,81 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.PackageNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class DeobfWhitelist extends AbstractDeobfCondition {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeobfWhitelist.class);
|
||||
|
||||
public static final List<String> DEFAULT_LIST = Arrays.asList(
|
||||
"android.support.v4.*",
|
||||
"android.support.v7.*",
|
||||
"android.support.v4.os.*",
|
||||
"android.support.annotation.Px",
|
||||
"android.support.*",
|
||||
"android.os.*",
|
||||
"androidx.core.os.*",
|
||||
"androidx.annotation.Px");
|
||||
"androidx.annotation.*");
|
||||
|
||||
public static final String DEFAULT_STR = Utils.listToString(DEFAULT_LIST, " ");
|
||||
|
||||
private final Set<String> packages = new HashSet<>();
|
||||
private final Set<String> classes = new HashSet<>();
|
||||
private final Set<ClassNode> classes = new HashSet<>();
|
||||
private boolean reportMissingItems = false;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
packages.clear();
|
||||
classes.clear();
|
||||
for (String whitelistItem : root.getArgs().getDeobfuscationWhitelist()) {
|
||||
if (!whitelistItem.isEmpty()) {
|
||||
if (whitelistItem.endsWith(".*")) {
|
||||
packages.add(whitelistItem.substring(0, whitelistItem.length() - 2));
|
||||
} else {
|
||||
classes.add(whitelistItem);
|
||||
}
|
||||
List<String> excludeList = root.getArgs().getDeobfuscationWhitelist();
|
||||
reportMissingItems = !excludeList.equals(DEFAULT_LIST);
|
||||
for (String name : excludeList) {
|
||||
if (name.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (name.endsWith(".*")) {
|
||||
excludePackage(root, name.substring(0, name.length() - 2));
|
||||
} else {
|
||||
excludeClass(root, name);
|
||||
}
|
||||
}
|
||||
LOG.debug("Excluded from deobfuscation: {} packages, {} classes", packages.size(), classes.size());
|
||||
}
|
||||
|
||||
private void excludeClass(RootNode root, String clsFullName) {
|
||||
ClassNode cls = root.resolveClass(clsFullName);
|
||||
if (cls == null) {
|
||||
if (reportMissingItems) {
|
||||
LOG.info("Can't exclude from deobfuscation: class '{}' not found", clsFullName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
excludeClsNode(cls);
|
||||
}
|
||||
|
||||
private void excludeClsNode(ClassNode cls) {
|
||||
classes.add(cls);
|
||||
cls.addInfoComment("Class excluded from deobfuscation");
|
||||
}
|
||||
|
||||
private void excludePackage(RootNode root, String fullPkgName) {
|
||||
PackageNode pkg = root.resolvePackage(fullPkgName);
|
||||
if (pkg == null) {
|
||||
if (reportMissingItems) {
|
||||
LOG.info("Can't exclude from deobfuscation: package '{}' not found", fullPkgName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
excludePkgNode(pkg);
|
||||
}
|
||||
|
||||
private void excludePkgNode(PackageNode pkg) {
|
||||
packages.add(pkg.getFullName());
|
||||
pkg.getClasses().forEach(this::excludeClsNode);
|
||||
pkg.getSubPackages().forEach(this::excludePkgNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,9 +92,19 @@ public class DeobfWhitelist extends AbstractDeobfCondition {
|
||||
|
||||
@Override
|
||||
public Action check(ClassNode cls) {
|
||||
if (classes.contains(cls.getClassInfo().getFullName())) {
|
||||
if (classes.contains(cls)) {
|
||||
return Action.FORBID_RENAME;
|
||||
}
|
||||
return Action.NO_ACTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action check(FieldNode fld) {
|
||||
return check(fld.getParentClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action check(MethodNode mth) {
|
||||
return check(mth.getParentClass());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ import jadx.core.dex.nodes.PackageNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
/**
|
||||
* Provides a list of all top level domains, so we can exclude them from deobfuscation.
|
||||
* Provides a list of all top level domains with 3 characters and less,
|
||||
* so we can exclude them from deobfuscation.
|
||||
*/
|
||||
public class ExcludePackageWithTLDNames extends AbstractDeobfCondition {
|
||||
|
||||
@@ -17,22 +18,23 @@ public class ExcludePackageWithTLDNames extends AbstractDeobfCondition {
|
||||
* Lazy load TLD set
|
||||
*/
|
||||
private static class TldHolder {
|
||||
private static final Set<String> TLD_SET = loadTldSet();
|
||||
private static final Set<String> TLD_SET = loadTldFile();
|
||||
}
|
||||
|
||||
private static Set<String> loadTldSet() {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(TldHolder.class.getResourceAsStream("tlds.txt")))) {
|
||||
private static Set<String> loadTldFile() {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(TldHolder.class.getResourceAsStream("tld_3.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: tlds.txt", e);
|
||||
throw new JadxRuntimeException("Failed to load top level domain list file: tld_3.txt", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action check(PackageNode pkg) {
|
||||
if (pkg.isRoot() && TldHolder.TLD_SET.contains(pkg.getName())) {
|
||||
if (TldHolder.TLD_SET.contains(pkg.getName())) {
|
||||
return Action.FORBID_RENAME;
|
||||
}
|
||||
return Action.NO_ACTION;
|
||||
|
||||
@@ -90,6 +90,7 @@ 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,7 +5,6 @@ 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;
|
||||
@@ -75,7 +74,6 @@ 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<>();
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
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 final List<IMethodDetails> overrideList;
|
||||
private List<IMethodDetails> overrideList;
|
||||
|
||||
/**
|
||||
* All method nodes from override hierarchy. Current method included.
|
||||
*/
|
||||
private SortedSet<MethodNode> relatedMthNodes;
|
||||
|
||||
private final Set<IMethodDetails> baseMethods;
|
||||
private Set<IMethodDetails> baseMethods;
|
||||
|
||||
public MethodOverrideAttr(List<IMethodDetails> overrideList, SortedSet<MethodNode> relatedMthNodes, Set<IMethodDetails> baseMethods) {
|
||||
this.overrideList = overrideList;
|
||||
|
||||
@@ -3,6 +3,10 @@ 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;
|
||||
@@ -13,8 +17,6 @@ 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;
|
||||
@@ -33,6 +35,8 @@ 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;
|
||||
|
||||
@@ -50,7 +54,7 @@ public class InsnDecoder {
|
||||
rawInsn.decode();
|
||||
insn = decode(rawInsn);
|
||||
} catch (Exception e) {
|
||||
method.addError("Failed to decode insn: " + rawInsn, e);
|
||||
method.addError("Failed to decode insn: " + rawInsn + ", method: " + method, e);
|
||||
insn = new InsnNode(InsnType.NOP, 0);
|
||||
insn.addAttr(AType.JADX_ERROR, new JadxError("decode failed: " + e.getMessage(), e));
|
||||
}
|
||||
@@ -60,6 +64,7 @@ public class InsnDecoder {
|
||||
return instructions;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected InsnNode decode(InsnData insn) throws DecodeException {
|
||||
switch (insn.getOpcode()) {
|
||||
case NOP:
|
||||
@@ -509,6 +514,7 @@ 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();
|
||||
@@ -516,7 +522,6 @@ public class InsnDecoder {
|
||||
swInsn.attachSwitchData(new SwitchData((ISwitchPayload) payload), insn.getTarget());
|
||||
}
|
||||
method.add(AFlag.COMPUTE_POST_DOM);
|
||||
CodeFeaturesAttr.add(method, CodeFeature.SWITCH);
|
||||
return swInsn;
|
||||
}
|
||||
|
||||
@@ -540,7 +545,6 @@ 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,9 +43,6 @@ 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,7 +13,6 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -292,20 +291,6 @@ 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,7 +6,6 @@ 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;
|
||||
@@ -293,19 +292,10 @@ public class SSAVar implements Comparable<SSAVar> {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('r').append(regNum).append('v').append(version);
|
||||
if (!names.isEmpty()) {
|
||||
String orderedNames = names.stream()
|
||||
.sorted()
|
||||
.collect(Collectors.joining(", ", "[", "]"));
|
||||
|
||||
sb.append(", names: ").append(orderedNames);
|
||||
sb.append(", names: ").append(names);
|
||||
}
|
||||
if (!types.isEmpty()) {
|
||||
String orderedTypes = types.stream()
|
||||
.map(String::valueOf)
|
||||
.sorted()
|
||||
.collect(Collectors.joining(", ", "[", "]"));
|
||||
|
||||
sb.append(", types: ").append(orderedTypes);
|
||||
sb.append(", types: ").append(types);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -25,12 +25,8 @@ 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 = callMth;
|
||||
this.callMth = invoke.getCallMth();
|
||||
this.callType = getCallType(mth, callMth.getDeclClass(), invoke.getArg(0));
|
||||
int argsCount = invoke.getArgsCount();
|
||||
for (int i = 1; i < argsCount; i++) {
|
||||
|
||||
@@ -226,10 +226,6 @@ 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,7 +37,6 @@ 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;
|
||||
@@ -325,10 +324,9 @@ public class ClassNode extends NotificationAttrNode
|
||||
try {
|
||||
unload();
|
||||
args.setDecompilationMode(mode);
|
||||
ProcessClass process = new ProcessClass(Jadx.getPassesList(args));
|
||||
ProcessClass process = new ProcessClass(args);
|
||||
process.initPasses(root);
|
||||
ICodeInfo code = process.forceGenerateCode(this);
|
||||
return Utils.getOrElse(code, ICodeInfo.EMPTY);
|
||||
return process.generateCode(this);
|
||||
} finally {
|
||||
args.setDecompilationMode(baseMode);
|
||||
unload();
|
||||
@@ -586,11 +584,6 @@ public class ClassNode extends NotificationAttrNode
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassNode getDeclaringClass() {
|
||||
return isInner() ? parentClass : null;
|
||||
}
|
||||
|
||||
public ClassNode getParentClass() {
|
||||
return parentClass;
|
||||
}
|
||||
@@ -864,12 +857,7 @@ public class ClassNode extends NotificationAttrNode
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Low level class data access.
|
||||
*
|
||||
* @return null for classes generated by jadx
|
||||
*/
|
||||
public @Nullable IClassData getClsData() {
|
||||
public IClassData getClsData() {
|
||||
return clsData;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,11 +86,6 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode, IField
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassNode getDeclaringClass() {
|
||||
return parentClass;
|
||||
}
|
||||
|
||||
public ClassNode getParentClass() {
|
||||
return parentClass;
|
||||
}
|
||||
|
||||
@@ -5,9 +5,6 @@ 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,10 +95,6 @@ public class InsnNode extends LineAttrNode {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public List<InsnArg> getArgList() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public int getArgsCount() {
|
||||
return arguments.size();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -325,11 +324,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
return mthInfo.getAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassNode getDeclaringClass() {
|
||||
return parentClass;
|
||||
}
|
||||
|
||||
public ClassNode getParentClass() {
|
||||
return parentClass;
|
||||
}
|
||||
@@ -408,10 +402,6 @@ 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);
|
||||
@@ -673,13 +663,6 @@ 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();
|
||||
@@ -705,7 +688,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
return javaNode;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public void setJavaNode(JavaMethod javaNode) {
|
||||
this.javaNode = javaNode;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ 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;
|
||||
@@ -49,7 +48,6 @@ 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;
|
||||
@@ -66,6 +64,10 @@ 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;
|
||||
@@ -76,7 +78,6 @@ 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<>();
|
||||
@@ -86,24 +87,21 @@ 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;
|
||||
private @Nullable String appPackage;
|
||||
private @Nullable ClassNode appResClass;
|
||||
@Nullable
|
||||
private String appPackage;
|
||||
@Nullable
|
||||
private 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(Jadx.getPassesList(args));
|
||||
this.processClasses = new ProcessClass(args);
|
||||
this.stringUtils = new StringUtils(args);
|
||||
this.constValues = new ConstStorage(args);
|
||||
this.typeUpdate = new TypeUpdate(this);
|
||||
@@ -111,15 +109,6 @@ public class RootNode {
|
||||
this.typeUtils = new TypeUtils(this);
|
||||
}
|
||||
|
||||
public void init() {
|
||||
if (args.isDeobfuscationOn() || !args.getRenameFlags().isEmpty()) {
|
||||
args.getAliasProvider().init(this);
|
||||
}
|
||||
if (args.isDeobfuscationOn()) {
|
||||
args.getRenameCondition().init(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadClasses(List<ICodeLoader> loadedInputs) {
|
||||
for (ICodeLoader codeLoader : loadedInputs) {
|
||||
codeLoader.visitClasses(cls -> {
|
||||
@@ -213,13 +202,18 @@ public class RootNode {
|
||||
if (parser != null) {
|
||||
processResources(parser.getResStorage());
|
||||
updateObfuscatedFiles(parser, resources);
|
||||
initManifestAttributes().updateAttributes(parser);
|
||||
updateManifestAttribMap(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) {
|
||||
@@ -313,21 +307,10 @@ 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() {
|
||||
@@ -723,13 +706,4 @@ 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,24 +169,6 @@ 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,11 +71,7 @@ public class CheckCode extends AbstractVisitor {
|
||||
}
|
||||
insnNode.getRegisterArgs(list);
|
||||
for (RegisterArg arg : list) {
|
||||
int regNum = arg.getRegNum();
|
||||
if (regNum < 0) {
|
||||
throw new JadxRuntimeException("Incorrect negative register number in instruction: " + insnNode);
|
||||
}
|
||||
if (regNum >= regsCount) {
|
||||
if (arg.getRegNum() >= regsCount) {
|
||||
throw new JadxRuntimeException("Incorrect register number in instruction: " + insnNode
|
||||
+ ", expected to be less than " + regsCount);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ 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,18 +1,11 @@
|
||||
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;
|
||||
@@ -25,7 +18,6 @@ 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",
|
||||
@@ -42,6 +34,9 @@ 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) {
|
||||
@@ -63,32 +58,24 @@ public class ConstructorVisitor extends AbstractVisitor {
|
||||
|
||||
private static boolean processInvoke(MethodNode mth, BlockNode block, int indexInBlock, InsnRemover remover) {
|
||||
InvokeNode inv = (InvokeNode) block.getInstructions().get(indexInBlock);
|
||||
MethodInfo callMth = inv.getCallMth();
|
||||
if (!callMth.isConstructor()) {
|
||||
if (!inv.getCallMth().isConstructor()) {
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
ConstructorInsn co = new ConstructorInsn(mth, inv);
|
||||
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
|
||||
// insert new PHI insn to merge these branched constructors results
|
||||
instanceArg = insertPhiInsn(mth, block, instanceArg, ((ConstructorInsn) assignInsn));
|
||||
mth.add(AFlag.RERUN_SSA_TRANSFORM);
|
||||
} else {
|
||||
InsnNode newInstInsn = removeAssignChain(mth, assignInsn, remover, InsnType.NEW_INSTANCE);
|
||||
if (newInstInsn != null) {
|
||||
@@ -112,56 +99,6 @@ 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,6 +3,7 @@ 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 {
|
||||
|
||||
@@ -23,6 +24,9 @@ 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,11 +57,6 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
this.rawInsn = rawInsn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "DotGraphVisitor";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
if (mth.isNoCode()) {
|
||||
|
||||
+73
-69
@@ -1,12 +1,8 @@
|
||||
package jadx.core.dex.visitors.fixaccessmodifiers;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
@@ -14,9 +10,6 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
import jadx.core.dex.visitors.ModVisitor;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
@@ -26,13 +19,10 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
)
|
||||
public class FixAccessModifiers extends AbstractVisitor {
|
||||
|
||||
private VisibilityUtils visibilityUtils;
|
||||
|
||||
private boolean respectAccessModifiers;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
this.visibilityUtils = new VisibilityUtils(root);
|
||||
this.respectAccessModifiers = root.getArgs().isRespectBytecodeAccModifiers();
|
||||
}
|
||||
|
||||
@@ -41,8 +31,10 @@ public class FixAccessModifiers extends AbstractVisitor {
|
||||
if (respectAccessModifiers) {
|
||||
return true;
|
||||
}
|
||||
|
||||
fixClassVisibility(cls);
|
||||
int newVisFlag = fixClassVisibility(cls);
|
||||
if (newVisFlag != -1) {
|
||||
changeVisibility(cls, newVisFlag);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -51,62 +43,9 @@ public class FixAccessModifiers extends AbstractVisitor {
|
||||
if (respectAccessModifiers || mth.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
fixMethodVisibility(mth);
|
||||
}
|
||||
|
||||
private void fixClassVisibility(ClassNode cls) {
|
||||
AccessInfo accessFlags = cls.getAccessFlags();
|
||||
if (cls.isTopClass() && accessFlags.isPublic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cls.isTopClass() && (accessFlags.isPrivate() || accessFlags.isProtected())) {
|
||||
changeVisibility(cls, AccessFlags.PUBLIC);
|
||||
return;
|
||||
}
|
||||
|
||||
for (ClassNode useCls : cls.getUseIn()) {
|
||||
visibilityUtils.checkVisibility(cls, useCls, (node, visFlag) -> {
|
||||
changeVisibility((NotificationAttrNode) node, visFlag);
|
||||
});
|
||||
}
|
||||
|
||||
for (MethodNode useMth : cls.getUseInMth()) {
|
||||
MethodInlineAttr inlineAttr = useMth.get(AType.METHOD_INLINE);
|
||||
boolean isInline = inlineAttr != null && !inlineAttr.notNeeded();
|
||||
boolean isCandidateForInline = useMth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE);
|
||||
|
||||
if (isInline || isCandidateForInline) {
|
||||
Set<ClassNode> usedInClss = useMth.getUseIn().stream()
|
||||
.map(MethodNode::getParentClass)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (ClassNode useCls : usedInClss) {
|
||||
visibilityUtils.checkVisibility(cls, useCls, (node, visFlag) -> {
|
||||
changeVisibility((NotificationAttrNode) node, visFlag);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fixMethodVisibility(MethodNode mth) {
|
||||
AccessInfo accessFlags = mth.getAccessFlags();
|
||||
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||
if (overrideAttr != null && !overrideAttr.getOverrideList().isEmpty()) {
|
||||
// visibility can't be weaker
|
||||
IMethodDetails parentMD = overrideAttr.getOverrideList().get(0);
|
||||
AccessInfo parentAccInfo = new AccessInfo(parentMD.getRawAccessFlags(), AccessInfo.AFType.METHOD);
|
||||
if (accessFlags.isVisibilityWeakerThan(parentAccInfo)) {
|
||||
changeVisibility(mth, parentAccInfo.getVisibility().rawValue());
|
||||
}
|
||||
}
|
||||
|
||||
for (MethodNode useMth : mth.getUseIn()) {
|
||||
visibilityUtils.checkVisibility(mth, useMth, (node, visFlag) -> {
|
||||
changeVisibility((NotificationAttrNode) node, visFlag);
|
||||
});
|
||||
int newVisFlag = fixMethodVisibility(mth);
|
||||
if (newVisFlag != -1) {
|
||||
changeVisibility(mth, newVisFlag);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,4 +57,69 @@ public class FixAccessModifiers extends AbstractVisitor {
|
||||
node.addInfoComment("Access modifiers changed from: " + accessFlags.visibilityName());
|
||||
}
|
||||
}
|
||||
|
||||
private int fixClassVisibility(ClassNode cls) {
|
||||
if (cls.getUseIn().isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
AccessInfo accessFlags = cls.getAccessFlags();
|
||||
if (accessFlags.isPrivate()) {
|
||||
if (!cls.isInner()) {
|
||||
return AccessFlags.PUBLIC;
|
||||
}
|
||||
// check if private inner class is used outside
|
||||
ClassNode topParentClass = cls.getTopParentClass();
|
||||
for (ClassNode useCls : cls.getUseIn()) {
|
||||
if (useCls.getTopParentClass() != topParentClass) {
|
||||
return AccessFlags.PUBLIC;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (accessFlags.isPackagePrivate()) {
|
||||
String pkg = cls.getPackage();
|
||||
for (ClassNode useCls : cls.getUseIn()) {
|
||||
if (!useCls.getPackage().equals(pkg)) {
|
||||
return AccessFlags.PUBLIC;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!accessFlags.isPublic()) {
|
||||
// if class is used in inlinable method => make it public
|
||||
for (MethodNode useMth : cls.getUseInMth()) {
|
||||
boolean canInline = useMth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE) || useMth.contains(AType.METHOD_INLINE);
|
||||
if (canInline && !useMth.getUseIn().isEmpty()) {
|
||||
return AccessFlags.PUBLIC;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int fixMethodVisibility(MethodNode mth) {
|
||||
AccessInfo accessFlags = mth.getAccessFlags();
|
||||
if (accessFlags.isPublic()) {
|
||||
return -1;
|
||||
}
|
||||
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||
if (overrideAttr != null && !overrideAttr.getOverrideList().isEmpty()) {
|
||||
// visibility can't be weaker
|
||||
IMethodDetails parentMD = overrideAttr.getOverrideList().get(0);
|
||||
AccessInfo parentAccInfo = new AccessInfo(parentMD.getRawAccessFlags(), AccessInfo.AFType.METHOD);
|
||||
if (accessFlags.isVisibilityWeakerThan(parentAccInfo)) {
|
||||
return parentAccInfo.getVisibility().rawValue();
|
||||
}
|
||||
}
|
||||
if (mth.getUseIn().isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ClassNode thisTopParentCls = mth.getParentClass().getTopParentClass();
|
||||
for (MethodNode useMth : mth.getUseIn()) {
|
||||
ClassNode useInTPCls = useMth.getParentClass().getTopParentClass();
|
||||
if (!useInTPCls.equals(thisTopParentCls)) {
|
||||
return AccessFlags.PUBLIC;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -59,17 +59,12 @@ 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 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;
|
||||
}
|
||||
// method not yet loaded => will retry at codegen stage
|
||||
callMth.getParentClass().reloadAtCodegenStage();
|
||||
return;
|
||||
}
|
||||
if (mia.notNeeded()) {
|
||||
return;
|
||||
|
||||
@@ -19,9 +19,7 @@ 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(
|
||||
@@ -44,22 +42,18 @@ public class MarkMethodsForInline extends AbstractVisitor {
|
||||
*/
|
||||
@Nullable
|
||||
public static MethodInlineAttr process(MethodNode mth) {
|
||||
try {
|
||||
MethodInlineAttr mia = mth.get(AType.METHOD_INLINE);
|
||||
if (mia != null) {
|
||||
return mia;
|
||||
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;
|
||||
}
|
||||
if (mth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE)) {
|
||||
if (mth.getBasicBlocks() == null) {
|
||||
return null;
|
||||
}
|
||||
MethodInlineAttr inlined = inlineMth(mth);
|
||||
if (inlined != null) {
|
||||
return inlined;
|
||||
}
|
||||
MethodInlineAttr inlined = inlineMth(mth);
|
||||
if (inlined != null) {
|
||||
return inlined;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
mth.addWarnComment("Method inline analysis failed", e);
|
||||
}
|
||||
return MethodInlineAttr.inlineNotNeeded(mth);
|
||||
}
|
||||
@@ -119,12 +113,9 @@ public class MarkMethodsForInline extends AbstractVisitor {
|
||||
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
|
||||
|
||||
case INVOKE:
|
||||
if (!retInsn.getArg(0).isSameVar(firstInsn.getResult())) {
|
||||
return false;
|
||||
}
|
||||
return ListUtils.orderedEquals(
|
||||
mth.getArgRegs(), firstInsn.getArgList(),
|
||||
(mthArg, insnArg) -> insnArg.isSameVar(mthArg));
|
||||
return !mthRegs.isEmpty()
|
||||
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0))
|
||||
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
|
||||
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) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user