Compare commits
132 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d5d6b0100 | |||
| 344270a0ba | |||
| 9957f694b9 | |||
| 4b9d69a169 | |||
| 1730dd83bf | |||
| a3a3b492dd | |||
| 2a2806ebd7 | |||
| c7a0f7a092 | |||
| 9710ebe09a | |||
| f0da486703 | |||
| 925503ba04 | |||
| 039900a278 | |||
| d73455c689 | |||
| 07b3e5a7c0 | |||
| c1fc73a524 | |||
| 0d982e9709 | |||
| e6b38e172a | |||
| 331c4aaa5e | |||
| 4e82233cbc | |||
| ad267e1618 | |||
| 54265e34e5 | |||
| c677901aa5 | |||
| 220a2198a1 | |||
| b725dd18b6 | |||
| a0466d4494 | |||
| ae1a5e9277 | |||
| 018c20187d | |||
| add11dff1d | |||
| 005a59668c | |||
| 8f727325d6 | |||
| 7bbb58863b | |||
| 8629e4cf22 | |||
| bd5c6b6516 | |||
| 85a7aaa9fb | |||
| a3df83e60f | |||
| b3be2794a0 | |||
| 02ea3be8f2 | |||
| b78745a87b | |||
| 01af6481f6 | |||
| 0c3e6e77cd | |||
| 8b48219dc3 | |||
| 80187c7e29 | |||
| 22f7b40151 | |||
| 3e709d6693 | |||
| 1aa16c4664 | |||
| 9fba709687 | |||
| 6b61599114 | |||
| 2829e284f3 | |||
| 27b15aeb1c | |||
| 74c52a8884 | |||
| 39e7b82353 | |||
| 365e258180 | |||
| ea68024851 | |||
| 56ae4a6048 | |||
| 1d831d82d4 | |||
| 4c760f1029 | |||
| cf0101f13d | |||
| 748f45b386 | |||
| 9e278efc5c | |||
| cc1beab0b5 | |||
| 2fba204b33 | |||
| 9bf079aad4 | |||
| 6aeaf6aca9 | |||
| 7ea478e18a | |||
| ef99412de1 | |||
| cda1b1ad2c | |||
| 05863881f8 | |||
| 3e208509e0 | |||
| bd75baef56 | |||
| d191f62b8d | |||
| f17c46224d | |||
| e008e818b0 | |||
| 560b1a9ca7 | |||
| 06f113f0a6 | |||
| 48a0073002 | |||
| 88e90722ae | |||
| 1becfa9977 | |||
| 20ed13936a | |||
| 421de675a2 | |||
| 06a118c104 | |||
| 2e93656007 | |||
| c8c4adb60c | |||
| f2e78fe800 | |||
| 7172f8df2d | |||
| d7c6be5664 | |||
| 654cf5e4fb | |||
| bf2d5b5e2e | |||
| 0f495afc99 | |||
| 5f1985f281 | |||
| 2a574d45d5 | |||
| 3ee476c6b6 | |||
| fd3d488016 | |||
| 9898cbb4a1 | |||
| 104a0f0636 | |||
| e58b77267e | |||
| 73913651b4 | |||
| f01e6aa505 | |||
| d9da6a7f89 | |||
| 5726a52ab6 | |||
| f61d90ec2f | |||
| 2cd112cd3d | |||
| 43358643be | |||
| 1f0d3dac0f | |||
| da95a8ae17 | |||
| da3ac6bff0 | |||
| bdbeaff8f0 | |||
| b1f48f1db1 | |||
| 5b09378614 | |||
| b64c93160b | |||
| 58c4f56a71 | |||
| 3d11d1fa87 | |||
| 0d158592e4 | |||
| 0c253f9a1f | |||
| f565178c8c | |||
| f6d13f1860 | |||
| d58c9ac926 | |||
| 7f9d51b9b1 | |||
| 432e49df03 | |||
| b530c234f3 | |||
| 181dcf7b4f | |||
| dc4dcb2bd0 | |||
| 74c396448e | |||
| cb9693a9d1 | |||
| 5d13acc6f3 | |||
| c04dddfa81 | |||
| 1bb645d676 | |||
| ecb597a461 | |||
| 46cd3b5597 | |||
| d523f1b15e | |||
| 8030c2f84e | |||
| 47224dc599 | |||
| d492628bfe |
@@ -8,15 +8,15 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 21
|
java-version: 25
|
||||||
|
|
||||||
- name: Set jadx version
|
- name: Set jadx version
|
||||||
run: |
|
run: |
|
||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew dist distWin
|
run: ./gradlew dist distWin
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
JADX_BUILD_JAVA_VERSION: 11
|
JADX_BUILD_JAVA_VERSION: 11
|
||||||
|
|
||||||
- name: Save bundle artifact
|
- name: Save bundle artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
|
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
|
||||||
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
|
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
retention-days: 14
|
retention-days: 14
|
||||||
|
|
||||||
- name: Save Windows bundle artifact
|
- name: Save Windows bundle artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: ${{ format('jadx-gui-{0}-no-jre-win', env.JADX_VERSION) }}
|
name: ${{ format('jadx-gui-{0}-no-jre-win', env.JADX_VERSION) }}
|
||||||
# Upload unpacked files for now
|
# Upload unpacked files for now
|
||||||
@@ -54,14 +54,14 @@ jobs:
|
|||||||
build-win-bundle:
|
build-win-bundle:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: oracle-actions/setup-java@v1
|
uses: oracle-actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
release: 24
|
release: 25
|
||||||
|
|
||||||
- name: Print Java version
|
- name: Print Java version
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -75,13 +75,13 @@ jobs:
|
|||||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew dist -PbundleJRE=true
|
run: ./gradlew dist -PbundleJRE=true
|
||||||
|
|
||||||
- name: Save Windows with JRE bundle artifact
|
- name: Save Windows with JRE bundle artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
||||||
# Upload unpacked files for now
|
# Upload unpacked files for now
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: Build Test
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master, build-test ]
|
branches: [ master, stable, build-test ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
|
||||||
@@ -14,19 +14,18 @@ jobs:
|
|||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 21
|
java-version: 25
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew build dist distWin
|
run: ./gradlew build dist distWin
|
||||||
env:
|
env:
|
||||||
JADX_BUILD_JAVA_VERSION: 11
|
JADX_BUILD_JAVA_VERSION: 11
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
pull_request:
|
|
||||||
# The branches below must be a subset of the branches above
|
|
||||||
branches: [master]
|
|
||||||
schedule:
|
|
||||||
- cron: '0 9 * * 5'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language: ['java']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v3
|
|
||||||
with:
|
|
||||||
queries: +security-extended
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
|
|
||||||
# Don't build tests in jadx-core also skip tests execution and checkstyle tasks
|
|
||||||
- run: |
|
|
||||||
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v3
|
|
||||||
@@ -13,28 +13,28 @@ jobs:
|
|||||||
build-release-win-bundle:
|
build-release-win-bundle:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: oracle-actions/setup-java@v1
|
uses: oracle-actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
release: 24
|
release: 25
|
||||||
|
|
||||||
- name: Set jadx version
|
- name: Set jadx version
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const jadxVersion = context.ref.split('/').pop().substring(1)
|
const jadxVersion = context.ref.split('/').pop().substring(1)
|
||||||
core.exportVariable('JADX_VERSION', jadxVersion);
|
core.exportVariable('JADX_VERSION', jadxVersion);
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew dist -PbundleJRE=true
|
run: ./gradlew dist -PbundleJRE=true
|
||||||
|
|
||||||
- name: Save JRE bundle artifact
|
- name: Save JRE bundle artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
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) }}
|
path: ${{ format('build/distWinWithJre/jadx-gui-{0}-with-jre-win.zip', env.JADX_VERSION) }}
|
||||||
@@ -45,23 +45,23 @@ jobs:
|
|||||||
needs: build-release-win-bundle
|
needs: build-release-win-bundle
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 21
|
java-version: 25
|
||||||
|
|
||||||
- name: Set jadx version and release name
|
- name: Set jadx version and release name
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const jadxVersion = context.ref.split('/').pop().substring(1)
|
const jadxVersion = context.ref.split('/').pop().substring(1)
|
||||||
core.exportVariable('JADX_VERSION', jadxVersion);
|
core.exportVariable('JADX_VERSION', jadxVersion);
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew dist distWin
|
run: ./gradlew dist distWin
|
||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
JADX_BUILD_JAVA_VERSION: 11
|
JADX_BUILD_JAVA_VERSION: 11
|
||||||
|
|
||||||
- name: Download Windows JRE bundle
|
- name: Download Windows JRE bundle
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
||||||
path: ${{ format('build/jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
path: ${{ format('build/jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
||||||
|
|||||||
@@ -91,110 +91,121 @@ commands (use '<command> --help' for command options):
|
|||||||
plugins - manage jadx plugins
|
plugins - manage jadx plugins
|
||||||
|
|
||||||
options:
|
options:
|
||||||
-d, --output-dir - output directory
|
-d, --output-dir - output directory
|
||||||
-ds, --output-dir-src - output directory for sources
|
-ds, --output-dir-src - output directory for sources
|
||||||
-dr, --output-dir-res - output directory for resources
|
-dr, --output-dir-res - output directory for resources
|
||||||
-r, --no-res - do not decode resources
|
-r, --no-res - do not decode resources
|
||||||
-s, --no-src - do not decompile source code
|
-s, --no-src - do not decompile source code
|
||||||
-j, --threads-count - processing threads count, default: 4
|
-j, --threads-count - processing threads count, default: 16
|
||||||
--single-class - decompile a single class, full name, raw or alias
|
--single-class - decompile a single class, full name, raw or alias
|
||||||
--single-class-output - file or dir for write if decompile a single class
|
--single-class-output - file or dir for write if decompile a single class
|
||||||
--output-format - can be 'java' or 'json', default: java
|
--output-format - can be 'java' or 'json', default: java
|
||||||
-e, --export-gradle - save as gradle project (set '--export-gradle-type' to 'auto')
|
-e, --export-gradle - save as gradle project (set '--export-gradle-type' to 'auto')
|
||||||
--export-gradle-type - Gradle project template for export:
|
--export-gradle-type - Gradle project template for export:
|
||||||
'auto' - detect automatically
|
'auto' - detect automatically
|
||||||
'android-app' - Android Application (apk)
|
'android-app' - Android Application (apk)
|
||||||
'android-library' - Android Library (aar)
|
'android-library' - Android Library (aar)
|
||||||
'simple-java' - simple Java
|
'simple-java' - simple Java
|
||||||
-m, --decompilation-mode - code output mode:
|
-m, --decompilation-mode - code output mode:
|
||||||
'auto' - trying best options (default)
|
'auto' - trying best options (default)
|
||||||
'restructure' - restore code structure (normal java code)
|
'restructure' - restore code structure (normal java code)
|
||||||
'simple' - simplified instructions (linear, with goto's)
|
'simple' - simplified instructions (linear, with goto's)
|
||||||
'fallback' - raw instructions without modifications
|
'fallback' - raw instructions without modifications
|
||||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||||
--no-xml-pretty-print - do not prettify XML
|
--no-xml-pretty-print - do not prettify XML
|
||||||
--no-imports - disable use of imports, always write entire package name
|
--no-imports - disable use of imports, always write entire package name
|
||||||
--no-debug-info - disable debug info parsing and processing
|
--no-debug-info - disable debug info parsing and processing
|
||||||
--add-debug-lines - add comments with debug line numbers if available
|
--add-debug-lines - add comments with debug line numbers if available
|
||||||
--no-inline-anonymous - disable anonymous classes inline
|
--no-inline-anonymous - disable anonymous classes inline
|
||||||
--no-inline-methods - disable methods inline
|
--no-inline-methods - disable methods inline
|
||||||
--no-move-inner-classes - disable move inner classes into parent
|
--no-move-inner-classes - disable move inner classes into parent
|
||||||
--no-inline-kotlin-lambda - disable inline for Kotlin lambdas
|
--no-inline-kotlin-lambda - disable inline for Kotlin lambdas
|
||||||
--no-finally - don't extract finally block
|
--no-finally - don't extract finally block
|
||||||
--no-restore-switch-over-string - don't restore switch over string
|
--no-restore-switch-over-string - don't restore switch over string
|
||||||
--no-replace-consts - don't replace constant value with matching constant field
|
--no-replace-consts - don't replace constant value with matching constant field
|
||||||
--escape-unicode - escape non latin characters in strings (with \u)
|
--escape-unicode - escape non latin characters in strings (with \u)
|
||||||
--respect-bytecode-access-modifiers - don't change original access modifiers
|
--respect-bytecode-access-modifiers - don't change original access modifiers
|
||||||
--mappings-path - deobfuscation mappings file or directory. Allowed formats: Tiny and Tiny v2 (both '.tiny'), Enigma (.mapping) or Enigma directory
|
--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:
|
--mappings-mode - set mode for handling the deobfuscation mapping file:
|
||||||
'read' - just read, user can always save manually (default)
|
'read' - just read, user can always save manually (default)
|
||||||
'read-and-autosave-every-change' - read and autosave after every change
|
'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
|
'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)
|
'ignore' - don't read or save (can be used to skip loading mapping files referenced in the project file)
|
||||||
--deobf - activate deobfuscation
|
--deobf - activate deobfuscation
|
||||||
--deobf-min - min length of name, renamed if shorter, default: 3
|
--deobf-min - min length of name, renamed if shorter, default: 3
|
||||||
--deobf-max - max length of name, renamed if longer, default: 64
|
--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-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 - 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:
|
--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' - read if found, don't save (default)
|
||||||
'read-or-save' - read if found, save otherwise (don't overwrite)
|
'read-or-save' - read if found, save otherwise (don't overwrite)
|
||||||
'overwrite' - don't read, always save
|
'overwrite' - don't read, always save
|
||||||
'ignore' - don't read and don't save
|
'ignore' - don't read and don't save
|
||||||
--deobf-res-name-source - better name source for resources:
|
--deobf-res-name-source - better name source for resources:
|
||||||
'auto' - automatically select best name (default)
|
'auto' - automatically select best name (default)
|
||||||
'resources' - use resources names
|
'resources' - use resources names
|
||||||
'code' - use R class fields names
|
'code' - use R class fields names
|
||||||
--use-source-name-as-class-name-alias - use source name as class name alias:
|
--use-source-name-as-class-name-alias - use source name as class name alias:
|
||||||
'always' - always use source name if it's available
|
'always' - always use source name if it's available
|
||||||
'if-better' - use source name if it seems better than the current one
|
'if-better' - use source name if it seems better than the current one
|
||||||
'never' - never use source name, even if it's available
|
'never' - never use source name, even if it's available
|
||||||
--source-name-repeat-limit - allow using source name if it appears less than a limit number, default: 10
|
--source-name-repeat-limit - allow using source name if it appears less than a limit number, default: 10
|
||||||
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
||||||
--rename-flags - fix options (comma-separated list of):
|
--use-headers-for-detect-resource-extensions - Use headers for detect resource extensions if resource obfuscated
|
||||||
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
--rename-flags - fix options (comma-separated list of):
|
||||||
'valid' - rename java identifiers to make them valid,
|
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
||||||
'printable' - remove non-printable chars from identifiers,
|
'valid' - rename java identifiers to make them valid,
|
||||||
or single 'none' - to disable all renames
|
'printable' - remove non-printable chars from identifiers,
|
||||||
or single 'all' - to enable all (default)
|
or single 'none' - to disable all renames
|
||||||
--integer-format - how integers are displayed:
|
or single 'all' - to enable all (default)
|
||||||
'auto' - automatically select (default)
|
--integer-format - how integers are displayed:
|
||||||
'decimal' - use decimal
|
'auto' - automatically select (default)
|
||||||
'hexadecimal' - use hexadecimal
|
'decimal' - use decimal
|
||||||
--fs-case-sensitive - treat filesystem as case sensitive, false by default
|
'hexadecimal' - use hexadecimal
|
||||||
--cfg - save methods control flow graph to dot file
|
--type-update-limit - type update limit count (per one instruction), default: 10
|
||||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
--fs-case-sensitive - treat filesystem as case sensitive, false by default
|
||||||
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
|
--cfg - save methods control flow graph to dot file
|
||||||
--use-dx - use dx/d8 to convert java bytecode
|
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||||
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
|
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
|
||||||
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
|
--use-dx - use dx/d8 to convert java bytecode
|
||||||
-v, --verbose - verbose output (set --log-level to DEBUG)
|
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
|
||||||
-q, --quiet - turn off output (set --log-level to QUIET)
|
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
|
||||||
--disable-plugins - comma separated list of plugin ids to disable, default:
|
-v, --verbose - verbose output (set --log-level to DEBUG)
|
||||||
--version - print jadx version
|
-q, --quiet - turn off output (set --log-level to QUIET)
|
||||||
-h, --help - print this help
|
--disable-plugins - comma separated list of plugin ids to disable
|
||||||
|
--config <config-ref> - load configuration from file, <config-ref> can be:
|
||||||
|
path to '.json' file
|
||||||
|
short name - uses file with this name from config directory
|
||||||
|
'none' - to disable config loading
|
||||||
|
--save-config <config-ref> - save current options into configuration file and exit, <config-ref> can be:
|
||||||
|
empty - for default config
|
||||||
|
path to '.json' file
|
||||||
|
short name - file will be saved in config directory
|
||||||
|
--print-files - print files and directories used by jadx (config, cache, temp)
|
||||||
|
--version - print jadx version
|
||||||
|
-h, --help - print this help
|
||||||
|
|
||||||
Plugin options (-P<name>=<value>):
|
Plugin options (-P<name>=<value>):
|
||||||
dex-input: Load .dex and .apk files
|
dex-input: Load .dex and .apk files
|
||||||
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
|
- 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: Convert .class, .jar and .aar files to dex
|
||||||
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
|
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
|
||||||
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
|
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
|
||||||
kotlin-metadata: Use kotlin.Metadata annotation for code generation
|
kotlin-metadata: Use kotlin.Metadata annotation for code generation
|
||||||
- kotlin-metadata.class-alias - rename class alias, values: [yes, no], default: yes
|
- 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.method-args - rename function arguments, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.fields - rename fields, 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.companion - rename companion object, values: [yes, no], default: yes
|
||||||
- kotlin-metadata.data-class - add data class modifier, 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.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
|
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
|
||||||
kotlin-smap: Use kotlin.SourceDebugExtension annotation for rename class alias
|
kotlin-smap: Use kotlin.SourceDebugExtension annotation for rename class alias
|
||||||
- kotlin-smap.class-alias-source-dbg - rename class alias from SourceDebugExtension, values: [yes, no], default: no
|
- kotlin-smap.class-alias-source-dbg - rename class alias from SourceDebugExtension, values: [yes, no], default: no
|
||||||
rename-mappings: various mappings support
|
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, INTELLIJ_MIGRATION_MAP_FILE, RECAF_SIMPLE_FILE, JOBF_FILE], default: AUTO
|
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, PROGUARD_FILE, SRG_FILE, XSRG_FILE, JAM_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, INTELLIJ_MIGRATION_MAP_FILE, RECAF_SIMPLE_FILE, JOBF_FILE], default: AUTO
|
||||||
- rename-mappings.invert - invert mapping on load, values: [yes, no], default: no
|
- rename-mappings.invert - invert mapping on load, values: [yes, no], default: no
|
||||||
smali-input: Load .smali files
|
smali-input: Load .smali files
|
||||||
- smali-input.api-level - Android API level, default: 27
|
- smali-input.api-level - Android API level, default: 27
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files
|
JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files
|
||||||
|
|||||||
+15
-8
@@ -6,8 +6,8 @@ import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.github.ben-manes.versions") version "0.52.0"
|
id("com.github.ben-manes.versions") version "0.53.0"
|
||||||
id("se.patrikerdes.use-latest-versions") version "0.2.18"
|
id("se.patrikerdes.use-latest-versions") version "0.2.19"
|
||||||
id("com.diffplug.spotless") version "6.25.0"
|
id("com.diffplug.spotless") version "6.25.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +82,17 @@ fun isNonStable(version: String): Boolean {
|
|||||||
return isStable.not()
|
return isStable.not()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val distWinConfiguration: Configuration by configurations.creating {
|
||||||
|
isCanBeConsumed = false
|
||||||
|
}
|
||||||
|
val distWinWithJreConfiguration: Configuration by configurations.creating {
|
||||||
|
isCanBeConsumed = false
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
distWinConfiguration(project(":jadx-gui", "distWinConfiguration"))
|
||||||
|
distWinWithJreConfiguration(project(":jadx-gui", "distWinWithJreConfiguration"))
|
||||||
|
}
|
||||||
|
|
||||||
val copyArtifacts by tasks.registering(Copy::class) {
|
val copyArtifacts by tasks.registering(Copy::class) {
|
||||||
val jarCliPattern = "jadx-cli-(.*)-all.jar".toPattern()
|
val jarCliPattern = "jadx-cli-(.*)-all.jar".toPattern()
|
||||||
from(tasks.getByPath(":jadx-cli:installShadowDist")) {
|
from(tasks.getByPath(":jadx-cli:installShadowDist")) {
|
||||||
@@ -119,9 +130,7 @@ val distWin by tasks.registering(Zip::class) {
|
|||||||
group = "jadx"
|
group = "jadx"
|
||||||
description = "Build Windows bundle"
|
description = "Build Windows bundle"
|
||||||
|
|
||||||
val guiTask = tasks.getByPath("jadx-gui:copyDistWin")
|
from(distWinConfiguration)
|
||||||
dependsOn(guiTask)
|
|
||||||
from(guiTask.outputs)
|
|
||||||
|
|
||||||
destinationDirectory.set(layout.buildDirectory.dir("distWin"))
|
destinationDirectory.set(layout.buildDirectory.dir("distWin"))
|
||||||
archiveFileName.set("jadx-gui-$jadxVersion-win.zip")
|
archiveFileName.set("jadx-gui-$jadxVersion-win.zip")
|
||||||
@@ -131,9 +140,7 @@ val distWin by tasks.registering(Zip::class) {
|
|||||||
val distWinWithJre by tasks.registering(Zip::class) {
|
val distWinWithJre by tasks.registering(Zip::class) {
|
||||||
description = "Build Windows with JRE bundle"
|
description = "Build Windows with JRE bundle"
|
||||||
|
|
||||||
val guiTask = tasks.getByPath(":jadx-gui:copyDistWinWithJre")
|
from(distWinWithJreConfiguration)
|
||||||
dependsOn(guiTask)
|
|
||||||
from(guiTask.outputs)
|
|
||||||
|
|
||||||
destinationDirectory.set(layout.buildDirectory.dir("distWinWithJre"))
|
destinationDirectory.set(layout.buildDirectory.dir("distWinWithJre"))
|
||||||
archiveFileName.set("jadx-gui-$jadxVersion-with-jre-win.zip")
|
archiveFileName.set("jadx-gui-$jadxVersion-with-jre-win.zip")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.21")
|
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.10")
|
||||||
|
|
||||||
implementation("org.openrewrite:plugin:6.19.1")
|
implementation("org.openrewrite:plugin:6.19.1")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ dependencies {
|
|||||||
implementation("org.slf4j:slf4j-api:2.0.17")
|
implementation("org.slf4j:slf4j-api:2.0.17")
|
||||||
compileOnly("org.jetbrains:annotations:26.0.2")
|
compileOnly("org.jetbrains:annotations:26.0.2")
|
||||||
|
|
||||||
testImplementation("ch.qos.logback:logback-classic:1.5.18")
|
testImplementation("ch.qos.logback:logback-classic:1.5.22")
|
||||||
testImplementation("org.assertj:assertj-core:3.27.3")
|
testImplementation("org.assertj:assertj-core:3.27.6")
|
||||||
|
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter:5.12.2")
|
testImplementation("org.junit.jupiter:junit-jupiter:5.13.3")
|
||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
|
||||||
testCompileOnly("org.jetbrains:annotations:26.0.2")
|
testCompileOnly("org.jetbrains:annotations:26.0.2")
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ plugins {
|
|||||||
id("org.jetbrains.kotlin.jvm")
|
id("org.jetbrains.kotlin.jvm")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(kotlin("stdlib"))
|
||||||
|
implementation(kotlin("reflect")) // don't work from plugin classloader
|
||||||
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
jvmTarget.set(JvmTarget.JVM_11)
|
jvmTarget.set(JvmTarget.JVM_11)
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
rewrite("org.openrewrite.recipe:rewrite-testing-frameworks:3.8.0")
|
rewrite("org.openrewrite.recipe:rewrite-testing-frameworks:3.24.0")
|
||||||
rewrite("org.openrewrite.recipe:rewrite-logging-frameworks:3.8.0")
|
rewrite("org.openrewrite.recipe:rewrite-logging-frameworks:3.20.0")
|
||||||
rewrite("org.openrewrite.recipe:rewrite-migrate-java:3.9.0")
|
rewrite("org.openrewrite.recipe:rewrite-migrate-java:3.24.0")
|
||||||
rewrite("org.openrewrite.recipe:rewrite-static-analysis:2.9.0")
|
rewrite("org.openrewrite.recipe:rewrite-static-analysis:2.24.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=61ad310d3c7d3e5da131b76bbf22b5a4c0786e9d892dae8c1658d4b484de3caa
|
distributionSha256Sum=72f44c9f8ebcb1af43838f45ee5c4aa9c5444898b3468ab3f4af7b6076c5bc3f
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015-2021 the original authors.
|
# Copyright © 2015 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -114,7 +114,6 @@ case "$( uname )" in #(
|
|||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
@@ -172,7 +171,6 @@ fi
|
|||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if "$cygwin" || "$msys" ; then
|
if "$cygwin" || "$msys" ; then
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
@@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-classpath "$CLASSPATH" \
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
# Stop when "xargs" is not available.
|
||||||
|
|||||||
Vendored
+1
-2
@@ -70,11 +70,10 @@ goto fail
|
|||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id("application")
|
id("application")
|
||||||
|
|
||||||
// use shadow only for application scripts, jar will be copied from jadx-gui
|
// use shadow only for application scripts, jar will be copied from jadx-gui
|
||||||
id("com.gradleup.shadow") version "8.3.6"
|
id("com.gradleup.shadow") version "8.3.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -19,13 +19,14 @@ dependencies {
|
|||||||
runtimeOnly(project(":jadx-plugins:jadx-rename-mappings"))
|
runtimeOnly(project(":jadx-plugins:jadx-rename-mappings"))
|
||||||
runtimeOnly(project(":jadx-plugins:jadx-kotlin-metadata"))
|
runtimeOnly(project(":jadx-plugins:jadx-kotlin-metadata"))
|
||||||
runtimeOnly(project(":jadx-plugins:jadx-kotlin-source-debug-extension"))
|
runtimeOnly(project(":jadx-plugins:jadx-kotlin-source-debug-extension"))
|
||||||
runtimeOnly(project(":jadx-plugins:jadx-script:jadx-script-plugin"))
|
|
||||||
runtimeOnly(project(":jadx-plugins:jadx-xapk-input"))
|
runtimeOnly(project(":jadx-plugins:jadx-xapk-input"))
|
||||||
runtimeOnly(project(":jadx-plugins:jadx-aab-input"))
|
runtimeOnly(project(":jadx-plugins:jadx-aab-input"))
|
||||||
runtimeOnly(project(":jadx-plugins:jadx-apkm-input"))
|
runtimeOnly(project(":jadx-plugins:jadx-apkm-input"))
|
||||||
|
runtimeOnly(project(":jadx-plugins:jadx-apks-input"))
|
||||||
|
|
||||||
implementation("org.jcommander:jcommander:2.0")
|
implementation("org.jcommander:jcommander:2.0")
|
||||||
implementation("ch.qos.logback:logback-classic:1.5.18")
|
implementation("ch.qos.logback:logback-classic:1.5.22")
|
||||||
|
implementation("com.google.code.gson:gson:2.13.2")
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
@@ -33,8 +34,10 @@ application {
|
|||||||
mainClass.set("jadx.cli.JadxCLI")
|
mainClass.set("jadx.cli.JadxCLI")
|
||||||
applicationDefaultJvmArgs =
|
applicationDefaultJvmArgs =
|
||||||
listOf(
|
listOf(
|
||||||
|
"-XX:+IgnoreUnrecognizedVMOptions",
|
||||||
"-Xms256M",
|
"-Xms256M",
|
||||||
"-XX:MaxRAMPercentage=70.0",
|
"-XX:MaxRAMPercentage=70.0",
|
||||||
|
"-XX:ParallelGCThreads=3",
|
||||||
// disable zip checks (#1962)
|
// disable zip checks (#1962)
|
||||||
"-Djdk.util.zip.disableZip64ExtraFieldValidation=true",
|
"-Djdk.util.zip.disableZip64ExtraFieldValidation=true",
|
||||||
// Foreign API access for 'directories' library (Windows only)
|
// Foreign API access for 'directories' library (Windows only)
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import jadx.api.plugins.options.OptionDescription;
|
|||||||
import jadx.core.plugins.JadxPluginManager;
|
import jadx.core.plugins.JadxPluginManager;
|
||||||
import jadx.core.plugins.PluginContext;
|
import jadx.core.plugins.PluginContext;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.plugins.tools.JadxExternalPluginsLoader;
|
|
||||||
|
|
||||||
public class JCommanderWrapper {
|
public class JCommanderWrapper {
|
||||||
private final JCommander jc;
|
private final JCommander jc;
|
||||||
@@ -41,12 +40,12 @@ public class JCommanderWrapper {
|
|||||||
|
|
||||||
public boolean parse(String[] args) {
|
public boolean parse(String[] args) {
|
||||||
try {
|
try {
|
||||||
jc.parse(args);
|
String[] fixedArgs = fixArgsForEmptySaveConfig(args);
|
||||||
|
jc.parse(fixedArgs);
|
||||||
applyFiles(argsObj);
|
applyFiles(argsObj);
|
||||||
return true;
|
return true;
|
||||||
} catch (ParameterException e) {
|
} catch (ParameterException e) {
|
||||||
System.err.println("Arguments parse error: " + e.getMessage());
|
System.err.println("Arguments parse error: " + e.getMessage());
|
||||||
printUsage();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,6 +95,41 @@ public class JCommanderWrapper {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Workaround to allow empty value (i.e. zero arity) for '--save-config' option
|
||||||
|
* Insert empty string arg if another option start right after this one, or it is a last one.
|
||||||
|
*/
|
||||||
|
private String[] fixArgsForEmptySaveConfig(String[] args) {
|
||||||
|
int len = args.length;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
String arg = args[i];
|
||||||
|
if (arg.equals("--save-config")) {
|
||||||
|
int next = i + 1;
|
||||||
|
if (next == len) {
|
||||||
|
return insertEmptyArg(args, next, true);
|
||||||
|
}
|
||||||
|
if (next < len) {
|
||||||
|
String nextArg = args[next];
|
||||||
|
if (nextArg.startsWith("-")) {
|
||||||
|
return insertEmptyArg(args, next, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] insertEmptyArg(String[] args, int i, boolean add) {
|
||||||
|
List<String> strings = new ArrayList<>(Arrays.asList(args));
|
||||||
|
if (add) {
|
||||||
|
strings.add("");
|
||||||
|
} else {
|
||||||
|
strings.add(i, "");
|
||||||
|
}
|
||||||
|
return strings.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
public void printUsage() {
|
public void printUsage() {
|
||||||
LogHelper.setLogLevel(LogHelper.LogLevelEnum.ERROR); // mute logger while printing help
|
LogHelper.setLogLevel(LogHelper.LogLevelEnum.ERROR); // mute logger while printing help
|
||||||
|
|
||||||
@@ -182,7 +216,9 @@ public class JCommanderWrapper {
|
|||||||
}
|
}
|
||||||
if (addDefaults) {
|
if (addDefaults) {
|
||||||
String defaultValue = getDefaultValue(args, f);
|
String defaultValue = getDefaultValue(args, f);
|
||||||
if (defaultValue != null && !description.contains("(default)")) {
|
if (defaultValue != null
|
||||||
|
&& !defaultValue.isEmpty()
|
||||||
|
&& !description.contains("(default)")) {
|
||||||
opt.append(", default: ").append(defaultValue);
|
opt.append(", default: ").append(defaultValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,19 +274,16 @@ public class JCommanderWrapper {
|
|||||||
|
|
||||||
private String appendPluginOptions(int maxNamesLen) {
|
private String appendPluginOptions(int maxNamesLen) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
int k = 1;
|
|
||||||
// load and init all options plugins to print all options
|
// load and init all options plugins to print all options
|
||||||
try (JadxDecompiler decompiler = new JadxDecompiler(argsObj.toJadxArgs())) {
|
try (JadxDecompiler decompiler = new JadxDecompiler(argsObj.toJadxArgs())) {
|
||||||
JadxPluginManager pluginManager = decompiler.getPluginManager();
|
JadxPluginManager pluginManager = decompiler.getPluginManager();
|
||||||
pluginManager.load(new JadxExternalPluginsLoader());
|
pluginManager.load(decompiler.getArgs().getPluginLoader());
|
||||||
pluginManager.initAll();
|
pluginManager.initAll();
|
||||||
try {
|
try {
|
||||||
for (PluginContext context : pluginManager.getAllPluginContexts()) {
|
for (PluginContext context : pluginManager.getAllPluginContexts()) {
|
||||||
JadxPluginOptions options = context.getOptions();
|
JadxPluginOptions options = context.getOptions();
|
||||||
if (options != null) {
|
if (options != null) {
|
||||||
if (appendPlugin(context.getPluginInfo(), context.getOptions(), sb, maxNamesLen)) {
|
appendPlugin(context.getPluginInfo(), context.getOptions(), sb, maxNamesLen);
|
||||||
k++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import jadx.api.JadxDecompiler;
|
|||||||
import jadx.api.impl.AnnotatedCodeWriter;
|
import jadx.api.impl.AnnotatedCodeWriter;
|
||||||
import jadx.api.impl.NoOpCodeCache;
|
import jadx.api.impl.NoOpCodeCache;
|
||||||
import jadx.api.impl.SimpleCodeWriter;
|
import jadx.api.impl.SimpleCodeWriter;
|
||||||
|
import jadx.api.usage.impl.EmptyUsageInfoCache;
|
||||||
import jadx.cli.LogHelper.LogLevelEnum;
|
import jadx.cli.LogHelper.LogLevelEnum;
|
||||||
|
import jadx.cli.config.JadxConfigAdapter;
|
||||||
import jadx.cli.plugins.JadxFilesGetter;
|
import jadx.cli.plugins.JadxFilesGetter;
|
||||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
import jadx.plugins.tools.JadxExternalPluginsLoader;
|
import jadx.plugins.tools.JadxExternalPluginsLoader;
|
||||||
@@ -34,15 +36,17 @@ public class JadxCLI {
|
|||||||
|
|
||||||
public static int execute(String[] args, @Nullable Consumer<JadxArgs> argsMod) {
|
public static int execute(String[] args, @Nullable Consumer<JadxArgs> argsMod) {
|
||||||
try {
|
try {
|
||||||
JadxCLIArgs cliArgs = new JadxCLIArgs();
|
JadxCLIArgs cliArgs = JadxCLIArgs.processArgs(args,
|
||||||
if (cliArgs.processArgs(args)) {
|
new JadxCLIArgs(),
|
||||||
JadxArgs jadxArgs = buildArgs(cliArgs);
|
new JadxConfigAdapter<>(JadxCLIArgs.class, "cli"));
|
||||||
if (argsMod != null) {
|
if (cliArgs == null) {
|
||||||
argsMod.accept(jadxArgs);
|
return 0;
|
||||||
}
|
|
||||||
return runSave(jadxArgs, cliArgs);
|
|
||||||
}
|
}
|
||||||
return 0;
|
JadxArgs jadxArgs = buildArgs(cliArgs);
|
||||||
|
if (argsMod != null) {
|
||||||
|
argsMod.accept(jadxArgs);
|
||||||
|
}
|
||||||
|
return runSave(jadxArgs, cliArgs);
|
||||||
} catch (JadxArgsValidateException e) {
|
} catch (JadxArgsValidateException e) {
|
||||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||||
return 1;
|
return 1;
|
||||||
@@ -53,10 +57,9 @@ public class JadxCLI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static JadxArgs buildArgs(JadxCLIArgs cliArgs) {
|
private static JadxArgs buildArgs(JadxCLIArgs cliArgs) {
|
||||||
LogHelper.initLogLevel(cliArgs);
|
|
||||||
LogHelper.setLogLevelsForLoadingStage();
|
|
||||||
JadxArgs jadxArgs = cliArgs.toJadxArgs();
|
JadxArgs jadxArgs = cliArgs.toJadxArgs();
|
||||||
jadxArgs.setCodeCache(new NoOpCodeCache());
|
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||||
|
jadxArgs.setUsageInfoCache(new EmptyUsageInfoCache());
|
||||||
jadxArgs.setPluginLoader(new JadxExternalPluginsLoader());
|
jadxArgs.setPluginLoader(new JadxExternalPluginsLoader());
|
||||||
jadxArgs.setFilesGetter(JadxFilesGetter.INSTANCE);
|
jadxArgs.setFilesGetter(JadxFilesGetter.INSTANCE);
|
||||||
initCodeWriterProvider(jadxArgs);
|
initCodeWriterProvider(jadxArgs);
|
||||||
@@ -68,9 +71,8 @@ public class JadxCLI {
|
|||||||
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
||||||
jadx.load();
|
jadx.load();
|
||||||
if (checkForErrors(jadx)) {
|
if (checkForErrors(jadx)) {
|
||||||
return 1;
|
return 2;
|
||||||
}
|
}
|
||||||
LogHelper.setLogLevelsForDecompileStage();
|
|
||||||
if (!SingleClassMode.process(jadx, cliArgs)) {
|
if (!SingleClassMode.process(jadx, cliArgs)) {
|
||||||
save(jadx);
|
save(jadx);
|
||||||
}
|
}
|
||||||
@@ -78,7 +80,7 @@ public class JadxCLI {
|
|||||||
if (errorsCount != 0) {
|
if (errorsCount != 0) {
|
||||||
jadx.printErrorsReport();
|
jadx.printErrorsReport();
|
||||||
LOG.error("finished with errors, count: {}", errorsCount);
|
LOG.error("finished with errors, count: {}", errorsCount);
|
||||||
return 1;
|
return 3;
|
||||||
}
|
}
|
||||||
LOG.info("done");
|
LOG.info("done");
|
||||||
return 0;
|
return 0;
|
||||||
@@ -108,10 +110,10 @@ public class JadxCLI {
|
|||||||
jadx.getArgs().setSkipSources(true);
|
jadx.getArgs().setSkipSources(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (jadx.getErrorsCount() > 0) {
|
int errorsCount = jadx.getErrorsCount();
|
||||||
LOG.error("Load with errors! Check log for details");
|
if (errorsCount > 0) {
|
||||||
|
LOG.error("Loading finished with errors! Count: {}", errorsCount);
|
||||||
// continue processing
|
// continue processing
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import java.util.stream.Collectors;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.beust.jcommander.DynamicParameter;
|
import com.beust.jcommander.DynamicParameter;
|
||||||
import com.beust.jcommander.IStringConverter;
|
import com.beust.jcommander.IStringConverter;
|
||||||
@@ -31,22 +33,33 @@ import jadx.api.args.IntegerFormat;
|
|||||||
import jadx.api.args.ResourceNameSource;
|
import jadx.api.args.ResourceNameSource;
|
||||||
import jadx.api.args.UseSourceNameAsClassNameAlias;
|
import jadx.api.args.UseSourceNameAsClassNameAlias;
|
||||||
import jadx.api.args.UserRenamesMappingsMode;
|
import jadx.api.args.UserRenamesMappingsMode;
|
||||||
|
import jadx.cli.config.IJadxConfig;
|
||||||
|
import jadx.cli.config.JadxConfigAdapter;
|
||||||
|
import jadx.cli.config.JadxConfigExclude;
|
||||||
|
import jadx.commons.app.JadxCommonFiles;
|
||||||
|
import jadx.commons.app.JadxTempFiles;
|
||||||
import jadx.core.deobf.conditions.DeobfWhitelist;
|
import jadx.core.deobf.conditions.DeobfWhitelist;
|
||||||
import jadx.core.export.ExportGradleType;
|
import jadx.core.export.ExportGradleType;
|
||||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
public class JadxCLIArgs {
|
public class JadxCLIArgs implements IJadxConfig {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(JadxCLIArgs.class);
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .apkm, .jadx.kts)")
|
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .apkm, .jadx.kts)")
|
||||||
protected List<String> files = Collections.emptyList();
|
protected List<String> files = Collections.emptyList();
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
||||||
protected String outDir;
|
protected String outDir;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "-ds", "--output-dir-src" }, description = "output directory for sources")
|
@Parameter(names = { "-ds", "--output-dir-src" }, description = "output directory for sources")
|
||||||
protected String outDirSrc;
|
protected String outDirSrc;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "-dr", "--output-dir-res" }, description = "output directory for resources")
|
@Parameter(names = { "-dr", "--output-dir-res" }, description = "output directory for resources")
|
||||||
protected String outDirRes;
|
protected String outDirRes;
|
||||||
|
|
||||||
@@ -59,9 +72,11 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
||||||
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
|
@Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
|
||||||
protected String singleClass = null;
|
protected String singleClass = null;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "--single-class-output" }, description = "file or dir for write if decompile a single class")
|
@Parameter(names = { "--single-class-output" }, description = "file or dir for write if decompile a single class")
|
||||||
protected String singleClassOutput = null;
|
protected String singleClassOutput = null;
|
||||||
|
|
||||||
@@ -166,6 +181,7 @@ public class JadxCLIArgs {
|
|||||||
)
|
)
|
||||||
protected String deobfuscationWhitelistStr = DeobfWhitelist.DEFAULT_STR;
|
protected String deobfuscationWhitelistStr = DeobfWhitelist.DEFAULT_STR;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(
|
@Parameter(
|
||||||
names = { "--deobf-cfg-file" },
|
names = { "--deobf-cfg-file" },
|
||||||
description = "deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format),"
|
description = "deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format),"
|
||||||
@@ -241,7 +257,7 @@ public class JadxCLIArgs {
|
|||||||
+ "\n 'printable' - remove non-printable chars from identifiers,"
|
+ "\n 'printable' - remove non-printable chars from identifiers,"
|
||||||
+ "\nor single 'none' - to disable all renames"
|
+ "\nor single 'none' - to disable all renames"
|
||||||
+ "\nor single 'all' - to enable all (default)",
|
+ "\nor single 'all' - to enable all (default)",
|
||||||
converter = RenameConverter.class
|
listConverter = RenameConverter.class
|
||||||
)
|
)
|
||||||
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
|
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
|
||||||
|
|
||||||
@@ -255,6 +271,9 @@ public class JadxCLIArgs {
|
|||||||
)
|
)
|
||||||
protected IntegerFormat integerFormat = IntegerFormat.AUTO;
|
protected IntegerFormat integerFormat = IntegerFormat.AUTO;
|
||||||
|
|
||||||
|
@Parameter(names = { "--type-update-limit" }, description = "type update limit count (per one instruction)")
|
||||||
|
protected int typeUpdatesLimitCount = 10;
|
||||||
|
|
||||||
@Parameter(names = { "--fs-case-sensitive" }, description = "treat filesystem as case sensitive, false by default")
|
@Parameter(names = { "--fs-case-sensitive" }, description = "treat filesystem as case sensitive, false by default")
|
||||||
protected boolean fsCaseSensitive = false;
|
protected boolean fsCaseSensitive = false;
|
||||||
|
|
||||||
@@ -284,27 +303,110 @@ public class JadxCLIArgs {
|
|||||||
)
|
)
|
||||||
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
|
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)")
|
@Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)")
|
||||||
protected boolean verbose = false;
|
protected boolean verbose = false;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
|
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
|
||||||
protected boolean quiet = false;
|
protected boolean quiet = false;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "--disable-plugins" }, description = "comma separated list of plugin ids to disable")
|
@Parameter(names = { "--disable-plugins" }, description = "comma separated list of plugin ids to disable")
|
||||||
protected String disablePlugins = "";
|
protected String disablePlugins = "";
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
|
@Parameter(
|
||||||
|
names = { "--config" },
|
||||||
|
defaultValueDescription = "<config-ref>",
|
||||||
|
description = "load configuration from file, <config-ref> can be:"
|
||||||
|
+ "\n path to '.json' file"
|
||||||
|
+ "\n short name - uses file with this name from config directory"
|
||||||
|
+ "\n 'none' - to disable config loading"
|
||||||
|
)
|
||||||
|
protected String config = "";
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
|
@Parameter(
|
||||||
|
names = { "--save-config" },
|
||||||
|
defaultValueDescription = "<config-ref>",
|
||||||
|
description = "save current options into configuration file and exit, <config-ref> can be:"
|
||||||
|
+ "\n empty - for default config"
|
||||||
|
+ "\n path to '.json' file"
|
||||||
|
+ "\n short name - file will be saved in config directory"
|
||||||
|
)
|
||||||
|
protected String saveConfig = null;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
|
@Parameter(names = { "--print-files" }, description = "print files and directories used by jadx (config, cache, temp)")
|
||||||
|
protected boolean printFiles = false;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "--version" }, description = "print jadx version")
|
@Parameter(names = { "--version" }, description = "print jadx version")
|
||||||
protected boolean printVersion = false;
|
protected boolean printVersion = false;
|
||||||
|
|
||||||
|
@JadxConfigExclude
|
||||||
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
||||||
protected boolean printHelp = false;
|
protected boolean printHelp = false;
|
||||||
|
|
||||||
@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
|
@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
|
||||||
protected Map<String, String> pluginOptions = new HashMap<>();
|
protected Map<String, String> pluginOptions = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obsolete method without config support,
|
||||||
|
* prefer {@link #processArgs(String[], JadxCLIArgs, JadxConfigAdapter)}
|
||||||
|
*/
|
||||||
public boolean processArgs(String[] args) {
|
public boolean processArgs(String[] args) {
|
||||||
JCommanderWrapper jcw = new JCommanderWrapper(this);
|
return processArgs(args, this, null) != null;
|
||||||
return jcw.parse(args) && process(jcw);
|
}
|
||||||
|
|
||||||
|
public static <T extends JadxCLIArgs> @Nullable T processArgs(
|
||||||
|
String[] args, T argsObj, @Nullable JadxConfigAdapter<T> configAdapter) {
|
||||||
|
JCommanderWrapper jcw = new JCommanderWrapper(argsObj);
|
||||||
|
if (!jcw.parse(args)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
applyArgs(argsObj);
|
||||||
|
|
||||||
|
// process commands and early exit flags
|
||||||
|
if (!argsObj.process(jcw)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (configAdapter != null) {
|
||||||
|
if (argsObj.printFiles) {
|
||||||
|
printFilesAndDirs(configAdapter.getDefaultConfigFileName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!argsObj.config.equalsIgnoreCase("none")) {
|
||||||
|
// load config file and merge with command line args
|
||||||
|
try {
|
||||||
|
configAdapter.useConfigRef(argsObj.config);
|
||||||
|
T configObj = configAdapter.load();
|
||||||
|
if (configObj != null) {
|
||||||
|
jcw.overrideProvided(configObj);
|
||||||
|
argsObj = configObj;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Config load failed, continue with default values", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// verify result object
|
||||||
|
argsObj.verify();
|
||||||
|
applyArgs(argsObj);
|
||||||
|
|
||||||
|
// save config if requested
|
||||||
|
if (argsObj.saveConfig != null) {
|
||||||
|
saveConfig(argsObj, configAdapter);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return argsObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends JadxCLIArgs> void applyArgs(T argsObj) {
|
||||||
|
// apply log levels
|
||||||
|
LogHelper.initLogLevel(argsObj);
|
||||||
|
LogHelper.applyLogLevels();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean process(JCommanderWrapper jcw) {
|
public boolean process(JCommanderWrapper jcw) {
|
||||||
@@ -319,9 +421,7 @@ public class JadxCLIArgs {
|
|||||||
System.out.println(JadxDecompiler.getVersion());
|
System.out.println(JadxDecompiler.getVersion());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (threadsCount <= 0) {
|
// unknown options added to 'files', run checks
|
||||||
throw new JadxArgsValidateException("Threads count must be positive, got: " + threadsCount);
|
|
||||||
}
|
|
||||||
for (String fileName : files) {
|
for (String fileName : files) {
|
||||||
if (fileName.startsWith("-")) {
|
if (fileName.startsWith("-")) {
|
||||||
throw new JadxArgsValidateException("Unknown option: " + fileName);
|
throw new JadxArgsValidateException("Unknown option: " + fileName);
|
||||||
@@ -330,6 +430,29 @@ public class JadxCLIArgs {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void printFilesAndDirs(String defaultConfigFileName) {
|
||||||
|
System.out.println("Files and directories used by jadx:");
|
||||||
|
System.out.println(" - default config file: " + JadxCommonFiles.getConfigDir().resolve(defaultConfigFileName).toAbsolutePath());
|
||||||
|
System.out.println(" - config directory: " + JadxCommonFiles.getConfigDir().toAbsolutePath());
|
||||||
|
System.out.println(" - cache directory: " + JadxCommonFiles.getCacheDir().toAbsolutePath());
|
||||||
|
System.out.println(" - temp directory: " + JadxTempFiles.getTempRootDir().getParent().toAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void verify() {
|
||||||
|
if (threadsCount <= 0) {
|
||||||
|
throw new JadxArgsValidateException("Threads count must be positive, got: " + threadsCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends JadxCLIArgs> void saveConfig(T argsObj, @Nullable JadxConfigAdapter<T> configAdapter) {
|
||||||
|
if (configAdapter == null) {
|
||||||
|
throw new JadxRuntimeException("Config adapter set to null, can't save config");
|
||||||
|
}
|
||||||
|
configAdapter.useConfigRef(argsObj.saveConfig);
|
||||||
|
configAdapter.save(argsObj);
|
||||||
|
System.out.println("Config saved to " + configAdapter.getConfigPath().toAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
public JadxArgs toJadxArgs() {
|
public JadxArgs toJadxArgs() {
|
||||||
JadxArgs args = new JadxArgs();
|
JadxArgs args = new JadxArgs();
|
||||||
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
|
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
|
||||||
@@ -380,16 +503,23 @@ public class JadxCLIArgs {
|
|||||||
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
|
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
|
||||||
args.setExtractFinally(extractFinally);
|
args.setExtractFinally(extractFinally);
|
||||||
args.setRestoreSwitchOverString(restoreSwitchOverString);
|
args.setRestoreSwitchOverString(restoreSwitchOverString);
|
||||||
args.setRenameFlags(renameFlags);
|
args.setRenameFlags(buildEnumSetForRenameFlags());
|
||||||
args.setFsCaseSensitive(fsCaseSensitive);
|
args.setFsCaseSensitive(fsCaseSensitive);
|
||||||
args.setCommentsLevel(commentsLevel);
|
args.setCommentsLevel(commentsLevel);
|
||||||
args.setIntegerFormat(integerFormat);
|
args.setIntegerFormat(integerFormat);
|
||||||
|
args.setTypeUpdatesLimitCount(typeUpdatesLimitCount);
|
||||||
args.setUseDxInput(useDx);
|
args.setUseDxInput(useDx);
|
||||||
args.setPluginOptions(pluginOptions);
|
args.setPluginOptions(pluginOptions);
|
||||||
args.setDisabledPlugins(Arrays.stream(disablePlugins.split(",")).map(String::trim).collect(Collectors.toSet()));
|
args.setDisabledPlugins(Arrays.stream(disablePlugins.split(",")).map(String::trim).collect(Collectors.toSet()));
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EnumSet<RenameEnum> buildEnumSetForRenameFlags() {
|
||||||
|
EnumSet<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
|
||||||
|
set.addAll(renameFlags);
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
public List<String> getFiles() {
|
public List<String> getFiles() {
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
@@ -422,14 +552,26 @@ public class JadxCLIArgs {
|
|||||||
return skipResources;
|
return skipResources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSkipResources(boolean skipResources) {
|
||||||
|
this.skipResources = skipResources;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSkipSources() {
|
public boolean isSkipSources() {
|
||||||
return skipSources;
|
return skipSources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSkipSources(boolean skipSources) {
|
||||||
|
this.skipSources = skipSources;
|
||||||
|
}
|
||||||
|
|
||||||
public int getThreadsCount() {
|
public int getThreadsCount() {
|
||||||
return threadsCount;
|
return threadsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setThreadsCount(int threadsCount) {
|
||||||
|
this.threadsCount = threadsCount;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isFallbackMode() {
|
public boolean isFallbackMode() {
|
||||||
return fallbackMode;
|
return fallbackMode;
|
||||||
}
|
}
|
||||||
@@ -438,82 +580,170 @@ public class JadxCLIArgs {
|
|||||||
return useDx;
|
return useDx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUseDx(boolean useDx) {
|
||||||
|
this.useDx = useDx;
|
||||||
|
}
|
||||||
|
|
||||||
public DecompilationMode getDecompilationMode() {
|
public DecompilationMode getDecompilationMode() {
|
||||||
return decompilationMode;
|
return decompilationMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDecompilationMode(DecompilationMode decompilationMode) {
|
||||||
|
this.decompilationMode = decompilationMode;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isShowInconsistentCode() {
|
public boolean isShowInconsistentCode() {
|
||||||
return showInconsistentCode;
|
return showInconsistentCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setShowInconsistentCode(boolean showInconsistentCode) {
|
||||||
|
this.showInconsistentCode = showInconsistentCode;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isUseImports() {
|
public boolean isUseImports() {
|
||||||
return useImports;
|
return useImports;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUseImports(boolean useImports) {
|
||||||
|
this.useImports = useImports;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDebugInfo() {
|
public boolean isDebugInfo() {
|
||||||
return debugInfo;
|
return debugInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDebugInfo(boolean debugInfo) {
|
||||||
|
this.debugInfo = debugInfo;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isAddDebugLines() {
|
public boolean isAddDebugLines() {
|
||||||
return addDebugLines;
|
return addDebugLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAddDebugLines(boolean addDebugLines) {
|
||||||
|
this.addDebugLines = addDebugLines;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isInlineAnonymousClasses() {
|
public boolean isInlineAnonymousClasses() {
|
||||||
return inlineAnonymousClasses;
|
return inlineAnonymousClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setInlineAnonymousClasses(boolean inlineAnonymousClasses) {
|
||||||
|
this.inlineAnonymousClasses = inlineAnonymousClasses;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isInlineMethods() {
|
public boolean isInlineMethods() {
|
||||||
return inlineMethods;
|
return inlineMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setInlineMethods(boolean inlineMethods) {
|
||||||
|
this.inlineMethods = inlineMethods;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isMoveInnerClasses() {
|
public boolean isMoveInnerClasses() {
|
||||||
return moveInnerClasses;
|
return moveInnerClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMoveInnerClasses(boolean moveInnerClasses) {
|
||||||
|
this.moveInnerClasses = moveInnerClasses;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isAllowInlineKotlinLambda() {
|
public boolean isAllowInlineKotlinLambda() {
|
||||||
return allowInlineKotlinLambda;
|
return allowInlineKotlinLambda;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAllowInlineKotlinLambda(boolean allowInlineKotlinLambda) {
|
||||||
|
this.allowInlineKotlinLambda = allowInlineKotlinLambda;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isExtractFinally() {
|
public boolean isExtractFinally() {
|
||||||
return extractFinally;
|
return extractFinally;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setExtractFinally(boolean extractFinally) {
|
||||||
|
this.extractFinally = extractFinally;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isRestoreSwitchOverString() {
|
public boolean isRestoreSwitchOverString() {
|
||||||
return restoreSwitchOverString;
|
return restoreSwitchOverString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRestoreSwitchOverString(boolean restoreSwitchOverString) {
|
||||||
|
this.restoreSwitchOverString = restoreSwitchOverString;
|
||||||
|
}
|
||||||
|
|
||||||
public Path getUserRenamesMappingsPath() {
|
public Path getUserRenamesMappingsPath() {
|
||||||
return userRenamesMappingsPath;
|
return userRenamesMappingsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUserRenamesMappingsPath(Path userRenamesMappingsPath) {
|
||||||
|
this.userRenamesMappingsPath = userRenamesMappingsPath;
|
||||||
|
}
|
||||||
|
|
||||||
public UserRenamesMappingsMode getUserRenamesMappingsMode() {
|
public UserRenamesMappingsMode getUserRenamesMappingsMode() {
|
||||||
return userRenamesMappingsMode;
|
return userRenamesMappingsMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUserRenamesMappingsMode(UserRenamesMappingsMode userRenamesMappingsMode) {
|
||||||
|
this.userRenamesMappingsMode = userRenamesMappingsMode;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDeobfuscationOn() {
|
public boolean isDeobfuscationOn() {
|
||||||
return deobfuscationOn;
|
return deobfuscationOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDeobfuscationOn(boolean deobfuscationOn) {
|
||||||
|
this.deobfuscationOn = deobfuscationOn;
|
||||||
|
}
|
||||||
|
|
||||||
public int getDeobfuscationMinLength() {
|
public int getDeobfuscationMinLength() {
|
||||||
return deobfuscationMinLength;
|
return deobfuscationMinLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDeobfuscationMinLength(int deobfuscationMinLength) {
|
||||||
|
this.deobfuscationMinLength = deobfuscationMinLength;
|
||||||
|
}
|
||||||
|
|
||||||
public int getDeobfuscationMaxLength() {
|
public int getDeobfuscationMaxLength() {
|
||||||
return deobfuscationMaxLength;
|
return deobfuscationMaxLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDeobfuscationMaxLength(int deobfuscationMaxLength) {
|
||||||
|
this.deobfuscationMaxLength = deobfuscationMaxLength;
|
||||||
|
}
|
||||||
|
|
||||||
public String getDeobfuscationWhitelistStr() {
|
public String getDeobfuscationWhitelistStr() {
|
||||||
return deobfuscationWhitelistStr;
|
return deobfuscationWhitelistStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDeobfuscationWhitelistStr(String deobfuscationWhitelistStr) {
|
||||||
|
this.deobfuscationWhitelistStr = deobfuscationWhitelistStr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getGeneratedRenamesMappingFile() {
|
public String getGeneratedRenamesMappingFile() {
|
||||||
return generatedRenamesMappingFile;
|
return generatedRenamesMappingFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setGeneratedRenamesMappingFile(String generatedRenamesMappingFile) {
|
||||||
|
this.generatedRenamesMappingFile = generatedRenamesMappingFile;
|
||||||
|
}
|
||||||
|
|
||||||
public GeneratedRenamesMappingFileMode getGeneratedRenamesMappingFileMode() {
|
public GeneratedRenamesMappingFileMode getGeneratedRenamesMappingFileMode() {
|
||||||
return generatedRenamesMappingFileMode;
|
return generatedRenamesMappingFileMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode) {
|
||||||
|
this.generatedRenamesMappingFileMode = generatedRenamesMappingFileMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSourceNameRepeatLimit() {
|
||||||
|
return sourceNameRepeatLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceNameRepeatLimit(int sourceNameRepeatLimit) {
|
||||||
|
this.sourceNameRepeatLimit = sourceNameRepeatLimit;
|
||||||
|
}
|
||||||
|
|
||||||
public UseSourceNameAsClassNameAlias getUseSourceNameAsClassNameAlias() {
|
public UseSourceNameAsClassNameAlias getUseSourceNameAsClassNameAlias() {
|
||||||
if (useSourceNameAsClassNameAlias != null) {
|
if (useSourceNameAsClassNameAlias != null) {
|
||||||
return useSourceNameAsClassNameAlias;
|
return useSourceNameAsClassNameAlias;
|
||||||
@@ -525,8 +755,8 @@ public class JadxCLIArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSourceNameRepeatLimit() {
|
public void setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias) {
|
||||||
return sourceNameRepeatLimit;
|
this.useSourceNameAsClassNameAlias = useSourceNameAsClassNameAlias;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -537,46 +767,98 @@ public class JadxCLIArgs {
|
|||||||
return getUseSourceNameAsClassNameAlias().toBoolean();
|
return getUseSourceNameAsClassNameAlias().toBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDeobfuscationUseSourceNameAsAlias(Boolean deobfuscationUseSourceNameAsAlias) {
|
||||||
|
this.deobfuscationUseSourceNameAsAlias = deobfuscationUseSourceNameAsAlias;
|
||||||
|
}
|
||||||
|
|
||||||
public ResourceNameSource getResourceNameSource() {
|
public ResourceNameSource getResourceNameSource() {
|
||||||
return resourceNameSource;
|
return resourceNameSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setResourceNameSource(ResourceNameSource resourceNameSource) {
|
||||||
|
this.resourceNameSource = resourceNameSource;
|
||||||
|
}
|
||||||
|
|
||||||
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
||||||
return useKotlinMethodsForVarNames;
|
return useKotlinMethodsForVarNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUseKotlinMethodsForVarNames(UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
|
||||||
|
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
|
||||||
|
}
|
||||||
|
|
||||||
public IntegerFormat getIntegerFormat() {
|
public IntegerFormat getIntegerFormat() {
|
||||||
return integerFormat;
|
return integerFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setIntegerFormat(IntegerFormat integerFormat) {
|
||||||
|
this.integerFormat = integerFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTypeUpdatesLimitCount() {
|
||||||
|
return typeUpdatesLimitCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTypeUpdatesLimitCount(int typeUpdatesLimitCount) {
|
||||||
|
this.typeUpdatesLimitCount = typeUpdatesLimitCount;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEscapeUnicode() {
|
public boolean isEscapeUnicode() {
|
||||||
return escapeUnicode;
|
return escapeUnicode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setEscapeUnicode(boolean escapeUnicode) {
|
||||||
|
this.escapeUnicode = escapeUnicode;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isCfgOutput() {
|
public boolean isCfgOutput() {
|
||||||
return cfgOutput;
|
return cfgOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCfgOutput(boolean cfgOutput) {
|
||||||
|
this.cfgOutput = cfgOutput;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isRawCfgOutput() {
|
public boolean isRawCfgOutput() {
|
||||||
return rawCfgOutput;
|
return rawCfgOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRawCfgOutput(boolean rawCfgOutput) {
|
||||||
|
this.rawCfgOutput = rawCfgOutput;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isReplaceConsts() {
|
public boolean isReplaceConsts() {
|
||||||
return replaceConsts;
|
return replaceConsts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setReplaceConsts(boolean replaceConsts) {
|
||||||
|
this.replaceConsts = replaceConsts;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isRespectBytecodeAccessModifiers() {
|
public boolean isRespectBytecodeAccessModifiers() {
|
||||||
return respectBytecodeAccessModifiers;
|
return respectBytecodeAccessModifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRespectBytecodeAccessModifiers(boolean respectBytecodeAccessModifiers) {
|
||||||
|
this.respectBytecodeAccessModifiers = respectBytecodeAccessModifiers;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isExportAsGradleProject() {
|
public boolean isExportAsGradleProject() {
|
||||||
return exportAsGradleProject;
|
return exportAsGradleProject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setExportAsGradleProject(boolean exportAsGradleProject) {
|
||||||
|
this.exportAsGradleProject = exportAsGradleProject;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSkipXmlPrettyPrint() {
|
public boolean isSkipXmlPrettyPrint() {
|
||||||
return skipXmlPrettyPrint;
|
return skipXmlPrettyPrint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSkipXmlPrettyPrint(boolean skipXmlPrettyPrint) {
|
||||||
|
this.skipXmlPrettyPrint = skipXmlPrettyPrint;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isRenameCaseSensitive() {
|
public boolean isRenameCaseSensitive() {
|
||||||
return renameFlags.contains(RenameEnum.CASE);
|
return renameFlags.contains(RenameEnum.CASE);
|
||||||
}
|
}
|
||||||
@@ -593,26 +875,70 @@ public class JadxCLIArgs {
|
|||||||
return fsCaseSensitive;
|
return fsCaseSensitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFsCaseSensitive(boolean fsCaseSensitive) {
|
||||||
|
this.fsCaseSensitive = fsCaseSensitive;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isUseHeadersForDetectResourceExtensions() {
|
public boolean isUseHeadersForDetectResourceExtensions() {
|
||||||
return useHeadersForDetectResourceExtensions;
|
return useHeadersForDetectResourceExtensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUseHeadersForDetectResourceExtensions(boolean useHeadersForDetectResourceExtensions) {
|
||||||
|
this.useHeadersForDetectResourceExtensions = useHeadersForDetectResourceExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
public CommentsLevel getCommentsLevel() {
|
public CommentsLevel getCommentsLevel() {
|
||||||
return commentsLevel;
|
return commentsLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCommentsLevel(CommentsLevel commentsLevel) {
|
||||||
|
this.commentsLevel = commentsLevel;
|
||||||
|
}
|
||||||
|
|
||||||
public LogHelper.LogLevelEnum getLogLevel() {
|
public LogHelper.LogLevelEnum getLogLevel() {
|
||||||
return logLevel;
|
return logLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setLogLevel(LogHelper.LogLevelEnum logLevel) {
|
||||||
|
this.logLevel = logLevel;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getPluginOptions() {
|
public Map<String, String> getPluginOptions() {
|
||||||
return pluginOptions;
|
return pluginOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPluginOptions(Map<String, String> pluginOptions) {
|
||||||
|
this.pluginOptions = pluginOptions;
|
||||||
|
}
|
||||||
|
|
||||||
public String getDisablePlugins() {
|
public String getDisablePlugins() {
|
||||||
return disablePlugins;
|
return disablePlugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDisablePlugins(String disablePlugins) {
|
||||||
|
this.disablePlugins = disablePlugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExportGradleType(@Nullable ExportGradleType exportGradleType) {
|
||||||
|
this.exportGradleType = exportGradleType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputFormat(String outputFormat) {
|
||||||
|
this.outputFormat = outputFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<RenameEnum> getRenameFlags() {
|
||||||
|
return renameFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRenameFlags(Set<RenameEnum> renameFlags) {
|
||||||
|
this.renameFlags = renameFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
||||||
private final String paramName;
|
private final String paramName;
|
||||||
|
|
||||||
|
|||||||
@@ -43,10 +43,9 @@ public class LogHelper {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (args.quiet) {
|
if (args.quiet) {
|
||||||
return LogLevelEnum.QUIET;
|
args.logLevel = LogLevelEnum.QUIET;
|
||||||
}
|
} else if (args.verbose) {
|
||||||
if (args.verbose) {
|
args.logLevel = LogLevelEnum.DEBUG;
|
||||||
return LogLevelEnum.DEBUG;
|
|
||||||
}
|
}
|
||||||
return args.logLevel;
|
return args.logLevel;
|
||||||
}
|
}
|
||||||
@@ -56,20 +55,7 @@ public class LogHelper {
|
|||||||
applyLogLevel(logLevelValue);
|
applyLogLevel(logLevelValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setLogLevelsForLoadingStage() {
|
public static void applyLogLevels() {
|
||||||
if (logLevelValue == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (logLevelValue == LogLevelEnum.PROGRESS) {
|
|
||||||
// show load errors
|
|
||||||
LogHelper.applyLogLevel(LogLevelEnum.ERROR);
|
|
||||||
fixForShowProgress();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
applyLogLevel(logLevelValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setLogLevelsForDecompileStage() {
|
|
||||||
if (logLevelValue == null) {
|
if (logLevelValue == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import jadx.cli.LogHelper;
|
|||||||
import jadx.core.utils.StringUtils;
|
import jadx.core.utils.StringUtils;
|
||||||
import jadx.plugins.tools.JadxPluginsList;
|
import jadx.plugins.tools.JadxPluginsList;
|
||||||
import jadx.plugins.tools.JadxPluginsTools;
|
import jadx.plugins.tools.JadxPluginsTools;
|
||||||
|
import jadx.plugins.tools.data.JadxPluginListEntry;
|
||||||
import jadx.plugins.tools.data.JadxPluginMetadata;
|
import jadx.plugins.tools.data.JadxPluginMetadata;
|
||||||
import jadx.plugins.tools.data.JadxPluginUpdate;
|
import jadx.plugins.tools.data.JadxPluginUpdate;
|
||||||
|
|
||||||
@@ -116,9 +117,9 @@ public class CommandPlugins implements ICommand {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (available) {
|
if (available) {
|
||||||
List<JadxPluginMetadata> availableList = JadxPluginsList.getInstance().get();
|
List<JadxPluginListEntry> availableList = JadxPluginsList.getInstance().get();
|
||||||
System.out.println("Available plugins: " + availableList.size());
|
System.out.println("Available plugins: " + availableList.size());
|
||||||
for (JadxPluginMetadata plugin : availableList) {
|
for (JadxPluginListEntry plugin : availableList) {
|
||||||
System.out.println(" - " + plugin.getName() + ": " + plugin.getDescription()
|
System.out.println(" - " + plugin.getName() + ": " + plugin.getDescription()
|
||||||
+ " (" + plugin.getLocationId() + ")");
|
+ " (" + plugin.getLocationId() + ")");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package jadx.cli.config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface for jadx config objects
|
||||||
|
*/
|
||||||
|
public interface IJadxConfig {
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package jadx.cli.config;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.ExclusionStrategy;
|
||||||
|
import com.google.gson.FieldAttributes;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.stream.JsonReader;
|
||||||
|
|
||||||
|
import jadx.commons.app.JadxCommonFiles;
|
||||||
|
import jadx.core.utils.GsonUtils;
|
||||||
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
public class JadxConfigAdapter<T extends IJadxConfig> {
|
||||||
|
private static final ExclusionStrategy GSON_EXCLUSION_STRATEGY = new ExclusionStrategy() {
|
||||||
|
@Override
|
||||||
|
public boolean shouldSkipField(FieldAttributes f) {
|
||||||
|
return f.getAnnotation(JadxConfigExclude.class) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldSkipClass(Class<?> clazz) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Class<T> configCls;
|
||||||
|
private final String defaultConfigFileName;
|
||||||
|
private final Gson gson;
|
||||||
|
|
||||||
|
private Path configPath;
|
||||||
|
|
||||||
|
public JadxConfigAdapter(Class<T> configCls, String defaultConfigName) {
|
||||||
|
this(configCls, defaultConfigName, gsonBuilder -> {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxConfigAdapter(Class<T> configCls, String defaultConfigName, Consumer<GsonBuilder> applyGsonOptions) {
|
||||||
|
this.configCls = configCls;
|
||||||
|
this.defaultConfigFileName = defaultConfigName + ".json";
|
||||||
|
GsonBuilder gsonBuilder = GsonUtils.defaultGsonBuilder();
|
||||||
|
gsonBuilder.setExclusionStrategies(GSON_EXCLUSION_STRATEGY);
|
||||||
|
applyGsonOptions.accept(gsonBuilder);
|
||||||
|
this.gson = gsonBuilder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useConfigRef(String configRef) {
|
||||||
|
this.configPath = resolveConfigRef(configRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getConfigPath() {
|
||||||
|
return configPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultConfigFileName() {
|
||||||
|
return defaultConfigFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable T load() {
|
||||||
|
if (!Files.isRegularFile(configPath)) {
|
||||||
|
// file not found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try (JsonReader reader = gson.newJsonReader(Files.newBufferedReader(configPath))) {
|
||||||
|
return gson.fromJson(reader, configCls);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Failed to load config file: " + configPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(T configObject) {
|
||||||
|
try {
|
||||||
|
String jsonStr = gson.toJson(configObject, configCls);
|
||||||
|
// don't use stream writer here because serialization errors will corrupt config
|
||||||
|
Files.writeString(configPath, jsonStr);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Failed to save config file: " + configPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String objectToJsonString(T configObject) {
|
||||||
|
return gson.toJson(configObject, configCls);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T jsonStringToObject(String jsonStr) {
|
||||||
|
return gson.fromJson(jsonStr, configCls);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path resolveConfigRef(String configRef) {
|
||||||
|
if (configRef == null || configRef.isEmpty()) {
|
||||||
|
// use default config file
|
||||||
|
return JadxCommonFiles.getConfigDir().resolve(defaultConfigFileName);
|
||||||
|
}
|
||||||
|
if (configRef.contains("/") || configRef.contains("\\")) {
|
||||||
|
if (!configRef.toLowerCase().endsWith(".json")) {
|
||||||
|
throw new JadxArgsValidateException("Config file extension should be '.json'");
|
||||||
|
}
|
||||||
|
return Path.of(configRef);
|
||||||
|
}
|
||||||
|
// treat as a short name
|
||||||
|
return JadxCommonFiles.getConfigDir().resolve(configRef + ".json");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package jadx.cli.config;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
public @interface JadxConfigExclude {
|
||||||
|
}
|
||||||
@@ -3,5 +3,5 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("io.get-coursier.util:directories-jni:0.1.3")
|
implementation("io.get-coursier.util:directories-jni:0.1.4")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,12 +81,15 @@ public class JadxCommonFiles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return JNI or Foreign implementation
|
* Return JNI, Foreign or PowerShell implementation
|
||||||
*/
|
*/
|
||||||
private static Windows getWinDirs() {
|
private static Windows getWinDirs() {
|
||||||
Windows defSup = Windows.getDefaultSupplier().get();
|
Windows defSup = Windows.getDefaultSupplier().get();
|
||||||
if (defSup instanceof WindowsPowerShell) {
|
if (defSup instanceof WindowsPowerShell) {
|
||||||
return new WindowsJni();
|
if (JadxSystemInfo.IS_AMD64) {
|
||||||
|
// JNI library compiled for x86-64
|
||||||
|
return new WindowsJni();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return defSup;
|
return defSup;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package jadx.commons.app;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class JadxSystemInfo {
|
||||||
|
public static final String JAVA_VM = System.getProperty("java.vm.name", "?");
|
||||||
|
public static final String JAVA_VER = System.getProperty("java.version", "?");
|
||||||
|
|
||||||
|
public static final String OS_NAME = System.getProperty("os.name", "?");
|
||||||
|
public static final String OS_ARCH = System.getProperty("os.arch", "?");
|
||||||
|
public static final String OS_VERSION = System.getProperty("os.version", "?");
|
||||||
|
|
||||||
|
private static final String OS_NAME_LOWER = OS_NAME.toLowerCase(Locale.ENGLISH);
|
||||||
|
public static final boolean IS_WINDOWS = OS_NAME_LOWER.startsWith("windows");
|
||||||
|
public static final boolean IS_MAC = OS_NAME_LOWER.startsWith("mac");
|
||||||
|
public static final boolean IS_LINUX = !IS_WINDOWS && !IS_MAC;
|
||||||
|
public static final boolean IS_UNIX = !IS_WINDOWS;
|
||||||
|
|
||||||
|
private static final String OS_ARCH_LOWER = OS_NAME.toLowerCase(Locale.ENGLISH);
|
||||||
|
public static final boolean IS_AMD64 = OS_ARCH_LOWER.equals("amd64");
|
||||||
|
public static final boolean IS_ARM64 = OS_ARCH_LOWER.equals("aarch64");
|
||||||
|
|
||||||
|
private JadxSystemInfo() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,9 @@ public class JadxTempFiles {
|
|||||||
String jadxTmpDir = System.getenv("JADX_TMP_DIR");
|
String jadxTmpDir = System.getenv("JADX_TMP_DIR");
|
||||||
Path dir;
|
Path dir;
|
||||||
if (jadxTmpDir != null) {
|
if (jadxTmpDir != null) {
|
||||||
dir = Files.createTempDirectory(Paths.get(jadxTmpDir), JADX_TMP_INSTANCE_PREFIX);
|
Path customTmpRootDir = Paths.get(jadxTmpDir);
|
||||||
|
Files.createDirectories(customTmpRootDir);
|
||||||
|
dir = Files.createTempDirectory(customTmpRootDir, JADX_TMP_INSTANCE_PREFIX);
|
||||||
} else {
|
} else {
|
||||||
dir = Files.createTempDirectory(JADX_TMP_INSTANCE_PREFIX);
|
dir = Files.createTempDirectory(JADX_TMP_INSTANCE_PREFIX);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,17 @@ package jadx.zip;
|
|||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class ZipContent implements Closeable {
|
public class ZipContent implements Closeable {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ZipContent.class);
|
||||||
|
|
||||||
private final IZipParser zipParser;
|
private final IZipParser zipParser;
|
||||||
private final List<IZipEntry> entries;
|
private final List<IZipEntry> entries;
|
||||||
private final Map<String, IZipEntry> entriesMap;
|
private final Map<String, IZipEntry> entriesMap;
|
||||||
@@ -17,7 +20,19 @@ public class ZipContent implements Closeable {
|
|||||||
public ZipContent(IZipParser zipParser, List<IZipEntry> entries) {
|
public ZipContent(IZipParser zipParser, List<IZipEntry> entries) {
|
||||||
this.zipParser = zipParser;
|
this.zipParser = zipParser;
|
||||||
this.entries = entries;
|
this.entries = entries;
|
||||||
this.entriesMap = entries.stream().collect(Collectors.toMap(IZipEntry::getName, Function.identity()));
|
this.entriesMap = buildNameMap(zipParser, entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, IZipEntry> buildNameMap(IZipParser zipParser, List<IZipEntry> entries) {
|
||||||
|
Map<String, IZipEntry> map = new HashMap<>(entries.size());
|
||||||
|
for (IZipEntry entry : entries) {
|
||||||
|
String name = entry.getName();
|
||||||
|
IZipEntry prevEntry = map.put(name, entry);
|
||||||
|
if (prevEntry != null) {
|
||||||
|
LOG.warn("Found duplicate entry: {} in {}", name, zipParser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<IZipEntry> getEntries() {
|
public List<IZipEntry> getEntries() {
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ final class ZipDeflate {
|
|||||||
buf.position(entry.getDataStart());
|
buf.position(entry.getDataStart());
|
||||||
ByteBuffer entryBuf = buf.slice();
|
ByteBuffer entryBuf = buf.slice();
|
||||||
entryBuf.limit((int) entry.getCompressedSize());
|
entryBuf.limit((int) entry.getCompressedSize());
|
||||||
|
if (entry.getUncompressedSize() > Integer.MAX_VALUE) {
|
||||||
|
throw new DataFormatException("Entry too large: " + entry.getUncompressedSize());
|
||||||
|
}
|
||||||
byte[] out = new byte[(int) entry.getUncompressedSize()];
|
byte[] out = new byte[(int) entry.getUncompressedSize()];
|
||||||
Inflater inflater = new Inflater(true);
|
Inflater inflater = new Inflater(true);
|
||||||
inflater.setInput(entryBuf);
|
inflater.setInput(entryBuf);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package jadx.zip.security;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -11,7 +13,7 @@ import jadx.zip.IZipEntry;
|
|||||||
public class JadxZipSecurity implements IJadxZipSecurity {
|
public class JadxZipSecurity implements IJadxZipSecurity {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JadxZipSecurity.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JadxZipSecurity.class);
|
||||||
|
|
||||||
private static final File CWD = getCWD();
|
private static final Path CWD = Paths.get(".").toAbsolutePath().normalize();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The size of uncompressed zip entry shouldn't be bigger of compressed in zipBombDetectionFactor
|
* The size of uncompressed zip entry shouldn't be bigger of compressed in zipBombDetectionFactor
|
||||||
@@ -56,14 +58,17 @@ public class JadxZipSecurity implements IJadxZipSecurity {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Path traversal check as presented on
|
||||||
|
// https://www.heise.de/en/background/Secure-Coding-Best-practices-for-using-Java-NIO-against-path-traversal-9996787.html
|
||||||
try {
|
try {
|
||||||
File currentPath = CWD;
|
Path entryPath = CWD.resolve(entryName).normalize();
|
||||||
File canonical = new File(currentPath, entryName).getCanonicalFile();
|
if (entryPath.startsWith(CWD)) {
|
||||||
if (isInSubDirectoryInternal(currentPath, canonical)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// check failed
|
// check failed
|
||||||
|
LOG.error("Invalid file name or path traversal attack detected: {} - error: {}", entryName, e.getMessage());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
LOG.error("Invalid file name or path traversal attack detected: {}", entryName);
|
LOG.error("Invalid file name or path traversal attack detected: {}", entryName);
|
||||||
return false;
|
return false;
|
||||||
@@ -121,12 +126,4 @@ public class JadxZipSecurity implements IJadxZipSecurity {
|
|||||||
this.useLimitedDataStream = useLimitedDataStream;
|
this.useLimitedDataStream = useLimitedDataStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getCWD() {
|
|
||||||
try {
|
|
||||||
return new File(".").getCanonicalFile();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Failed to init current working dir constant", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ dependencies {
|
|||||||
api(project(":jadx-plugins:jadx-input-api"))
|
api(project(":jadx-plugins:jadx-input-api"))
|
||||||
api(project(":jadx-commons:jadx-zip"))
|
api(project(":jadx-commons:jadx-zip"))
|
||||||
|
|
||||||
implementation("com.google.code.gson:gson:2.13.1")
|
implementation("com.google.code.gson:gson:2.13.2")
|
||||||
|
|
||||||
testImplementation("org.apache.commons:commons-lang3:3.17.0")
|
testImplementation("org.apache.commons:commons-lang3:3.20.0")
|
||||||
|
|
||||||
testImplementation(project(":jadx-plugins:jadx-dex-input"))
|
testImplementation(project(":jadx-plugins:jadx-dex-input"))
|
||||||
testRuntimeOnly(project(":jadx-plugins:jadx-smali-input"))
|
testRuntimeOnly(project(":jadx-plugins:jadx-smali-input"))
|
||||||
@@ -22,7 +22,7 @@ dependencies {
|
|||||||
strictly("[3.33, 3.34[") // from 3.34 compiled with Java 17
|
strictly("[3.33, 3.34[") // from 3.34 compiled with Java 17
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
testImplementation("tools.profiler:async-profiler:4.0")
|
testImplementation("tools.profiler:async-profiler:4.2")
|
||||||
}
|
}
|
||||||
|
|
||||||
val jadxTestJavaVersion = getTestJavaVersion()
|
val jadxTestJavaVersion = getTestJavaVersion()
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import java.io.Closeable;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -167,6 +166,12 @@ public class JadxArgs implements Closeable {
|
|||||||
|
|
||||||
private IntegerFormat integerFormat = IntegerFormat.AUTO;
|
private IntegerFormat integerFormat = IntegerFormat.AUTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum updates allowed total in method per one instruction.
|
||||||
|
* Should be more or equal 1, default value is 10.
|
||||||
|
*/
|
||||||
|
private int typeUpdatesLimitCount = 10;
|
||||||
|
|
||||||
private boolean useDxInput = false;
|
private boolean useDxInput = false;
|
||||||
|
|
||||||
public enum UseKotlinMethodsForVarNames {
|
public enum UseKotlinMethodsForVarNames {
|
||||||
@@ -196,6 +201,11 @@ public class JadxArgs implements Closeable {
|
|||||||
*/
|
*/
|
||||||
private boolean runDebugChecks = false;
|
private boolean runDebugChecks = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passes to exclude from processing.
|
||||||
|
*/
|
||||||
|
private final List<String> disabledPasses = new ArrayList<>();
|
||||||
|
|
||||||
private Map<String, String> pluginOptions = new HashMap<>();
|
private Map<String, String> pluginOptions = new HashMap<>();
|
||||||
|
|
||||||
private Set<String> disabledPlugins = new HashSet<>();
|
private Set<String> disabledPlugins = new HashSet<>();
|
||||||
@@ -239,8 +249,12 @@ public class JadxArgs implements Closeable {
|
|||||||
return inputFiles;
|
return inputFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addInputFile(File inputFile) {
|
||||||
|
this.inputFiles.add(inputFile);
|
||||||
|
}
|
||||||
|
|
||||||
public void setInputFile(File inputFile) {
|
public void setInputFile(File inputFile) {
|
||||||
this.inputFiles = Collections.singletonList(inputFile);
|
addInputFile(inputFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setInputFiles(List<File> inputFiles) {
|
public void setInputFiles(List<File> inputFiles) {
|
||||||
@@ -738,6 +752,14 @@ public class JadxArgs implements Closeable {
|
|||||||
this.integerFormat = format;
|
this.integerFormat = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getTypeUpdatesLimitCount() {
|
||||||
|
return typeUpdatesLimitCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTypeUpdatesLimitCount(int typeUpdatesLimitCount) {
|
||||||
|
this.typeUpdatesLimitCount = Math.max(1, typeUpdatesLimitCount);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isUseDxInput() {
|
public boolean isUseDxInput() {
|
||||||
return useDxInput;
|
return useDxInput;
|
||||||
}
|
}
|
||||||
@@ -786,6 +808,10 @@ public class JadxArgs implements Closeable {
|
|||||||
this.runDebugChecks = runDebugChecks;
|
this.runDebugChecks = runDebugChecks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getDisabledPasses() {
|
||||||
|
return disabledPasses;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getPluginOptions() {
|
public Map<String, String> getPluginOptions() {
|
||||||
return pluginOptions;
|
return pluginOptions;
|
||||||
}
|
}
|
||||||
@@ -839,7 +865,7 @@ public class JadxArgs implements Closeable {
|
|||||||
+ insertDebugLines + extractFinally
|
+ insertDebugLines + extractFinally
|
||||||
+ debugInfo + escapeUnicode + replaceConsts + restoreSwitchOverString
|
+ debugInfo + escapeUnicode + replaceConsts + restoreSwitchOverString
|
||||||
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
|
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
|
||||||
+ commentsLevel + useDxInput + integerFormat
|
+ commentsLevel + useDxInput + integerFormat + typeUpdatesLimitCount
|
||||||
+ "|" + buildPluginsHash(decompiler);
|
+ "|" + buildPluginsHash(decompiler);
|
||||||
return FileUtils.md5Sum(argStr);
|
return FileUtils.md5Sum(argStr);
|
||||||
}
|
}
|
||||||
@@ -898,6 +924,7 @@ public class JadxArgs implements Closeable {
|
|||||||
+ ", cfgOutput=" + cfgOutput
|
+ ", cfgOutput=" + cfgOutput
|
||||||
+ ", rawCFGOutput=" + rawCFGOutput
|
+ ", rawCFGOutput=" + rawCFGOutput
|
||||||
+ ", useHeadersForDetectResourceExtensions=" + useHeadersForDetectResourceExtensions
|
+ ", useHeadersForDetectResourceExtensions=" + useHeadersForDetectResourceExtensions
|
||||||
|
+ ", typeUpdatesLimitCount=" + typeUpdatesLimitCount
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,11 +138,16 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
loadFinished();
|
loadFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload passes and plugins without processing classes and inputs
|
||||||
|
*/
|
||||||
public void reloadPasses() {
|
public void reloadPasses() {
|
||||||
LOG.info("reloading (passes only) ...");
|
LOG.info("reloading (passes only) ...");
|
||||||
customPasses.clear();
|
customPasses.clear();
|
||||||
root.resetPasses();
|
root.resetPasses();
|
||||||
events.reset();
|
events.reset();
|
||||||
|
unloadPlugins();
|
||||||
|
|
||||||
loadPlugins();
|
loadPlugins();
|
||||||
root.mergePasses(customPasses);
|
root.mergePasses(customPasses);
|
||||||
root.restartVisitors();
|
root.restartVisitors();
|
||||||
@@ -435,7 +440,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaClass> getClasses() {
|
public synchronized List<JavaClass> getClasses() {
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
@@ -443,10 +448,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
List<ClassNode> classNodeList = root.getClasses();
|
List<ClassNode> classNodeList = root.getClasses();
|
||||||
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
||||||
for (ClassNode classNode : classNodeList) {
|
for (ClassNode classNode : classNodeList) {
|
||||||
if (classNode.contains(AFlag.DONT_GENERATE)) {
|
if (!classNode.contains(AFlag.DONT_GENERATE) && !classNode.isInner()) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!classNode.getClassInfo().isInner()) {
|
|
||||||
clsList.add(convertClassNode(classNode));
|
clsList.add(convertClassNode(classNode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -546,9 +548,10 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return foundPkg;
|
return foundPkg;
|
||||||
}
|
}
|
||||||
List<JavaClass> clsList = Utils.collectionMap(pkg.getClasses(), this::convertClassNode);
|
List<JavaClass> clsList = Utils.collectionMap(pkg.getClasses(), this::convertClassNode);
|
||||||
|
List<JavaClass> clsListNoDup = Utils.collectionMap(pkg.getClassesNoDup(), this::convertClassNode);
|
||||||
int subPkgsCount = pkg.getSubPackages().size();
|
int subPkgsCount = pkg.getSubPackages().size();
|
||||||
List<JavaPackage> subPkgs = subPkgsCount == 0 ? Collections.emptyList() : new ArrayList<>(subPkgsCount);
|
List<JavaPackage> subPkgs = subPkgsCount == 0 ? Collections.emptyList() : new ArrayList<>(subPkgsCount);
|
||||||
JavaPackage javaPkg = new JavaPackage(pkg, clsList, subPkgs);
|
JavaPackage javaPkg = new JavaPackage(pkg, clsList, clsListNoDup, subPkgs);
|
||||||
if (subPkgsCount != 0) {
|
if (subPkgsCount != 0) {
|
||||||
// add subpackages after parent to avoid endless recursion
|
// add subpackages after parent to avoid endless recursion
|
||||||
for (PackageNode subPackage : pkg.getSubPackages()) {
|
for (PackageNode subPackage : pkg.getSubPackages()) {
|
||||||
|
|||||||
@@ -15,11 +15,17 @@ import jadx.core.dex.nodes.PackageNode;
|
|||||||
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||||
private final PackageNode pkgNode;
|
private final PackageNode pkgNode;
|
||||||
private final List<JavaClass> classes;
|
private final List<JavaClass> classes;
|
||||||
|
private final List<JavaClass> clsListNoDup;
|
||||||
private final List<JavaPackage> subPkgs;
|
private final List<JavaPackage> subPkgs;
|
||||||
|
|
||||||
JavaPackage(PackageNode pkgNode, List<JavaClass> classes, List<JavaPackage> subPkgs) {
|
JavaPackage(PackageNode pkgNode, List<JavaClass> classes, List<JavaPackage> subPkgs) {
|
||||||
|
this(pkgNode, classes, classes, subPkgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
JavaPackage(PackageNode pkgNode, List<JavaClass> classes, List<JavaClass> clsListNoDup, List<JavaPackage> subPkgs) {
|
||||||
this.pkgNode = pkgNode;
|
this.pkgNode = pkgNode;
|
||||||
this.classes = classes;
|
this.classes = classes;
|
||||||
|
this.clsListNoDup = clsListNoDup;
|
||||||
this.subPkgs = subPkgs;
|
this.subPkgs = subPkgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +55,10 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
|||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<JavaClass> getClassesNoDup() {
|
||||||
|
return clsListNoDup;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isRoot() {
|
public boolean isRoot() {
|
||||||
return pkgNode.isRoot();
|
return pkgNode.isRoot();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,31 +4,44 @@ import java.util.HashMap;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import jadx.api.resources.ResourceContentType;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public enum ResourceType {
|
import static jadx.api.resources.ResourceContentType.CONTENT_BINARY;
|
||||||
CODE(".dex", ".jar", ".class"),
|
import static jadx.api.resources.ResourceContentType.CONTENT_TEXT;
|
||||||
XML(".xml"),
|
import static jadx.api.resources.ResourceContentType.CONTENT_UNKNOWN;
|
||||||
ARSC(".arsc"),
|
|
||||||
APK(".apk", ".apkm", ".apks"),
|
|
||||||
FONT(".ttf", ".ttc", ".otf"),
|
|
||||||
IMG(".png", ".gif", ".jpg", ".webp", ".bmp", ".tiff"),
|
|
||||||
ARCHIVE(".zip", ".rar", ".7zip", ".7z", ".arj", ".tar", ".gzip", ".bzip", ".bzip2", ".cab", ".cpio", ".ar", ".gz", ".tgz", ".bz2"),
|
|
||||||
VIDEOS(".mp4", ".mkv", ".webm", ".avi", ".flv", ".3gp"),
|
|
||||||
SOUNDS(".aac", ".ogg", ".opus", ".mp3", ".wav", ".wma", ".mid", ".midi"),
|
|
||||||
JSON(".json"),
|
|
||||||
TEXT(".txt", ".ini", ".conf", ".yaml", ".properties", ".js"),
|
|
||||||
HTML(".html"),
|
|
||||||
LIB(".so"),
|
|
||||||
MANIFEST,
|
|
||||||
UNKNOWN;
|
|
||||||
|
|
||||||
|
public enum ResourceType {
|
||||||
|
CODE(CONTENT_BINARY, ".dex", ".jar", ".class"),
|
||||||
|
XML(CONTENT_TEXT, ".xml"),
|
||||||
|
ARSC(CONTENT_TEXT, ".arsc"),
|
||||||
|
APK(CONTENT_BINARY, ".apk", ".apkm", ".apks"),
|
||||||
|
FONT(CONTENT_BINARY, ".ttf", ".ttc", ".otf"),
|
||||||
|
IMG(CONTENT_BINARY, ".png", ".gif", ".jpg", ".jpeg", ".webp", ".bmp", ".tiff"),
|
||||||
|
ARCHIVE(CONTENT_BINARY, ".zip", ".rar", ".7zip", ".7z", ".arj", ".tar", ".gzip", ".bzip", ".bzip2", ".cab", ".cpio", ".ar", ".gz",
|
||||||
|
".tgz", ".bz2"),
|
||||||
|
VIDEOS(CONTENT_BINARY, ".mp4", ".mkv", ".webm", ".avi", ".flv", ".3gp"),
|
||||||
|
SOUNDS(CONTENT_BINARY, ".aac", ".ogg", ".opus", ".mp3", ".wav", ".wma", ".mid", ".midi"),
|
||||||
|
JSON(CONTENT_TEXT, ".json"),
|
||||||
|
TEXT(CONTENT_TEXT, ".txt", ".ini", ".conf", ".yaml", ".properties", ".js", ".java", ".kt", ".md"),
|
||||||
|
HTML(CONTENT_TEXT, ".html", ".htm"),
|
||||||
|
LIB(CONTENT_BINARY, ".so"),
|
||||||
|
MANIFEST(CONTENT_TEXT),
|
||||||
|
UNKNOWN_BIN(CONTENT_BINARY, ".bin"),
|
||||||
|
UNKNOWN(CONTENT_UNKNOWN);
|
||||||
|
|
||||||
|
private final ResourceContentType contentType;
|
||||||
private final String[] exts;
|
private final String[] exts;
|
||||||
|
|
||||||
ResourceType(String... exts) {
|
ResourceType(ResourceContentType contentType, String... exts) {
|
||||||
|
this.contentType = contentType;
|
||||||
this.exts = exts;
|
this.exts = exts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResourceContentType getContentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
|
||||||
public String[] getExts() {
|
public String[] getExts() {
|
||||||
return exts;
|
return exts;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.io.File;
|
|||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -229,9 +230,13 @@ public final class ResourcesLoader implements IResourcesLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ICodeInfo loadToCodeWriter(InputStream is) throws IOException {
|
public static ICodeInfo loadToCodeWriter(InputStream is) throws IOException {
|
||||||
|
return loadToCodeWriter(is, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ICodeInfo loadToCodeWriter(InputStream is, Charset charset) throws IOException {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
||||||
copyStream(is, baos);
|
copyStream(is, baos);
|
||||||
return new SimpleCodeInfo(baos.toString(StandardCharsets.UTF_8));
|
return new SimpleCodeInfo(baos.toString(charset));
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized BinaryXMLParser loadBinaryXmlParser() {
|
private synchronized BinaryXMLParser loadBinaryXmlParser() {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class DecompilePassWrapper extends AbstractVisitor implements IPassWrappe
|
|||||||
public void init(RootNode root) throws JadxException {
|
public void init(RootNode root) throws JadxException {
|
||||||
try {
|
try {
|
||||||
decompilePass.init(root);
|
decompilePass.init(root);
|
||||||
} catch (Throwable e) {
|
} catch (StackOverflowError | Exception e) {
|
||||||
LOG.error("Error in decompile pass init: {}", this, e);
|
LOG.error("Error in decompile pass init: {}", this, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,8 +38,8 @@ public class DecompilePassWrapper extends AbstractVisitor implements IPassWrappe
|
|||||||
public boolean visit(ClassNode cls) throws JadxException {
|
public boolean visit(ClassNode cls) throws JadxException {
|
||||||
try {
|
try {
|
||||||
return decompilePass.visit(cls);
|
return decompilePass.visit(cls);
|
||||||
} catch (Throwable e) {
|
} catch (StackOverflowError | Exception e) {
|
||||||
LOG.error("Error in decompile pass: {}, class: {}", this, cls, e);
|
cls.addError("Error in decompile pass: " + this, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,8 +48,8 @@ public class DecompilePassWrapper extends AbstractVisitor implements IPassWrappe
|
|||||||
public void visit(MethodNode mth) throws JadxException {
|
public void visit(MethodNode mth) throws JadxException {
|
||||||
try {
|
try {
|
||||||
decompilePass.visit(mth);
|
decompilePass.visit(mth);
|
||||||
} catch (Throwable e) {
|
} catch (StackOverflowError | Exception e) {
|
||||||
LOG.error("Error in decompile pass: {}, method: {}", this, mth, e);
|
mth.addError("Error in decompile pass: " + this, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package jadx.api.resources;
|
||||||
|
|
||||||
|
public enum ResourceContentType {
|
||||||
|
CONTENT_TEXT,
|
||||||
|
CONTENT_BINARY,
|
||||||
|
CONTENT_NONE,
|
||||||
|
CONTENT_UNKNOWN,
|
||||||
|
}
|
||||||
@@ -66,6 +66,7 @@ import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
|||||||
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
|
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
|
||||||
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
|
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
|
||||||
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
||||||
|
import jadx.core.dex.visitors.regions.SwitchBreakVisitor;
|
||||||
import jadx.core.dex.visitors.regions.SwitchOverStringVisitor;
|
import jadx.core.dex.visitors.regions.SwitchOverStringVisitor;
|
||||||
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
||||||
import jadx.core.dex.visitors.rename.CodeRenameVisitor;
|
import jadx.core.dex.visitors.rename.CodeRenameVisitor;
|
||||||
@@ -196,12 +197,14 @@ public class Jadx {
|
|||||||
passes.add(new FixAccessModifiers());
|
passes.add(new FixAccessModifiers());
|
||||||
passes.add(new ClassModifier());
|
passes.add(new ClassModifier());
|
||||||
passes.add(new LoopRegionVisitor());
|
passes.add(new LoopRegionVisitor());
|
||||||
|
passes.add(new SwitchBreakVisitor());
|
||||||
|
|
||||||
if (args.isInlineMethods()) {
|
if (args.isInlineMethods()) {
|
||||||
passes.add(new MarkMethodsForInline());
|
passes.add(new MarkMethodsForInline());
|
||||||
}
|
}
|
||||||
passes.add(new ProcessVariables());
|
passes.add(new ProcessVariables());
|
||||||
passes.add(new ApplyVariableNames());
|
passes.add(new ApplyVariableNames());
|
||||||
|
|
||||||
passes.add(new PrepareForCodeGen());
|
passes.add(new PrepareForCodeGen());
|
||||||
if (args.isCfgOutput()) {
|
if (args.isCfgOutput()) {
|
||||||
passes.add(DotGraphVisitor.dumpRegions());
|
passes.add(DotGraphVisitor.dumpRegions());
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
package jadx.core;
|
package jadx.core;
|
||||||
|
|
||||||
|
import java.util.EnumMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.DecompilationMode;
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.impl.SimpleCodeInfo;
|
import jadx.api.impl.SimpleCodeInfo;
|
||||||
import jadx.core.codegen.CodeGen;
|
import jadx.core.codegen.CodeGen;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.DecompileModeOverrideAttr;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.LoadStage;
|
import jadx.core.dex.nodes.LoadStage;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.visitors.DepthTraversal;
|
import jadx.core.dex.visitors.DepthTraversal;
|
||||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
|
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
|
||||||
@@ -41,6 +48,7 @@ public class ProcessClass {
|
|||||||
// nothing to do
|
// nothing to do
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
Utils.checkThreadInterrupt();
|
||||||
synchronized (cls.getClassInfo()) {
|
synchronized (cls.getClassInfo()) {
|
||||||
try {
|
try {
|
||||||
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
|
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
|
||||||
@@ -76,6 +84,7 @@ public class ProcessClass {
|
|||||||
cls.setState(PROCESS_COMPLETE);
|
cls.setState(PROCESS_COMPLETE);
|
||||||
}
|
}
|
||||||
if (codegen) {
|
if (codegen) {
|
||||||
|
Utils.checkThreadInterrupt();
|
||||||
ICodeInfo code = CodeGen.generate(cls);
|
ICodeInfo code = CodeGen.generate(cls);
|
||||||
if (!cls.contains(AFlag.DONT_UNLOAD_CLASS)) {
|
if (!cls.contains(AFlag.DONT_UNLOAD_CLASS)) {
|
||||||
cls.unload();
|
cls.unload();
|
||||||
@@ -84,7 +93,7 @@ public class ProcessClass {
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (Throwable e) {
|
} catch (StackOverflowError | Exception e) {
|
||||||
if (codegen) {
|
if (codegen) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@@ -119,7 +128,7 @@ public class ProcessClass {
|
|||||||
throw new JadxRuntimeException("Codegen failed");
|
throw new JadxRuntimeException("Codegen failed");
|
||||||
}
|
}
|
||||||
return code;
|
return code;
|
||||||
} catch (Throwable e) {
|
} catch (StackOverflowError | Exception e) {
|
||||||
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
|
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,7 +144,7 @@ public class ProcessClass {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
process(cls, false);
|
process(cls, false);
|
||||||
} catch (Throwable e) {
|
} catch (StackOverflowError | Exception e) {
|
||||||
throw new JadxRuntimeException("Failed to process class: " + cls.getFullName(), e);
|
throw new JadxRuntimeException("Failed to process class: " + cls.getFullName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,11 +155,48 @@ public class ProcessClass {
|
|||||||
public @Nullable ICodeInfo forceGenerateCode(ClassNode cls) {
|
public @Nullable ICodeInfo forceGenerateCode(ClassNode cls) {
|
||||||
try {
|
try {
|
||||||
return process(cls, true);
|
return process(cls, true);
|
||||||
} catch (Throwable e) {
|
} catch (StackOverflowError | Exception e) {
|
||||||
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
|
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Map<DecompilationMode, ProcessClass> modesMap = new EnumMap<>(DecompilationMode.class);
|
||||||
|
|
||||||
|
public @Nullable ICodeInfo forceGenerateCodeForMode(ClassNode cls, DecompilationMode mode) {
|
||||||
|
synchronized (modesMap) {
|
||||||
|
ProcessClass prCls = modesMap.computeIfAbsent(mode, m -> {
|
||||||
|
RootNode root = cls.root();
|
||||||
|
ProcessClass newPrCls = new ProcessClass(getPassesForMode(root.getArgs(), m));
|
||||||
|
newPrCls.initPasses(root);
|
||||||
|
return newPrCls;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
cls.addAttr(new DecompileModeOverrideAttr(mode));
|
||||||
|
return prCls.forceGenerateCode(cls);
|
||||||
|
} finally {
|
||||||
|
cls.remove(AType.DECOMPILE_MODE_OVERRIDE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<IDexTreeVisitor> getPassesForMode(JadxArgs baseArgs, DecompilationMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case FALLBACK:
|
||||||
|
return Jadx.getFallbackPassesList();
|
||||||
|
|
||||||
|
case SIMPLE:
|
||||||
|
// copy properties into new args
|
||||||
|
// keep in sync with properties usage in Jadx.getSimpleModePasses method
|
||||||
|
JadxArgs args = new JadxArgs();
|
||||||
|
args.setDebugInfo(baseArgs.isDebugInfo());
|
||||||
|
args.setCommentsLevel(baseArgs.getCommentsLevel());
|
||||||
|
return Jadx.getSimpleModePasses(args);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unexpected decompilation mode: " + mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void initPasses(RootNode root) {
|
public void initPasses(RootNode root) {
|
||||||
for (IDexTreeVisitor pass : passes) {
|
for (IDexTreeVisitor pass : passes) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -155,8 +155,6 @@ public class ClassGen {
|
|||||||
if (Consts.DEBUG_USAGE) {
|
if (Consts.DEBUG_USAGE) {
|
||||||
addClassUsageInfo(code, cls);
|
addClassUsageInfo(code, cls);
|
||||||
}
|
}
|
||||||
CodeGenUtils.addErrorsAndComments(code, cls);
|
|
||||||
CodeGenUtils.addSourceFileInfo(code, cls);
|
|
||||||
addClassDeclaration(code);
|
addClassDeclaration(code);
|
||||||
addClassBody(code);
|
addClassBody(code);
|
||||||
}
|
}
|
||||||
@@ -177,9 +175,13 @@ public class ClassGen {
|
|||||||
af = af.remove(AccessFlags.STATIC).remove(AccessFlags.PRIVATE);
|
af = af.remove(AccessFlags.STATIC).remove(AccessFlags.PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
annotationGen.addForClass(clsCode);
|
CodeGenUtils.addComments(clsCode, cls);
|
||||||
insertRenameInfo(clsCode, cls);
|
CodeGenUtils.addClassRenamedComment(clsCode, cls);
|
||||||
|
CodeGenUtils.addErrors(clsCode, cls);
|
||||||
|
CodeGenUtils.addSourceFileInfo(clsCode, cls);
|
||||||
CodeGenUtils.addInputFileInfo(clsCode, cls);
|
CodeGenUtils.addInputFileInfo(clsCode, cls);
|
||||||
|
|
||||||
|
annotationGen.addForClass(clsCode);
|
||||||
clsCode.startLineWithNum(cls.getSourceLine()).add(af.makeString(cls.checkCommentsLevel(CommentsLevel.INFO)));
|
clsCode.startLineWithNum(cls.getSourceLine()).add(af.makeString(cls.checkCommentsLevel(CommentsLevel.INFO)));
|
||||||
if (af.isInterface()) {
|
if (af.isInterface()) {
|
||||||
if (af.isAnnotation()) {
|
if (af.isAnnotation()) {
|
||||||
@@ -434,10 +436,10 @@ public class ClassGen {
|
|||||||
if (Consts.DEBUG_USAGE) {
|
if (Consts.DEBUG_USAGE) {
|
||||||
addFieldUsageInfo(code, f);
|
addFieldUsageInfo(code, f);
|
||||||
}
|
}
|
||||||
|
CodeGenUtils.addComments(code, f);
|
||||||
if (f.getFieldInfo().hasAlias()) {
|
if (f.getFieldInfo().hasAlias()) {
|
||||||
CodeGenUtils.addRenamedComment(code, f, f.getName());
|
CodeGenUtils.addRenamedComment(code, f, f.getName());
|
||||||
}
|
}
|
||||||
CodeGenUtils.addComments(code, f);
|
|
||||||
annotationGen.addForField(code, f);
|
annotationGen.addForField(code, f);
|
||||||
|
|
||||||
code.startLine(f.getAccessFlags().makeString(f.checkCommentsLevel(CommentsLevel.INFO)));
|
code.startLine(f.getAccessFlags().makeString(f.checkCommentsLevel(CommentsLevel.INFO)));
|
||||||
@@ -802,13 +804,6 @@ public class ClassGen {
|
|||||||
return root.getClsp().isClsKnown(currentPkg + '.' + shortName);
|
return root.getClsp().isClsKnown(currentPkg + '.' + shortName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertRenameInfo(ICodeWriter code, ClassNode cls) {
|
|
||||||
ClassInfo classInfo = cls.getClassInfo();
|
|
||||||
if (classInfo.hasAlias() && cls.checkCommentsLevel(CommentsLevel.INFO)) {
|
|
||||||
CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addClassUsageInfo(ICodeWriter code, ClassNode cls) {
|
private static void addClassUsageInfo(ICodeWriter code, ClassNode cls) {
|
||||||
List<ClassNode> deps = cls.getDependencies();
|
List<ClassNode> deps = cls.getDependencies();
|
||||||
code.startLine("// deps - ").add(Integer.toString(deps.size()));
|
code.startLine("// deps - ").add(Integer.toString(deps.size()));
|
||||||
|
|||||||
@@ -3,14 +3,13 @@ package jadx.core.codegen;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
|
import jadx.api.DecompilationMode;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.args.IntegerFormat;
|
import jadx.api.args.IntegerFormat;
|
||||||
@@ -25,6 +24,7 @@ import jadx.core.Jadx;
|
|||||||
import jadx.core.codegen.utils.CodeGenUtils;
|
import jadx.core.codegen.utils.CodeGenUtils;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.DecompileModeOverrideAttr;
|
||||||
import jadx.core.dex.attributes.nodes.JadxError;
|
import jadx.core.dex.attributes.nodes.JadxError;
|
||||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
@@ -268,7 +268,14 @@ public class MethodGen {
|
|||||||
|
|
||||||
public void addInstructions(ICodeWriter code) throws CodegenException {
|
public void addInstructions(ICodeWriter code) throws CodegenException {
|
||||||
JadxArgs args = mth.root().getArgs();
|
JadxArgs args = mth.root().getArgs();
|
||||||
switch (args.getDecompilationMode()) {
|
DecompileModeOverrideAttr modeOverrideAttr = mth.getTopParentClass().get(AType.DECOMPILE_MODE_OVERRIDE);
|
||||||
|
DecompilationMode mode;
|
||||||
|
if (modeOverrideAttr != null) {
|
||||||
|
mode = modeOverrideAttr.getMode();
|
||||||
|
} else {
|
||||||
|
mode = args.getDecompilationMode();
|
||||||
|
}
|
||||||
|
switch (mode) {
|
||||||
case AUTO:
|
case AUTO:
|
||||||
if (classGen.isFallbackMode() || mth.getRegion() == null) {
|
if (classGen.isFallbackMode() || mth.getRegion() == null) {
|
||||||
// TODO: try simple mode first
|
// TODO: try simple mode first
|
||||||
@@ -381,6 +388,20 @@ public class MethodGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addFallbackMethodCode(ICodeWriter code, FallbackOption fallbackOption) {
|
public void addFallbackMethodCode(ICodeWriter code, FallbackOption fallbackOption) {
|
||||||
|
if (fallbackOption == COMMENTED_DUMP && mth.getCommentsLevel() != CommentsLevel.DEBUG) {
|
||||||
|
long insnCountEstimate = mth.getInsnsCount();
|
||||||
|
if (insnCountEstimate > 200) {
|
||||||
|
code.incIndent();
|
||||||
|
code.startLine("Method dump skipped, instruction units count: " + insnCountEstimate);
|
||||||
|
if (code.isMetadataSupported()) {
|
||||||
|
code.startLine("To view this dump change 'Code comments level' option to 'DEBUG'");
|
||||||
|
} else {
|
||||||
|
code.startLine("To view this dump add '--comments-level debug' option");
|
||||||
|
}
|
||||||
|
code.decIndent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (fallbackOption != FALLBACK_MODE) {
|
if (fallbackOption != FALLBACK_MODE) {
|
||||||
List<JadxError> errors = mth.getAll(AType.JADX_ERROR); // preserve error before unload
|
List<JadxError> errors = mth.getAll(AType.JADX_ERROR); // preserve error before unload
|
||||||
try {
|
try {
|
||||||
@@ -404,23 +425,6 @@ public class MethodGen {
|
|||||||
code.startLine("// Can't load method instructions.");
|
code.startLine("// Can't load method instructions.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fallbackOption == COMMENTED_DUMP && mth.getCommentsLevel() != CommentsLevel.DEBUG) {
|
|
||||||
long insnCountEstimate = Stream.of(insnArr)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.filter(insn -> insn.getType() != InsnType.NOP)
|
|
||||||
.count();
|
|
||||||
if (insnCountEstimate > 100) {
|
|
||||||
code.incIndent();
|
|
||||||
code.startLine("Method dump skipped, instructions count: " + insnArr.length);
|
|
||||||
if (code.isMetadataSupported()) {
|
|
||||||
code.startLine("To view this dump change 'Code comments level' option to 'DEBUG'");
|
|
||||||
} else {
|
|
||||||
code.startLine("To view this dump add '--comments-level debug' option");
|
|
||||||
}
|
|
||||||
code.decIndent();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
if (mth.getThisArg() != null) {
|
if (mth.getThisArg() != null) {
|
||||||
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
|
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
|
||||||
|
|||||||
@@ -294,6 +294,9 @@ public class RegionGen extends InsnGen {
|
|||||||
isEnum = clsDetails != null && clsDetails.hasAccFlag(AccessFlags.ENUM);
|
isEnum = clsDetails != null && clsDetails.hasAccFlag(AccessFlags.ENUM);
|
||||||
}
|
}
|
||||||
if (isEnum) {
|
if (isEnum) {
|
||||||
|
if (fld != null) {
|
||||||
|
code.attachAnnotation(fld);
|
||||||
|
}
|
||||||
code.add(fldInfo.getAlias());
|
code.add(fldInfo.getAlias());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package jadx.core.codegen.utils;
|
package jadx.core.codegen.utils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -16,6 +17,7 @@ import jadx.core.dex.attributes.nodes.JadxCommentsAttr;
|
|||||||
import jadx.core.dex.attributes.nodes.JadxError;
|
import jadx.core.dex.attributes.nodes.JadxError;
|
||||||
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
||||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.instructions.args.CodeVar;
|
import jadx.core.dex.instructions.args.CodeVar;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
import jadx.core.dex.instructions.args.SSAVar;
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
@@ -26,8 +28,8 @@ import jadx.core.utils.Utils;
|
|||||||
public class CodeGenUtils {
|
public class CodeGenUtils {
|
||||||
|
|
||||||
public static void addErrorsAndComments(ICodeWriter code, NotificationAttrNode node) {
|
public static void addErrorsAndComments(ICodeWriter code, NotificationAttrNode node) {
|
||||||
addErrors(code, node);
|
|
||||||
addComments(code, node);
|
addComments(code, node);
|
||||||
|
addErrors(code, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addErrors(ICodeWriter code, NotificationAttrNode node) {
|
public static void addErrors(ICodeWriter code, NotificationAttrNode node) {
|
||||||
@@ -86,9 +88,37 @@ public class CodeGenUtils {
|
|||||||
} else {
|
} else {
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
}
|
}
|
||||||
CommentStyle style = comment.getStyle();
|
addCommentWithStyle(code, comment.getStyle(), comment.getComment());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addJadxNodeComment(ICodeWriter code, NotificationAttrNode node,
|
||||||
|
CommentsLevel level, BiConsumer<ICodeWriter, String> commentFunc) {
|
||||||
|
if (node.checkCommentsLevel(level)) {
|
||||||
|
code.startLine();
|
||||||
|
addCommentWithStyle(code, CommentStyle.BLOCK_CONDENSED, (commentCode, newLinePrefix) -> {
|
||||||
|
commentCode.add("JADX ").add(level.name()).add(": ");
|
||||||
|
commentFunc.accept(commentCode, newLinePrefix);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addJadxComment(ICodeWriter code, CommentsLevel level, String commentStr) {
|
||||||
|
code.startLine();
|
||||||
|
addCommentWithStyle(code, CommentStyle.BLOCK_CONDENSED, "JADX " + level.name() + ": " + commentStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addCommentWithStyle(ICodeWriter code, CommentStyle style, String commentStr) {
|
||||||
appendMultiLineString(code, "", style.getStart());
|
appendMultiLineString(code, "", style.getStart());
|
||||||
appendMultiLineString(code, style.getOnNewLine(), comment.getComment());
|
appendMultiLineString(code, style.getOnNewLine(), commentStr);
|
||||||
|
appendMultiLineString(code, "", style.getEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert comment with function, use second arg as new line prefix
|
||||||
|
*/
|
||||||
|
private static void addCommentWithStyle(ICodeWriter code, CommentStyle style, BiConsumer<ICodeWriter, String> commentFunc) {
|
||||||
|
appendMultiLineString(code, "", style.getStart());
|
||||||
|
commentFunc.accept(code, style.getOnNewLine());
|
||||||
appendMultiLineString(code, "", style.getEnd());
|
appendMultiLineString(code, "", style.getEnd());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,16 +137,21 @@ public class CodeGenUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void addClassRenamedComment(ICodeWriter code, ClassNode cls) {
|
||||||
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
|
if (classInfo.hasAlias()) {
|
||||||
|
addRenamedComment(code, cls, classInfo.getType().getObject());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void addRenamedComment(ICodeWriter code, NotificationAttrNode node, String origName) {
|
public static void addRenamedComment(ICodeWriter code, NotificationAttrNode node, String origName) {
|
||||||
if (!node.checkCommentsLevel(CommentsLevel.INFO)) {
|
addJadxNodeComment(code, node, CommentsLevel.INFO, (commentCode, newLinePrefix) -> {
|
||||||
return;
|
commentCode.add("renamed from: ").add(origName);
|
||||||
}
|
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
|
||||||
code.startLine("/* renamed from: ").add(origName);
|
if (renameReasonAttr != null) {
|
||||||
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
|
commentCode.add(", reason: ").add(renameReasonAttr.getDescription());
|
||||||
if (renameReasonAttr != null) {
|
}
|
||||||
code.add(", reason: ").add(renameReasonAttr.getDescription());
|
});
|
||||||
}
|
|
||||||
code.add(" */");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addSourceFileInfo(ICodeWriter code, ClassNode node) {
|
public static void addSourceFileInfo(ICodeWriter code, ClassNode node) {
|
||||||
@@ -131,7 +166,7 @@ public class CodeGenUtils {
|
|||||||
// ignore similar name
|
// ignore similar name
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
code.startLine("/* compiled from: ").add(fileName).add(" */");
|
addJadxComment(code, CommentsLevel.INFO, "compiled from: " + fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +181,7 @@ public class CodeGenUtils {
|
|||||||
// don't add same comment for inner classes
|
// don't add same comment for inner classes
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
code.startLine("/* loaded from: ").add(inputFileName).add(" */");
|
addJadxComment(code, CommentsLevel.INFO, "loaded from: " + inputFileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
|||||||
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
|
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
|
||||||
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr;
|
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr;
|
||||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.DecompileModeOverrideAttr;
|
||||||
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
||||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||||
import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
||||||
@@ -62,6 +63,7 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
|
|||||||
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
|
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
|
||||||
public static final AType<AnonymousClassAttr> ANONYMOUS_CLASS = new AType<>();
|
public static final AType<AnonymousClassAttr> ANONYMOUS_CLASS = new AType<>();
|
||||||
public static final AType<InlinedAttr> INLINED = new AType<>();
|
public static final AType<InlinedAttr> INLINED = new AType<>();
|
||||||
|
public static final AType<DecompileModeOverrideAttr> DECOMPILE_MODE_OVERRIDE = new AType<>();
|
||||||
|
|
||||||
// field
|
// field
|
||||||
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
|
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
|
import jadx.api.DecompilationMode;
|
||||||
|
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
|
||||||
|
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
|
||||||
|
public class DecompileModeOverrideAttr implements IJadxAttribute {
|
||||||
|
|
||||||
|
private final DecompilationMode mode;
|
||||||
|
|
||||||
|
public DecompileModeOverrideAttr(DecompilationMode mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DecompilationMode getMode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IJadxAttrType<DecompileModeOverrideAttr> getAttrType() {
|
||||||
|
return AType.DECOMPILE_MODE_OVERRIDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DECOMPILE_MODE_OVERRIDE: " + mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
package jadx.core.dex.attributes.nodes;
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
@@ -30,10 +31,10 @@ public class JadxCommentsAttr implements IJadxAttribute {
|
|||||||
return newAttr;
|
return newAttr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Map<CommentsLevel, List<String>> comments = new EnumMap<>(CommentsLevel.class);
|
private final Map<CommentsLevel, Set<String>> comments = new EnumMap<>(CommentsLevel.class);
|
||||||
|
|
||||||
public void add(CommentsLevel level, String comment) {
|
public void add(CommentsLevel level, String comment) {
|
||||||
comments.computeIfAbsent(level, l -> new ArrayList<>()).add(comment);
|
comments.computeIfAbsent(level, l -> new HashSet<>()).add(comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> formatAndFilter(CommentsLevel level) {
|
public List<String> formatAndFilter(CommentsLevel level) {
|
||||||
@@ -47,12 +48,11 @@ public class JadxCommentsAttr implements IJadxAttribute {
|
|||||||
return e.getValue().stream()
|
return e.getValue().stream()
|
||||||
.map(v -> "JADX " + levelName + ": " + v);
|
.map(v -> "JADX " + levelName + ": " + v);
|
||||||
})
|
})
|
||||||
.distinct()
|
|
||||||
.sorted()
|
.sorted()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<CommentsLevel, List<String>> getComments() {
|
public Map<CommentsLevel, Set<String>> getComments() {
|
||||||
return comments;
|
return comments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,6 @@ public class RegionRefAttr implements IJadxAttribute {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "RegionRef:" + region;
|
return "RegionRef:" + region.baseString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private ClassAliasInfo alias;
|
private ClassAliasInfo alias;
|
||||||
|
|
||||||
private ClassInfo(RootNode root, ArgType type) {
|
private ClassInfo(RootNode root, ArgType type, boolean canBeInner) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
splitAndApplyNames(root, type, root.getArgs().isMoveInnerClasses());
|
splitAndApplyNames(root, type, canBeInner);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ClassInfo fromType(RootNode root, ArgType type) {
|
public static ClassInfo fromType(RootNode root, ArgType type) {
|
||||||
@@ -34,7 +34,8 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
|||||||
if (cls != null) {
|
if (cls != null) {
|
||||||
return cls;
|
return cls;
|
||||||
}
|
}
|
||||||
ClassInfo newClsInfo = new ClassInfo(root, clsType);
|
boolean canBeInner = root.getArgs().isMoveInnerClasses();
|
||||||
|
ClassInfo newClsInfo = new ClassInfo(root, clsType, canBeInner);
|
||||||
return root.getInfoStorage().putCls(newClsInfo);
|
return root.getInfoStorage().putCls(newClsInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +43,10 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
|||||||
return fromType(root, ArgType.object(clsName));
|
return fromType(root, ArgType.object(clsName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ClassInfo fromNameWithoutCache(RootNode root, String fullClsName, boolean canBeInner) {
|
||||||
|
return new ClassInfo(root, ArgType.object(fullClsName), canBeInner);
|
||||||
|
}
|
||||||
|
|
||||||
private static ArgType checkClassType(ArgType type) {
|
private static ArgType checkClassType(ArgType type) {
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
throw new JadxRuntimeException("Null class type");
|
throw new JadxRuntimeException("Null class type");
|
||||||
|
|||||||
@@ -50,7 +50,12 @@ public class InsnDecoder {
|
|||||||
rawInsn.decode();
|
rawInsn.decode();
|
||||||
insn = decode(rawInsn);
|
insn = decode(rawInsn);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
boolean mthWithErrors = method.contains(AType.JADX_ERROR);
|
||||||
method.addError("Failed to decode insn: " + rawInsn, e);
|
method.addError("Failed to decode insn: " + rawInsn, e);
|
||||||
|
if (mthWithErrors) {
|
||||||
|
// second error in this method => abort processing
|
||||||
|
throw new JadxRuntimeException("Failed to decode insn: " + rawInsn, e);
|
||||||
|
}
|
||||||
insn = new InsnNode(InsnType.NOP, 0);
|
insn = new InsnNode(InsnType.NOP, 0);
|
||||||
insn.addAttr(AType.JADX_ERROR, new JadxError("decode failed: " + e.getMessage(), e));
|
insn.addAttr(AType.JADX_ERROR, new JadxError("decode failed: " + e.getMessage(), e));
|
||||||
}
|
}
|
||||||
@@ -478,7 +483,7 @@ public class InsnDecoder {
|
|||||||
case FILL_ARRAY_DATA:
|
case FILL_ARRAY_DATA:
|
||||||
return new FillArrayInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN_ARRAY), insn.getTarget());
|
return new FillArrayInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN_ARRAY), insn.getTarget());
|
||||||
case FILL_ARRAY_DATA_PAYLOAD:
|
case FILL_ARRAY_DATA_PAYLOAD:
|
||||||
return new FillArrayData(((IArrayPayload) Objects.requireNonNull(insn.getPayload())));
|
return new FillArrayData((IArrayPayload) Objects.requireNonNull(insn.getPayload()));
|
||||||
|
|
||||||
case FILLED_NEW_ARRAY:
|
case FILLED_NEW_ARRAY:
|
||||||
return filledNewArray(insn, false);
|
return filledNewArray(insn, false);
|
||||||
@@ -492,7 +497,7 @@ public class InsnDecoder {
|
|||||||
|
|
||||||
case PACKED_SWITCH_PAYLOAD:
|
case PACKED_SWITCH_PAYLOAD:
|
||||||
case SPARSE_SWITCH_PAYLOAD:
|
case SPARSE_SWITCH_PAYLOAD:
|
||||||
return new SwitchData(((ISwitchPayload) insn.getPayload()));
|
return new SwitchData((ISwitchPayload) insn.getPayload());
|
||||||
|
|
||||||
case MONITOR_ENTER:
|
case MONITOR_ENTER:
|
||||||
return insn(InsnType.MONITOR_ENTER,
|
return insn(InsnType.MONITOR_ENTER,
|
||||||
@@ -510,7 +515,7 @@ public class InsnDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private SwitchInsn makeSwitch(InsnData insn, boolean packed) {
|
private SwitchInsn makeSwitch(InsnData insn, boolean packed) {
|
||||||
SwitchInsn swInsn = new SwitchInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN), insn.getTarget(), packed);
|
SwitchInsn swInsn = new SwitchInsn(InsnArg.reg(insn, 0, ArgType.NARROW_INTEGRAL), insn.getTarget(), packed);
|
||||||
ICustomPayload payload = insn.getPayload();
|
ICustomPayload payload = insn.getPayload();
|
||||||
if (payload != null) {
|
if (payload != null) {
|
||||||
swInsn.attachSwitchData(new SwitchData((ISwitchPayload) payload), insn.getTarget());
|
swInsn.attachSwitchData(new SwitchData((ISwitchPayload) payload), insn.getTarget());
|
||||||
|
|||||||
@@ -69,6 +69,15 @@ public final class PhiInsn extends InsnNode {
|
|||||||
return (RegisterArg) super.getArg(n);
|
return (RegisterArg) super.getArg(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable RegisterArg getArgByBlock(BlockNode block) {
|
||||||
|
for (int i = 0; i < blockBinds.size(); i++) {
|
||||||
|
if (blockBinds.get(i) == block) {
|
||||||
|
return getArg(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean removeArg(InsnArg arg) {
|
public boolean removeArg(InsnArg arg) {
|
||||||
int index = getArgIndex(arg);
|
int index = getArgIndex(arg);
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ public abstract class ArgType {
|
|||||||
PrimitiveType.INT, PrimitiveType.FLOAT,
|
PrimitiveType.INT, PrimitiveType.FLOAT,
|
||||||
PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
|
PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
|
||||||
|
|
||||||
|
public static final ArgType NARROW_NEG_NUMBERS = unknown(
|
||||||
|
PrimitiveType.INT, PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.FLOAT);
|
||||||
|
|
||||||
public static final ArgType NARROW_NUMBERS_NO_FLOAT = unknown(
|
public static final ArgType NARROW_NUMBERS_NO_FLOAT = unknown(
|
||||||
PrimitiveType.INT, PrimitiveType.BOOLEAN,
|
PrimitiveType.INT, PrimitiveType.BOOLEAN,
|
||||||
PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
|
PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
|
||||||
@@ -746,6 +749,9 @@ public abstract class ArgType {
|
|||||||
case '[':
|
case '[':
|
||||||
return array(parse(type.substring(1)));
|
return array(parse(type.substring(1)));
|
||||||
default:
|
default:
|
||||||
|
if (type.length() != 1) {
|
||||||
|
throw new JadxRuntimeException("Unknown type string: \"" + type + '"');
|
||||||
|
}
|
||||||
return parse(f);
|
return parse(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -772,7 +778,7 @@ public abstract class ArgType {
|
|||||||
return VOID;
|
return VOID;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
throw new JadxRuntimeException("Unknown type char: '" + f + "' (0x" + Integer.toHexString(f) + ')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -292,6 +292,17 @@ public abstract class InsnArg extends Typed {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSameVar(SSAVar ssaVar) {
|
||||||
|
if (ssaVar == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isRegister()) {
|
||||||
|
SSAVar thisSsaVar = ((RegisterArg) this).getSVar();
|
||||||
|
return Objects.equals(thisSsaVar, ssaVar);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSameCodeVar(RegisterArg arg) {
|
public boolean isSameCodeVar(RegisterArg arg) {
|
||||||
if (arg == null) {
|
if (arg == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ public final class LiteralArg extends InsnArg {
|
|||||||
if (value == 1) {
|
if (value == 1) {
|
||||||
return ArgType.NARROW_NUMBERS;
|
return ArgType.NARROW_NUMBERS;
|
||||||
}
|
}
|
||||||
|
if (value < 0) {
|
||||||
|
return ArgType.NARROW_NEG_NUMBERS;
|
||||||
|
}
|
||||||
return ArgType.NARROW_NUMBERS_NO_BOOL;
|
return ArgType.NARROW_NUMBERS_NO_BOOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
import jadx.api.DecompilationMode;
|
import jadx.api.DecompilationMode;
|
||||||
import jadx.api.ICodeCache;
|
import jadx.api.ICodeCache;
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.JadxArgs;
|
|
||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
import jadx.api.impl.SimpleCodeInfo;
|
import jadx.api.impl.SimpleCodeInfo;
|
||||||
import jadx.api.impl.SimpleCodeWriter;
|
import jadx.api.impl.SimpleCodeWriter;
|
||||||
@@ -38,8 +37,6 @@ import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
|
|||||||
import jadx.api.plugins.input.data.impl.ListConsumer;
|
import jadx.api.plugins.input.data.impl.ListConsumer;
|
||||||
import jadx.api.usage.IUsageInfoData;
|
import jadx.api.usage.IUsageInfoData;
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.Jadx;
|
|
||||||
import jadx.core.ProcessClass;
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
||||||
@@ -72,6 +69,7 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
private ArgType superClass;
|
private ArgType superClass;
|
||||||
private List<ArgType> interfaces;
|
private List<ArgType> interfaces;
|
||||||
private List<ArgType> generics = Collections.emptyList();
|
private List<ArgType> generics = Collections.emptyList();
|
||||||
|
private String inputFileName;
|
||||||
|
|
||||||
private List<MethodNode> methods;
|
private List<MethodNode> methods;
|
||||||
private List<FieldNode> fields;
|
private List<FieldNode> fields;
|
||||||
@@ -123,6 +121,7 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
this.accessFlags = new AccessInfo(getAccessFlags(cls), AFType.CLASS);
|
this.accessFlags = new AccessInfo(getAccessFlags(cls), AFType.CLASS);
|
||||||
this.superClass = checkSuperType(cls);
|
this.superClass = checkSuperType(cls);
|
||||||
this.interfaces = Utils.collectionMap(cls.getInterfacesTypes(), ArgType::object);
|
this.interfaces = Utils.collectionMap(cls.getInterfacesTypes(), ArgType::object);
|
||||||
|
setInputFileName(cls.getInputFileName());
|
||||||
|
|
||||||
ListConsumer<IFieldData, FieldNode> fieldsConsumer = new ListConsumer<>(fld -> FieldNode.build(this, fld));
|
ListConsumer<IFieldData, FieldNode> fieldsConsumer = new ListConsumer<>(fld -> FieldNode.build(this, fld));
|
||||||
ListConsumer<IMethodData, MethodNode> methodsConsumer = new ListConsumer<>(mth -> MethodNode.build(this, mth));
|
ListConsumer<IMethodData, MethodNode> methodsConsumer = new ListConsumer<>(mth -> MethodNode.build(this, mth));
|
||||||
@@ -150,6 +149,8 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
IUsageInfoData usageInfoData = root.getArgs().getUsageInfoCache().get(root);
|
IUsageInfoData usageInfoData = root.getArgs().getUsageInfoCache().get(root);
|
||||||
if (usageInfoData != null) {
|
if (usageInfoData != null) {
|
||||||
usageInfoData.applyForClass(this);
|
usageInfoData.applyForClass(this);
|
||||||
|
} else {
|
||||||
|
LOG.warn("Can't restore usage data for class: {}", this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +227,7 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
public static ClassNode addSyntheticClass(RootNode root, ClassInfo clsInfo, int accessFlags) {
|
public static ClassNode addSyntheticClass(RootNode root, ClassInfo clsInfo, int accessFlags) {
|
||||||
ClassNode cls = new ClassNode(root, clsInfo, accessFlags);
|
ClassNode cls = new ClassNode(root, clsInfo, accessFlags);
|
||||||
cls.add(AFlag.SYNTHETIC);
|
cls.add(AFlag.SYNTHETIC);
|
||||||
|
cls.setInputFileName("synthetic");
|
||||||
cls.setState(ProcessState.PROCESS_COMPLETE);
|
cls.setState(ProcessState.PROCESS_COMPLETE);
|
||||||
root.addClassNode(cls);
|
root.addClassNode(cls);
|
||||||
return cls;
|
return cls;
|
||||||
@@ -315,23 +317,25 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
* WARNING: Slow operation! Use with caution!
|
* WARNING: Slow operation! Use with caution!
|
||||||
*/
|
*/
|
||||||
public ICodeInfo decompileWithMode(DecompilationMode mode) {
|
public ICodeInfo decompileWithMode(DecompilationMode mode) {
|
||||||
DecompilationMode baseMode = root.getArgs().getDecompilationMode();
|
switch (mode) {
|
||||||
if (mode == baseMode) {
|
case AUTO:
|
||||||
return decompile(true);
|
case RESTRUCTURE:
|
||||||
}
|
return decompile(true);
|
||||||
synchronized (DECOMPILE_WITH_MODE_SYNC) {
|
|
||||||
JadxArgs args = root.getArgs();
|
case SIMPLE:
|
||||||
try {
|
case FALLBACK:
|
||||||
unload();
|
synchronized (DECOMPILE_WITH_MODE_SYNC) {
|
||||||
args.setDecompilationMode(mode);
|
try {
|
||||||
ProcessClass process = new ProcessClass(Jadx.getPassesList(args));
|
unload();
|
||||||
process.initPasses(root);
|
ICodeInfo code = root.getProcessClasses().forceGenerateCodeForMode(this, mode);
|
||||||
ICodeInfo code = process.forceGenerateCode(this);
|
return Utils.getOrElse(code, ICodeInfo.EMPTY);
|
||||||
return Utils.getOrElse(code, ICodeInfo.EMPTY);
|
} finally {
|
||||||
} finally {
|
unload();
|
||||||
args.setDecompilationMode(baseMode);
|
}
|
||||||
unload();
|
}
|
||||||
}
|
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unknown mode: " + mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +405,7 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
|
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
|
||||||
processDefinitionAnnotations(codeInfo);
|
processDefinitionAnnotations(codeInfo);
|
||||||
return codeInfo;
|
return codeInfo;
|
||||||
} catch (Throwable e) {
|
} catch (StackOverflowError | Exception e) {
|
||||||
addError("Code generation failed", e);
|
addError("Code generation failed", e);
|
||||||
return new SimpleCodeInfo(Utils.getStackTrace(e));
|
return new SimpleCodeInfo(Utils.getStackTrace(e));
|
||||||
}
|
}
|
||||||
@@ -610,17 +614,9 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
return parentClass;
|
return parentClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateParentClass() {
|
public void notInner() {
|
||||||
if (clsInfo.isInner()) {
|
this.clsInfo.notInner(root);
|
||||||
ClassNode parent = root.resolveClass(clsInfo.getParentClass());
|
this.parentClass = this;
|
||||||
if (parent != null) {
|
|
||||||
parentClass = parent;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// undo inner mark in class info
|
|
||||||
clsInfo.notInner(root);
|
|
||||||
}
|
|
||||||
parentClass = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -630,32 +626,36 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void rename(String newName) {
|
public void rename(String newName) {
|
||||||
int lastDot = newName.lastIndexOf('.');
|
if (newName.indexOf('.') == -1) {
|
||||||
if (lastDot == -1) {
|
|
||||||
clsInfo.changeShortName(newName);
|
clsInfo.changeShortName(newName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (clsInfo.isInner()) {
|
// full name provided
|
||||||
addWarn("Can't change package for inner class: " + this + " to " + newName);
|
ClassInfo newClsInfo = ClassInfo.fromNameWithoutCache(root, newName, clsInfo.isInner());
|
||||||
return;
|
|
||||||
}
|
|
||||||
// change class package
|
// change class package
|
||||||
String newPkg = newName.substring(0, lastDot);
|
String newPkg = newClsInfo.getPackage();
|
||||||
String newShortName = newName.substring(lastDot + 1);
|
String newShortName = newClsInfo.getShortName();
|
||||||
if (changeClassNodePackage(newPkg)) {
|
if (clsInfo.isInner()) {
|
||||||
clsInfo.changePkgAndName(newPkg, newShortName);
|
if (!newPkg.equals(clsInfo.getPackage())) {
|
||||||
} else {
|
addWarn("Can't change package for inner class: " + this + " to " + newName);
|
||||||
|
}
|
||||||
clsInfo.changeShortName(newShortName);
|
clsInfo.changeShortName(newShortName);
|
||||||
|
} else {
|
||||||
|
if (changeClassNodePackage(newPkg)) {
|
||||||
|
clsInfo.changePkgAndName(newPkg, newShortName);
|
||||||
|
} else {
|
||||||
|
clsInfo.changeShortName(newShortName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean changeClassNodePackage(String fullPkg) {
|
private boolean changeClassNodePackage(String fullPkg) {
|
||||||
if (clsInfo.isInner()) {
|
|
||||||
throw new JadxRuntimeException("Can't change package for inner class: " + clsInfo);
|
|
||||||
}
|
|
||||||
if (fullPkg.equals(clsInfo.getAliasPkg())) {
|
if (fullPkg.equals(clsInfo.getAliasPkg())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (clsInfo.isInner()) {
|
||||||
|
throw new JadxRuntimeException("Can't change package for inner class: " + clsInfo);
|
||||||
|
}
|
||||||
root.removeClsFromPackage(packageNode, this);
|
root.removeClsFromPackage(packageNode, this);
|
||||||
packageNode = PackageNode.getForClass(root, fullPkg, this);
|
packageNode = PackageNode.getForClass(root, fullPkg, this);
|
||||||
root.sortPackages();
|
root.sortPackages();
|
||||||
@@ -876,7 +876,7 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
code.startLine(String.format("###### Class %s (%s)", getFullName(), getRawName()));
|
code.startLine(String.format("###### Class %s (%s)", getFullName(), getRawName()));
|
||||||
try {
|
try {
|
||||||
code.startLine(clsData.getDisassembledCode());
|
code.startLine(clsData.getDisassembledCode());
|
||||||
} catch (Throwable e) {
|
} catch (Exception e) {
|
||||||
code.startLine("Failed to disassemble class:");
|
code.startLine("Failed to disassemble class:");
|
||||||
code.startLine(Utils.getStackTrace(e));
|
code.startLine(Utils.getStackTrace(e));
|
||||||
}
|
}
|
||||||
@@ -963,7 +963,11 @@ public class ClassNode extends NotificationAttrNode
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getInputFileName() {
|
public String getInputFileName() {
|
||||||
return clsData == null ? "synthetic" : clsData.getInputFileName();
|
return inputFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInputFileName(String inputFileName) {
|
||||||
|
this.inputFileName = inputFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JavaClass getJavaNode() {
|
public JavaClass getJavaNode() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package jadx.core.dex.nodes;
|
package jadx.core.dex.nodes;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AttrNode;
|
import jadx.core.dex.attributes.AttrNode;
|
||||||
@@ -14,7 +14,9 @@ public final class InsnContainer extends AttrNode implements IBlock {
|
|||||||
private final List<InsnNode> insns;
|
private final List<InsnNode> insns;
|
||||||
|
|
||||||
public InsnContainer(InsnNode insn) {
|
public InsnContainer(InsnNode insn) {
|
||||||
this.insns = Collections.singletonList(insn);
|
List<InsnNode> list = new ArrayList<>(1);
|
||||||
|
list.add(insn);
|
||||||
|
this.insns = list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InsnContainer(List<InsnNode> insns) {
|
public InsnContainer(List<InsnNode> insns) {
|
||||||
@@ -28,11 +30,11 @@ public final class InsnContainer extends AttrNode implements IBlock {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String baseString() {
|
public String baseString() {
|
||||||
return Integer.toString(insns.size());
|
return "IC";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "InsnContainer:" + insns.size();
|
return "InsnContainer";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,6 +217,19 @@ public class InsnNode extends LineAttrNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isExitEdgeInsn() {
|
||||||
|
switch (getType()) {
|
||||||
|
case RETURN:
|
||||||
|
case THROW:
|
||||||
|
case CONTINUE:
|
||||||
|
case BREAK:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean canRemoveResult() {
|
public boolean canRemoveResult() {
|
||||||
switch (getType()) {
|
switch (getType()) {
|
||||||
case INVOKE:
|
case INVOKE:
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import static jadx.core.utils.Utils.lockList;
|
|||||||
|
|
||||||
public class MethodNode extends NotificationAttrNode implements IMethodDetails, ILoadable, ICodeNode, Comparable<MethodNode> {
|
public class MethodNode extends NotificationAttrNode implements IMethodDetails, ILoadable, ICodeNode, Comparable<MethodNode> {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
|
private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
|
||||||
|
private static final InsnNode[] EMPTY_INSN_ARRAY = new InsnNode[0];
|
||||||
|
|
||||||
private final MethodInfo mthInfo;
|
private final MethodInfo mthInfo;
|
||||||
private final ClassNode parentClass;
|
private final ClassNode parentClass;
|
||||||
@@ -154,8 +155,14 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
this.regsCount = codeReader.getRegistersCount();
|
this.regsCount = codeReader.getRegistersCount();
|
||||||
this.argsStartReg = codeReader.getArgsStartReg();
|
this.argsStartReg = codeReader.getArgsStartReg();
|
||||||
initArguments(this.argTypes);
|
initArguments(this.argTypes);
|
||||||
InsnDecoder decoder = new InsnDecoder(this);
|
|
||||||
this.instructions = decoder.process(codeReader);
|
if (contains(AType.JADX_ERROR)) {
|
||||||
|
// don't load instructions for method with errors
|
||||||
|
this.instructions = EMPTY_INSN_ARRAY;
|
||||||
|
} else {
|
||||||
|
InsnDecoder decoder = new InsnDecoder(this);
|
||||||
|
this.instructions = decoder.process(codeReader);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (!noCode) {
|
if (!noCode) {
|
||||||
unload();
|
unload();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package jadx.core.dex.nodes;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -188,6 +189,14 @@ public class PackageNode extends LineAttrNode
|
|||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ClassNode> getClassesNoDup() {
|
||||||
|
return classes.stream()
|
||||||
|
.map(ClassNode::getClassInfo)
|
||||||
|
.collect(Collectors.toSet())
|
||||||
|
.stream()
|
||||||
|
.map(e -> root.resolveClass(e)).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
public JavaPackage getJavaNode() {
|
public JavaPackage getJavaNode() {
|
||||||
return javaNode;
|
return javaNode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -42,6 +45,7 @@ import jadx.core.dex.info.MethodInfo;
|
|||||||
import jadx.core.dex.info.PackageInfo;
|
import jadx.core.dex.info.PackageInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.utils.MethodUtils;
|
import jadx.core.dex.nodes.utils.MethodUtils;
|
||||||
|
import jadx.core.dex.nodes.utils.SelectFromDuplicates;
|
||||||
import jadx.core.dex.nodes.utils.TypeUtils;
|
import jadx.core.dex.nodes.utils.TypeUtils;
|
||||||
import jadx.core.dex.visitors.DepthTraversal;
|
import jadx.core.dex.visitors.DepthTraversal;
|
||||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
@@ -149,7 +153,7 @@ public class RootNode {
|
|||||||
public void finishClassLoad() {
|
public void finishClassLoad() {
|
||||||
if (classes.size() != clsMap.size()) {
|
if (classes.size() != clsMap.size()) {
|
||||||
// class name duplication detected
|
// class name duplication detected
|
||||||
markDuplicatedClasses(classes);
|
fixDuplicatedClasses();
|
||||||
}
|
}
|
||||||
classes = new ArrayList<>(clsMap.values());
|
classes = new ArrayList<>(clsMap.values());
|
||||||
|
|
||||||
@@ -159,7 +163,7 @@ public class RootNode {
|
|||||||
LOG.info("Loaded classes: {}, methods: {}, instructions: {}", classes.size(), mthCount, insnsCount);
|
LOG.info("Loaded classes: {}, methods: {}, instructions: {}", classes.size(), mthCount, insnsCount);
|
||||||
|
|
||||||
// sort classes by name, expect top classes before inner
|
// sort classes by name, expect top classes before inner
|
||||||
classes.sort(Comparator.comparing(ClassNode::getFullName));
|
classes.sort(Comparator.comparing(ClassNode::getRawName));
|
||||||
|
|
||||||
if (args.isMoveInnerClasses()) {
|
if (args.isMoveInnerClasses()) {
|
||||||
// detect and move inner classes
|
// detect and move inner classes
|
||||||
@@ -191,24 +195,30 @@ public class RootNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void markDuplicatedClasses(List<ClassNode> classes) {
|
private void fixDuplicatedClasses() {
|
||||||
classes.stream()
|
classes.stream()
|
||||||
.collect(Collectors.groupingBy(ClassNode::getClassInfo))
|
.collect(Collectors.groupingBy(ClassNode::getClassInfo))
|
||||||
.entrySet()
|
.entrySet()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(entry -> entry.getValue().size() > 1)
|
.filter(entry -> entry.getValue().size() > 1)
|
||||||
.forEach(entry -> {
|
.forEach(entry -> {
|
||||||
List<String> sources = Utils.collectionMap(entry.getValue(), ClassNode::getInputFileName);
|
ClassInfo clsInfo = entry.getKey();
|
||||||
LOG.warn("Found duplicated class: {}, count: {}. Only one will be loaded!\n {}",
|
List<ClassNode> dupClsList = entry.getValue();
|
||||||
entry.getKey(), entry.getValue().size(), String.join("\n ", sources));
|
ClassNode selectedCls = SelectFromDuplicates.process(dupClsList);
|
||||||
entry.getValue().forEach(cls -> {
|
|
||||||
String thisSource = cls.getInputFileName();
|
// keep only selected class in classes maps
|
||||||
String otherSourceStr = sources.stream()
|
clsMap.put(clsInfo, selectedCls);
|
||||||
.filter(s -> !s.equals(thisSource))
|
rawClsMap.put(selectedCls.getRawName(), selectedCls);
|
||||||
.sorted()
|
|
||||||
.collect(Collectors.joining("\n "));
|
String selectedSource = selectedCls.getInputFileName();
|
||||||
cls.addWarnComment("Classes with same name are omitted:\n " + otherSourceStr + '\n');
|
String sources = dupClsList.stream()
|
||||||
});
|
.map(ClassNode::getInputFileName)
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.joining("\n "));
|
||||||
|
LOG.warn("Found duplicated class: {}, count: {}, sources:"
|
||||||
|
+ "\n {}\n Keep class with source: {}, others will be removed.",
|
||||||
|
clsInfo, dupClsList.size(), sources, selectedSource);
|
||||||
|
selectedCls.addWarnComment("Classes with same name are omitted, all sources:\n " + sources + '\n');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,10 +319,8 @@ public class RootNode {
|
|||||||
ClassInfo clsInfo = cls.getClassInfo();
|
ClassInfo clsInfo = cls.getClassInfo();
|
||||||
ClassNode parent = resolveParentClass(clsInfo);
|
ClassNode parent = resolveParentClass(clsInfo);
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
clsMap.remove(clsInfo);
|
|
||||||
clsInfo.notInner(this);
|
|
||||||
clsMap.put(clsInfo, cls);
|
|
||||||
updated.add(cls);
|
updated.add(cls);
|
||||||
|
cls.notInner();
|
||||||
} else {
|
} else {
|
||||||
parent.addInnerClass(cls);
|
parent.addInnerClass(cls);
|
||||||
}
|
}
|
||||||
@@ -323,7 +331,6 @@ public class RootNode {
|
|||||||
innerCls.getClassInfo().updateNames(this);
|
innerCls.getClassInfo().updateNames(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
classes.forEach(ClassNode::updateParentClass);
|
|
||||||
for (PackageNode pkg : packages) {
|
for (PackageNode pkg : packages) {
|
||||||
pkg.getClasses().removeIf(cls -> cls.getClassInfo().isInner());
|
pkg.getClasses().removeIf(cls -> cls.getClassInfo().isInner());
|
||||||
}
|
}
|
||||||
@@ -345,6 +352,19 @@ public class RootNode {
|
|||||||
preDecompilePasses = DebugChecks.insertPasses(preDecompilePasses);
|
preDecompilePasses = DebugChecks.insertPasses(preDecompilePasses);
|
||||||
processClasses = new ProcessClass(DebugChecks.insertPasses(processClasses.getPasses()));
|
processClasses = new ProcessClass(DebugChecks.insertPasses(processClasses.getPasses()));
|
||||||
}
|
}
|
||||||
|
List<String> disabledPasses = args.getDisabledPasses();
|
||||||
|
if (!disabledPasses.isEmpty()) {
|
||||||
|
Set<String> disabledSet = new HashSet<>(disabledPasses);
|
||||||
|
Predicate<IDexTreeVisitor> filter = p -> {
|
||||||
|
if (disabledSet.contains(p.getName())) {
|
||||||
|
LOG.debug("Disable pass: {}", p.getName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
preDecompilePasses.removeIf(filter);
|
||||||
|
processClasses.getPasses().removeIf(filter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void runPreDecompileStage() {
|
public void runPreDecompileStage() {
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package jadx.core.dex.nodes.utils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select best class from list of classes with same full name
|
||||||
|
* Current implementation: use class with source file as 'classesN.dex' where N is minimal
|
||||||
|
*/
|
||||||
|
public class SelectFromDuplicates {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SelectFromDuplicates.class);
|
||||||
|
|
||||||
|
private static final Pattern CLASSES_DEX_PATTERN = Pattern.compile("classes([1-9]\\d*)\\.dex");
|
||||||
|
|
||||||
|
public static ClassNode process(List<ClassNode> dupClsList) {
|
||||||
|
ClassNode bestCls = null;
|
||||||
|
int bestClsIndex = -1;
|
||||||
|
for (ClassNode clsNode : dupClsList) {
|
||||||
|
boolean selectCurrent = false;
|
||||||
|
if (bestCls == null) {
|
||||||
|
selectCurrent = true;
|
||||||
|
} else {
|
||||||
|
int clsIndex = getClassesIndex(clsNode.getInputFileName());
|
||||||
|
if (clsIndex != -1) {
|
||||||
|
if (bestClsIndex != -1) {
|
||||||
|
// if both are valid, the lower index has precedence
|
||||||
|
if (clsIndex < bestClsIndex) {
|
||||||
|
selectCurrent = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// valid dex names have precedence
|
||||||
|
selectCurrent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selectCurrent) {
|
||||||
|
bestCls = clsNode;
|
||||||
|
bestClsIndex = getClassesIndex(clsNode.getInputFileName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestCls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get N from classesN.dex
|
||||||
|
*
|
||||||
|
* @return -1 if source is not valid dex name
|
||||||
|
*/
|
||||||
|
private static int getClassesIndex(String source) {
|
||||||
|
if ("classes.dex".equals(source)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Matcher matcher = CLASSES_DEX_PATTERN.matcher(source);
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
String num = matcher.group(1);
|
||||||
|
if (num.equals("1")) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return Integer.parseInt(num);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.debug("Failed to parse source classes index", e);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -242,9 +242,9 @@ public class TypeUtils {
|
|||||||
}
|
}
|
||||||
Map<ArgType, ArgType> map = new HashMap<>(1 + invokeInsn.getArgsCount());
|
Map<ArgType, ArgType> map = new HashMap<>(1 + invokeInsn.getArgsCount());
|
||||||
addTypeVarMapping(map, mthDetails.getReturnType(), invokeInsn.getResult());
|
addTypeVarMapping(map, mthDetails.getReturnType(), invokeInsn.getResult());
|
||||||
int argCount = Math.min(mthDetails.getArgTypes().size(), invokeInsn.getArgsCount());
|
int argCount = Math.min(mthDetails.getArgTypes().size(), invokeInsn.getArgsCount() - invokeInsn.getFirstArgOffset());
|
||||||
for (int i = 0; i < argCount; i++) {
|
for (int i = 0; i < argCount; i++) {
|
||||||
addTypeVarMapping(map, mthDetails.getArgTypes().get(i), invokeInsn.getArg(i));
|
addTypeVarMapping(map, mthDetails.getArgTypes().get(i), invokeInsn.getArg(i + invokeInsn.getFirstArgOffset()));
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
|
|||||||
this.container = container;
|
this.container = container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDefaultCase() {
|
||||||
|
return keys.size() == 1 && keys.get(0) == DEFAULT_CASE_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
public List<Object> getKeys() {
|
public List<Object> getKeys() {
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
@@ -86,13 +90,13 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String baseString() {
|
public String baseString() {
|
||||||
return header.baseString();
|
return "SW:" + header.baseString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("Switch: ").append(cases.size());
|
sb.append("Switch: ").append(header.baseString());
|
||||||
for (CaseInfo caseInfo : cases) {
|
for (CaseInfo caseInfo : cases) {
|
||||||
List<String> keyStrings = Utils.collectionMap(caseInfo.getKeys(),
|
List<String> keyStrings = Utils.collectionMap(caseInfo.getKeys(),
|
||||||
k -> k == DEFAULT_CASE_KEY ? "default" : k.toString());
|
k -> k == DEFAULT_CASE_KEY ? "default" : k.toString());
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public class DepthTraversal {
|
|||||||
cls.getInnerClasses().forEach(inCls -> visit(visitor, inCls));
|
cls.getInnerClasses().forEach(inCls -> visit(visitor, inCls));
|
||||||
cls.getMethods().forEach(mth -> visit(visitor, mth));
|
cls.getMethods().forEach(mth -> visit(visitor, mth));
|
||||||
}
|
}
|
||||||
} catch (StackOverflowError | Exception e) {
|
} catch (StackOverflowError | BootstrapMethodError | Exception e) {
|
||||||
cls.addError(e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
|
cls.addError(e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ public class DepthTraversal {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
visitor.visit(mth);
|
visitor.visit(mth);
|
||||||
} catch (StackOverflowError | Exception e) {
|
} catch (StackOverflowError | BootstrapMethodError | Exception e) {
|
||||||
mth.addError(e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
|
mth.addError(e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.Collections;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.impl.SimpleCodeWriter;
|
import jadx.api.impl.SimpleCodeWriter;
|
||||||
@@ -30,6 +31,7 @@ import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP;
|
|||||||
public class DotGraphVisitor extends AbstractVisitor {
|
public class DotGraphVisitor extends AbstractVisitor {
|
||||||
|
|
||||||
private static final String NL = "\\l";
|
private static final String NL = "\\l";
|
||||||
|
private static final String NLQR = Matcher.quoteReplacement(NL);
|
||||||
private static final boolean PRINT_DOMINATORS = false;
|
private static final boolean PRINT_DOMINATORS = false;
|
||||||
private static final boolean PRINT_DOMINATORS_INFO = false;
|
private static final boolean PRINT_DOMINATORS_INFO = false;
|
||||||
|
|
||||||
@@ -324,7 +326,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
|||||||
.replace("\"", "\\\"")
|
.replace("\"", "\\\"")
|
||||||
.replace("-", "\\-")
|
.replace("-", "\\-")
|
||||||
.replace("|", "\\|")
|
.replace("|", "\\|")
|
||||||
.replaceAll("\\R", NL);
|
.replaceAll("\\R", NLQR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package jadx.core.dex.visitors;
|
package jadx.core.dex.visitors;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -153,12 +152,12 @@ public class MethodThrowsVisitor extends AbstractVisitor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
visitThrows(mth, exceptionType);
|
visitThrows(mth, exceptionType, excludedExceptions);
|
||||||
} else {
|
} else {
|
||||||
if (throwArg instanceof InsnWrapArg) {
|
if (throwArg instanceof InsnWrapArg) {
|
||||||
InsnWrapArg insnWrapArg = (InsnWrapArg) throwArg;
|
InsnWrapArg insnWrapArg = (InsnWrapArg) throwArg;
|
||||||
ArgType exceptionType = insnWrapArg.getType();
|
ArgType exceptionType = insnWrapArg.getType();
|
||||||
visitThrows(mth, exceptionType);
|
visitThrows(mth, exceptionType, excludedExceptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -180,7 +179,9 @@ public class MethodThrowsVisitor extends AbstractVisitor {
|
|||||||
MethodThrowsAttr cAttr = cMth.get(AType.METHOD_THROWS);
|
MethodThrowsAttr cAttr = cMth.get(AType.METHOD_THROWS);
|
||||||
MethodThrowsAttr attr = mth.get(AType.METHOD_THROWS);
|
MethodThrowsAttr attr = mth.get(AType.METHOD_THROWS);
|
||||||
if (attr != null && cAttr != null && !cAttr.getList().isEmpty()) {
|
if (attr != null && cAttr != null && !cAttr.getList().isEmpty()) {
|
||||||
attr.getList().addAll(filterExceptions(cAttr.getList(), excludedExceptions));
|
for (String argTypeStr : cAttr.getList()) {
|
||||||
|
visitThrows(mth, ArgType.object(argTypeStr), excludedExceptions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ClspClass clsDetails = root.getClsp().getClsDetails(classInfo.getType());
|
ClspClass clsDetails = root.getClsp().getClsDetails(classInfo.getType());
|
||||||
@@ -189,8 +190,8 @@ public class MethodThrowsVisitor extends AbstractVisitor {
|
|||||||
if (cMth != null && cMth.getThrows() != null && !cMth.getThrows().isEmpty()) {
|
if (cMth != null && cMth.getThrows() != null && !cMth.getThrows().isEmpty()) {
|
||||||
MethodThrowsAttr attr = mth.get(AType.METHOD_THROWS);
|
MethodThrowsAttr attr = mth.get(AType.METHOD_THROWS);
|
||||||
if (attr != null) {
|
if (attr != null) {
|
||||||
for (ArgType ex : cMth.getThrows()) {
|
for (ArgType argType : cMth.getThrows()) {
|
||||||
attr.getList().add(ex.getObject());
|
visitThrows(mth, argType, excludedExceptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,8 +200,14 @@ public class MethodThrowsVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void visitThrows(MethodNode mth, ArgType excType) {
|
private void visitThrows(MethodNode mth, ArgType excType, Set<String> excludedExceptions) {
|
||||||
if (excType.isTypeKnown() && isThrowsRequired(mth, excType)) {
|
if (excType.isTypeKnown() && isThrowsRequired(mth, excType)) {
|
||||||
|
for (String excludedException : excludedExceptions) {
|
||||||
|
if (isBaseException(excType.getObject(), excludedException)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mth.get(AType.METHOD_THROWS).getList().add(excType.getObject());
|
mth.get(AType.METHOD_THROWS).getList().add(excType.getObject());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +238,7 @@ public class MethodThrowsVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return is 'possibleParent' a exception class of 'exception'
|
* @return is 'possibleParent' an exception class of 'exception'
|
||||||
*/
|
*/
|
||||||
private boolean isBaseException(String exception, String possibleParent) {
|
private boolean isBaseException(String exception, String possibleParent) {
|
||||||
if (exception.equals(possibleParent)) {
|
if (exception.equals(possibleParent)) {
|
||||||
@@ -247,23 +254,6 @@ public class MethodThrowsVisitor extends AbstractVisitor {
|
|||||||
return root.getClsp().isImplements(type.getObject(), baseType.getObject());
|
return root.getClsp().isImplements(type.getObject(), baseType.getObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<String> filterExceptions(Set<String> exceptions, Set<String> excludedExceptions) {
|
|
||||||
Set<String> filteredExceptions = new HashSet<>();
|
|
||||||
for (String exception : exceptions) {
|
|
||||||
boolean filtered = false;
|
|
||||||
for (String excluded : excludedExceptions) {
|
|
||||||
filtered = exception.equals(excluded) || isBaseException(exception, excluded);
|
|
||||||
if (filtered) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!filtered) {
|
|
||||||
filteredExceptions.add(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filteredExceptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable MethodNode searchOverriddenMethod(ClassNode cls, MethodInfo mth, String signature) {
|
private @Nullable MethodNode searchOverriddenMethod(ClassNode cls, MethodInfo mth, String signature) {
|
||||||
// search by exact full signature (with return value) to fight obfuscation (see test
|
// search by exact full signature (with return value) to fight obfuscation (see test
|
||||||
// 'TestOverrideWithSameName')
|
// 'TestOverrideWithSameName')
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public class ProcessAnonymous extends AbstractVisitor {
|
|||||||
private static void processClass(ClassNode cls) {
|
private static void processClass(ClassNode cls) {
|
||||||
try {
|
try {
|
||||||
markAnonymousClass(cls);
|
markAnonymousClass(cls);
|
||||||
} catch (Throwable e) {
|
} catch (StackOverflowError | Exception e) {
|
||||||
cls.addError("Anonymous visitor error", e);
|
cls.addError("Anonymous visitor error", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,6 +231,9 @@ public class SimplifyVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ArgType castToType = (ArgType) castInsn.getIndex();
|
ArgType castToType = (ArgType) castInsn.getIndex();
|
||||||
|
if (isArithWideUpCast(parentInsn, argType, castToType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (!ArgType.isCastNeeded(mth.root(), argType, castToType)
|
if (!ArgType.isCastNeeded(mth.root(), argType, castToType)
|
||||||
|| isCastDuplicate(castInsn)
|
|| isCastDuplicate(castInsn)
|
||||||
|| shadowedByOuterCast(mth.root(), castToType, parentInsn)) {
|
|| shadowedByOuterCast(mth.root(), castToType, parentInsn)) {
|
||||||
@@ -243,6 +246,21 @@ public class SimplifyVisitor extends AbstractVisitor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep cast to wide types in arith instructions,
|
||||||
|
* because arguments type determine instruction used in result bytecode.
|
||||||
|
* Example: (long) i << 32 - without 'long' cast will be used 'int shift' instruction and result
|
||||||
|
* will be incorrect
|
||||||
|
*/
|
||||||
|
private static boolean isArithWideUpCast(@Nullable InsnNode parentInsn, ArgType argType, ArgType castToType) {
|
||||||
|
if (parentInsn != null
|
||||||
|
&& parentInsn.getType() == InsnType.ARITH
|
||||||
|
&& argType.isPrimitive() && castToType.isPrimitive()) {
|
||||||
|
return castToType.getRegCount() > argType.getRegCount();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isCastDuplicate(IndexInsnNode castInsn) {
|
private static boolean isCastDuplicate(IndexInsnNode castInsn) {
|
||||||
InsnArg arg = castInsn.getArg(0);
|
InsnArg arg = castInsn.getArg(0);
|
||||||
if (arg.isRegister()) {
|
if (arg.isRegister()) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import jadx.api.plugins.input.data.attributes.IJadxAttrType;
|
|||||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
@@ -315,6 +316,13 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
if (mergeConstReturn(mth)) {
|
if (mergeConstReturn(mth)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (CodeFeaturesAttr.contains(mth, CodeFeaturesAttr.CodeFeature.SWITCH)) {
|
||||||
|
for (BlockNode basicBlock : mth.getBasicBlocks()) {
|
||||||
|
if (duplicateSimpleMoveBlock(mth, basicBlock)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return splitExitBlocks(mth);
|
return splitExitBlocks(mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,6 +391,65 @@ public class BlockProcessor extends AbstractVisitor {
|
|||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicate block if it contains only one 'move' insn and all predecessors are 'switch' and 'if'.
|
||||||
|
* This will help to resolve switch cases order and fallthrough detection
|
||||||
|
* because such move blocks can be deduplicated by compiler.
|
||||||
|
*/
|
||||||
|
private static boolean duplicateSimpleMoveBlock(MethodNode mth, BlockNode block) {
|
||||||
|
List<InsnNode> insns = block.getInstructions();
|
||||||
|
if (insns.size() == 1 && block.getSuccessors().size() == 1) {
|
||||||
|
InsnNode insn = insns.get(0);
|
||||||
|
if (insn.getType() == InsnType.MOVE) {
|
||||||
|
List<BlockNode> preds = block.getPredecessors();
|
||||||
|
int predSize = preds.size();
|
||||||
|
if (predSize >= 3 && onlySwitchAndIfInLastInsns(preds)) {
|
||||||
|
// confirmed, duplicate block
|
||||||
|
BlockNode successor = block.getSuccessors().get(0);
|
||||||
|
List<BlockNode> predsCopy = new ArrayList<>(preds);
|
||||||
|
for (int i = 1; i < predSize; i++) {
|
||||||
|
BlockNode pred = predsCopy.get(i);
|
||||||
|
BlockNode newBlock = BlockSplitter.startNewBlock(mth, -1);
|
||||||
|
newBlock.add(AFlag.SYNTHETIC);
|
||||||
|
for (InsnNode oldInsn : block.getInstructions()) {
|
||||||
|
InsnNode copyInsn = oldInsn.copyWithoutSsa();
|
||||||
|
copyInsn.add(AFlag.SYNTHETIC);
|
||||||
|
newBlock.getInstructions().add(copyInsn);
|
||||||
|
}
|
||||||
|
newBlock.copyAttributesFrom(block);
|
||||||
|
BlockSplitter.replaceConnection(pred, block, newBlock);
|
||||||
|
BlockSplitter.connect(newBlock, successor);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean onlySwitchAndIfInLastInsns(List<BlockNode> preds) {
|
||||||
|
boolean hasSwitch = false;
|
||||||
|
boolean hasIf = false;
|
||||||
|
for (BlockNode pred : preds) {
|
||||||
|
InsnNode lastInsn = BlockUtils.getLastInsn(pred);
|
||||||
|
if (lastInsn == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InsnType insnType = lastInsn.getType();
|
||||||
|
switch (insnType) {
|
||||||
|
case SWITCH:
|
||||||
|
hasSwitch = true;
|
||||||
|
break;
|
||||||
|
case IF:
|
||||||
|
hasIf = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasSwitch && hasIf;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean simplifyLoopEnd(MethodNode mth, LoopInfo loop) {
|
private static boolean simplifyLoopEnd(MethodNode mth, LoopInfo loop) {
|
||||||
BlockNode loopEnd = loop.getEnd();
|
BlockNode loopEnd = loop.getEnd();
|
||||||
if (loopEnd.getSuccessors().size() <= 1) {
|
if (loopEnd.getSuccessors().size() <= 1) {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class PostDominatorTree {
|
|||||||
}
|
}
|
||||||
mth.addInfoComment("Infinite loop detected, blocks: " + blocksDelta + ", insns: " + insnsCount);
|
mth.addInfoComment("Infinite loop detected, blocks: " + blocksDelta + ", insns: " + insnsCount);
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (StackOverflowError | Exception e) {
|
||||||
// show error as a warning because this info not always used
|
// show error as a warning because this info not always used
|
||||||
mth.addWarnComment("Failed to build post-dominance tree", e);
|
mth.addWarnComment("Failed to build post-dominance tree", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType type, String varName) {
|
public static boolean applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType type, String varName) {
|
||||||
TypeUpdateResult result = mth.root().getTypeUpdate().applyWithWiderIgnoreUnknown(mth, ssaVar, type);
|
TypeUpdateResult result = mth.root().getTypeUpdate().applyDebugInfo(mth, ssaVar, type);
|
||||||
if (result == TypeUpdateResult.REJECT) {
|
if (result == TypeUpdateResult.REJECT) {
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth);
|
LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
|||||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
import jadx.core.deobf.NameMapper;
|
import jadx.core.deobf.NameMapper;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
@@ -87,7 +88,7 @@ public class ProcessKotlinInternals extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void processMth(MethodNode mth) {
|
private void processMth(MethodNode mth) {
|
||||||
if (mth.isNoCode()) {
|
if (mth.isNoCode() || mth.contains(AType.JADX_ERROR)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (BlockNode block : mth.getBasicBlocks()) {
|
for (BlockNode block : mth.getBasicBlocks()) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public abstract class AbstractRegionVisitor implements IRegionVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processBlock(MethodNode mth, IBlock container) {
|
public void processBlock(MethodNode mth, IBlock block) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -30,7 +30,17 @@ public class IfRegionVisitor extends AbstractVisitor {
|
|||||||
process(mth);
|
process(mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void process(MethodNode mth) {
|
public static void processIfRequested(MethodNode mth) {
|
||||||
|
if (mth.contains(AFlag.REQUEST_IF_REGION_OPTIMIZE)) {
|
||||||
|
try {
|
||||||
|
process(mth);
|
||||||
|
} finally {
|
||||||
|
mth.remove(AFlag.REQUEST_IF_REGION_OPTIMIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void process(MethodNode mth) {
|
||||||
TernaryMod.process(mth);
|
TernaryMod.process(mth);
|
||||||
DepthRegionTraversal.traverse(mth, PROCESS_IF_REGION_VISITOR);
|
DepthRegionTraversal.traverse(mth, PROCESS_IF_REGION_VISITOR);
|
||||||
DepthRegionTraversal.traverseIterative(mth, REMOVE_REDUNDANT_ELSE_VISITOR);
|
DepthRegionTraversal.traverseIterative(mth, REMOVE_REDUNDANT_ELSE_VISITOR);
|
||||||
@@ -48,7 +58,7 @@ public class IfRegionVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "UnnecessaryReturnStatement", "StatementWithEmptyBody" })
|
@SuppressWarnings({ "UnnecessaryReturnStatement" })
|
||||||
private static void orderBranches(MethodNode mth, IfRegion ifRegion) {
|
private static void orderBranches(MethodNode mth, IfRegion ifRegion) {
|
||||||
if (RegionUtils.isEmpty(ifRegion.getElseRegion())) {
|
if (RegionUtils.isEmpty(ifRegion.getElseRegion())) {
|
||||||
return;
|
return;
|
||||||
@@ -158,7 +168,7 @@ public class IfRegionVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("StatementWithEmptyBody")
|
@SuppressWarnings("UnnecessaryParentheses")
|
||||||
private static boolean removeRedundantElseBlock(MethodNode mth, IfRegion ifRegion) {
|
private static boolean removeRedundantElseBlock(MethodNode mth, IfRegion ifRegion) {
|
||||||
if (ifRegion.getElseRegion() == null) {
|
if (ifRegion.getElseRegion() == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -53,10 +53,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
|||||||
@Override
|
@Override
|
||||||
public void visit(MethodNode mth) {
|
public void visit(MethodNode mth) {
|
||||||
DepthRegionTraversal.traverse(mth, this);
|
DepthRegionTraversal.traverse(mth, this);
|
||||||
if (mth.contains(AFlag.REQUEST_IF_REGION_OPTIMIZE)) {
|
IfRegionVisitor.processIfRequested(mth);
|
||||||
IfRegionVisitor.process(mth);
|
|
||||||
mth.remove(AFlag.REQUEST_IF_REGION_OPTIMIZE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -396,7 +393,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
|||||||
if (insn.getType() == InsnType.INVOKE) {
|
if (insn.getType() == InsnType.INVOKE) {
|
||||||
InvokeNode inv = (InvokeNode) insn;
|
InvokeNode inv = (InvokeNode) insn;
|
||||||
MethodInfo callMth = inv.getCallMth();
|
MethodInfo callMth = inv.getCallMth();
|
||||||
if (inv.getInvokeType() == InvokeType.INTERFACE
|
if ((inv.getInvokeType() == InvokeType.INTERFACE || inv.getInvokeType() == InvokeType.VIRTUAL)
|
||||||
&& callMth.getShortId().equals(mthId)) {
|
&& callMth.getShortId().equals(mthId)) {
|
||||||
if (declClsFullName == null) {
|
if (declClsFullName == null) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
package jadx.core.dex.visitors.regions;
|
package jadx.core.dex.visitors.regions;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.IBlock;
|
|
||||||
import jadx.core.dex.nodes.IContainer;
|
import jadx.core.dex.nodes.IContainer;
|
||||||
import jadx.core.dex.nodes.IRegion;
|
import jadx.core.dex.nodes.IRegion;
|
||||||
import jadx.core.dex.nodes.InsnContainer;
|
import jadx.core.dex.nodes.InsnContainer;
|
||||||
@@ -23,11 +14,9 @@ import jadx.core.dex.nodes.MethodNode;
|
|||||||
import jadx.core.dex.regions.Region;
|
import jadx.core.dex.regions.Region;
|
||||||
import jadx.core.dex.regions.SwitchRegion;
|
import jadx.core.dex.regions.SwitchRegion;
|
||||||
import jadx.core.dex.regions.loops.LoopRegion;
|
import jadx.core.dex.regions.loops.LoopRegion;
|
||||||
import jadx.core.utils.RegionUtils;
|
import jadx.core.dex.visitors.regions.maker.SwitchRegionMaker;
|
||||||
|
|
||||||
final class PostProcessRegions extends AbstractRegionVisitor {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(PostProcessRegions.class);
|
|
||||||
|
|
||||||
|
public final class PostProcessRegions extends AbstractRegionVisitor {
|
||||||
private static final IRegionVisitor INSTANCE = new PostProcessRegions();
|
private static final IRegionVisitor INSTANCE = new PostProcessRegions();
|
||||||
|
|
||||||
static void process(MethodNode mth) {
|
static void process(MethodNode mth) {
|
||||||
@@ -41,8 +30,7 @@ final class PostProcessRegions extends AbstractRegionVisitor {
|
|||||||
LoopRegion loop = (LoopRegion) region;
|
LoopRegion loop = (LoopRegion) region;
|
||||||
loop.mergePreCondition();
|
loop.mergePreCondition();
|
||||||
} else if (region instanceof SwitchRegion) {
|
} else if (region instanceof SwitchRegion) {
|
||||||
// insert 'break' in switch cases (run after try/catch insertion)
|
SwitchRegionMaker.insertBreaks(mth, (SwitchRegion) region);
|
||||||
processSwitch(mth, (SwitchRegion) region);
|
|
||||||
} else if (region instanceof Region) {
|
} else if (region instanceof Region) {
|
||||||
insertEdgeInsn((Region) region);
|
insertEdgeInsn((Region) region);
|
||||||
}
|
}
|
||||||
@@ -76,55 +64,6 @@ final class PostProcessRegions extends AbstractRegionVisitor {
|
|||||||
region.add(new InsnContainer(insns));
|
region.add(new InsnContainer(insns));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void processSwitch(MethodNode mth, SwitchRegion sw) {
|
|
||||||
for (IContainer c : sw.getBranches()) {
|
|
||||||
if (c instanceof Region) {
|
|
||||||
Set<IBlock> blocks = new HashSet<>();
|
|
||||||
RegionUtils.getAllRegionBlocks(c, blocks);
|
|
||||||
if (blocks.isEmpty()) {
|
|
||||||
addBreakToContainer((Region) c);
|
|
||||||
} else {
|
|
||||||
for (IBlock block : blocks) {
|
|
||||||
if (block instanceof BlockNode) {
|
|
||||||
addBreakForBlock(mth, c, blocks, (BlockNode) block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addBreakToContainer(Region c) {
|
|
||||||
if (RegionUtils.hasExitEdge(c)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
List<InsnNode> insns = new ArrayList<>(1);
|
|
||||||
insns.add(new InsnNode(InsnType.BREAK, 0));
|
|
||||||
c.add(new InsnContainer(insns));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addBreakForBlock(MethodNode mth, IContainer c, Set<IBlock> blocks, BlockNode bn) {
|
|
||||||
for (BlockNode s : bn.getCleanSuccessors()) {
|
|
||||||
if (!blocks.contains(s)
|
|
||||||
&& !bn.contains(AFlag.ADDED_TO_REGION)
|
|
||||||
&& !s.contains(AFlag.FALL_THROUGH)) {
|
|
||||||
addBreak(mth, c, bn);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addBreak(MethodNode mth, IContainer c, BlockNode bn) {
|
|
||||||
IContainer blockContainer = RegionUtils.getBlockContainer(c, bn);
|
|
||||||
if (blockContainer instanceof Region) {
|
|
||||||
addBreakToContainer((Region) blockContainer);
|
|
||||||
} else if (c instanceof Region) {
|
|
||||||
addBreakToContainer((Region) c);
|
|
||||||
} else {
|
|
||||||
LOG.warn("Can't insert break, container: {}, block: {}, mth: {}", blockContainer, bn, mth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PostProcessRegions() {
|
private PostProcessRegions() {
|
||||||
// singleton
|
// singleton
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,267 @@
|
|||||||
|
package jadx.core.dex.visitors.regions;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
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.RegionRefAttr;
|
||||||
|
import jadx.core.dex.instructions.InsnType;
|
||||||
|
import jadx.core.dex.nodes.IBlock;
|
||||||
|
import jadx.core.dex.nodes.IBranchRegion;
|
||||||
|
import jadx.core.dex.nodes.IContainer;
|
||||||
|
import jadx.core.dex.nodes.IRegion;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.regions.SwitchRegion;
|
||||||
|
import jadx.core.dex.visitors.AbstractVisitor;
|
||||||
|
import jadx.core.dex.visitors.JadxVisitor;
|
||||||
|
import jadx.core.dex.visitors.regions.maker.SwitchRegionMaker;
|
||||||
|
import jadx.core.utils.BlockInsnPair;
|
||||||
|
import jadx.core.utils.BlockParentContainer;
|
||||||
|
import jadx.core.utils.BlockUtils;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
|
import jadx.core.utils.RegionUtils;
|
||||||
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
|
import static jadx.core.dex.attributes.nodes.CodeFeaturesAttr.CodeFeature.SWITCH;
|
||||||
|
|
||||||
|
@JadxVisitor(
|
||||||
|
name = "SwitchBreakVisitor",
|
||||||
|
desc = "Optimize 'break' instruction: common code extract, remove unreachable",
|
||||||
|
runAfter = LoopRegionVisitor.class // can add 'continue' at case end
|
||||||
|
)
|
||||||
|
public class SwitchBreakVisitor extends AbstractVisitor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(MethodNode mth) throws JadxException {
|
||||||
|
if (CodeFeaturesAttr.contains(mth, SWITCH)) {
|
||||||
|
runSwitchTraverse(mth, ExtractCommonBreak::new);
|
||||||
|
runSwitchTraverse(mth, RemoveUnreachableBreak::new);
|
||||||
|
IfRegionVisitor.processIfRequested(mth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void runSwitchTraverse(MethodNode mth, Supplier<BaseSwitchRegionVisitor> builder) {
|
||||||
|
DepthRegionTraversal.traverse(mth, new IterativeSwitchRegionVisitor(builder));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add common 'break' if 'break' or exit insn ('return', 'throw', 'continue') found in all branches.
|
||||||
|
* Remove exist common break if all branches contain exit insn.
|
||||||
|
*/
|
||||||
|
private static final class ExtractCommonBreak extends BaseSwitchRegionVisitor {
|
||||||
|
@Override
|
||||||
|
public void processRegion(MethodNode mth, IRegion region) {
|
||||||
|
if (region instanceof IBranchRegion && !(region instanceof SwitchRegion)) {
|
||||||
|
// if break in all branches extract to parent region
|
||||||
|
processBranchRegion(mth, region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processBranchRegion(MethodNode mth, IRegion region) {
|
||||||
|
IRegion parentRegion = region.getParent();
|
||||||
|
if (parentRegion.contains(AFlag.FALL_THROUGH)) {
|
||||||
|
// fallthrough case, can't extract break
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean dontAddCommonBreak = false;
|
||||||
|
IBlock lastParentBlock = RegionUtils.getLastBlock(parentRegion);
|
||||||
|
if (BlockUtils.containsExitInsn(lastParentBlock)) {
|
||||||
|
if (isBreakBlock(lastParentBlock)) {
|
||||||
|
// parent block already contains 'break'
|
||||||
|
dontAddCommonBreak = true;
|
||||||
|
} else {
|
||||||
|
// can't add 'break' after 'return', 'throw' or 'continue'
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<IContainer> branches = ((IBranchRegion) region).getBranches();
|
||||||
|
boolean removeCommonBreak = true; // all branches contain exit insns, common break is unreachable
|
||||||
|
List<BlockParentContainer> forBreakRemove = new ArrayList<>();
|
||||||
|
for (IContainer branch : branches) {
|
||||||
|
if (branch == null) {
|
||||||
|
removeCommonBreak = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
BlockInsnPair last = RegionUtils.getLastInsnWithBlock(branch);
|
||||||
|
if (last == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InsnNode lastInsn = last.getInsn();
|
||||||
|
if (lastInsn.getType() == InsnType.BREAK) {
|
||||||
|
IBlock block = last.getBlock();
|
||||||
|
IContainer parent = RegionUtils.getBlockContainer(branch, block);
|
||||||
|
forBreakRemove.add(new BlockParentContainer(parent, block));
|
||||||
|
removeCommonBreak = false;
|
||||||
|
} else if (!lastInsn.isExitEdgeInsn()) {
|
||||||
|
removeCommonBreak = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!forBreakRemove.isEmpty()) {
|
||||||
|
// common 'break' confirmed
|
||||||
|
for (BlockParentContainer breakData : forBreakRemove) {
|
||||||
|
removeBreak(breakData.getBlock(), breakData.getParent());
|
||||||
|
}
|
||||||
|
if (!dontAddCommonBreak) {
|
||||||
|
addBreakRegion.add(parentRegion);
|
||||||
|
// new 'break' might become 'common' for upper branch region, request to run checks again
|
||||||
|
requestReRun();
|
||||||
|
}
|
||||||
|
// removed 'break' may allow to use 'else-if' chain
|
||||||
|
mth.add(AFlag.REQUEST_IF_REGION_OPTIMIZE);
|
||||||
|
}
|
||||||
|
if (removeCommonBreak && lastParentBlock != null) {
|
||||||
|
removeBreak(lastParentBlock, parentRegion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RemoveUnreachableBreak extends BaseSwitchRegionVisitor {
|
||||||
|
@Override
|
||||||
|
public void processRegion(MethodNode mth, IRegion region) {
|
||||||
|
List<IContainer> subBlocks = region.getSubBlocks();
|
||||||
|
IContainer lastContainer = ListUtils.last(subBlocks);
|
||||||
|
if (lastContainer instanceof IBlock) {
|
||||||
|
IBlock block = (IBlock) lastContainer;
|
||||||
|
if (isBreakBlock(block) && isPrevInsnIsExit(block, subBlocks)) {
|
||||||
|
removeBreak(block, region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPrevInsnIsExit(IBlock breakBlock, List<IContainer> subBlocks) {
|
||||||
|
InsnNode prevInsn = null;
|
||||||
|
if (breakBlock.getInstructions().size() > 1) {
|
||||||
|
// check prev insn in same block
|
||||||
|
List<InsnNode> insns = breakBlock.getInstructions();
|
||||||
|
prevInsn = insns.get(insns.size() - 2);
|
||||||
|
} else if (subBlocks.size() > 1) {
|
||||||
|
IContainer prev = subBlocks.get(subBlocks.size() - 2);
|
||||||
|
if (prev instanceof IBlock) {
|
||||||
|
List<InsnNode> insns = ((IBlock) prev).getInstructions();
|
||||||
|
prevInsn = ListUtils.last(insns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prevInsn != null && prevInsn.isExitEdgeInsn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For every 'switch' region run new instance of provided 'switch' visitor.
|
||||||
|
* If rerun requested, run traverse for that visitor again.
|
||||||
|
*/
|
||||||
|
private static final class IterativeSwitchRegionVisitor extends AbstractRegionVisitor {
|
||||||
|
private final Supplier<BaseSwitchRegionVisitor> builder;
|
||||||
|
|
||||||
|
public IterativeSwitchRegionVisitor(Supplier<BaseSwitchRegionVisitor> builder) {
|
||||||
|
this.builder = builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leaveRegion(MethodNode mth, IRegion region) {
|
||||||
|
if (region instanceof SwitchRegion) {
|
||||||
|
SwitchRegion switchRegion = (SwitchRegion) region;
|
||||||
|
BaseSwitchRegionVisitor switchVisitor = builder.get();
|
||||||
|
switchVisitor.setCurrentSwitch(switchRegion);
|
||||||
|
boolean runAgain;
|
||||||
|
int k = 0;
|
||||||
|
do {
|
||||||
|
runAgain = false;
|
||||||
|
DepthRegionTraversal.traverse(mth, switchRegion, switchVisitor);
|
||||||
|
if (switchVisitor.isReRunRequested()) {
|
||||||
|
switchVisitor.reset();
|
||||||
|
runAgain = true;
|
||||||
|
}
|
||||||
|
if (k++ > 20) {
|
||||||
|
// 20 nested 'if' are not expected
|
||||||
|
mth.addWarnComment("Unexpected iteration count in SwitchBreakVisitor. Please report as an issue");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (runAgain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract static class BaseSwitchRegionVisitor extends AbstractRegionVisitor {
|
||||||
|
protected final Set<IRegion> addBreakRegion = new HashSet<>();
|
||||||
|
protected final Set<IContainer> cleanupSet = new HashSet<>();
|
||||||
|
protected SwitchRegion currentSwitch;
|
||||||
|
private boolean reRunRequested = false;
|
||||||
|
|
||||||
|
public abstract void processRegion(MethodNode mth, IRegion region);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean enterRegion(MethodNode mth, IRegion region) {
|
||||||
|
processRegion(mth, region);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leaveRegion(MethodNode mth, IRegion region) {
|
||||||
|
if (addBreakRegion.contains(region)) {
|
||||||
|
addBreakRegion.remove(region);
|
||||||
|
region.getSubBlocks().add(SwitchRegionMaker.buildBreakContainer(currentSwitch));
|
||||||
|
}
|
||||||
|
if (cleanupSet.contains(region)) {
|
||||||
|
cleanupSet.remove(region);
|
||||||
|
region.getSubBlocks().removeIf(r -> r.contains(AFlag.REMOVE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called before visitor rerun
|
||||||
|
*/
|
||||||
|
public void reset() {
|
||||||
|
reRunRequested = false;
|
||||||
|
addBreakRegion.clear();
|
||||||
|
cleanupSet.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requestReRun() {
|
||||||
|
reRunRequested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isReRunRequested() {
|
||||||
|
return reRunRequested;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentSwitch(SwitchRegion currentSwitch) {
|
||||||
|
this.currentSwitch = currentSwitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isBreakBlock(@Nullable IBlock block) {
|
||||||
|
if (block != null) {
|
||||||
|
InsnNode lastInsn = ListUtils.last(block.getInstructions());
|
||||||
|
if (lastInsn != null && lastInsn.getType() == InsnType.BREAK) {
|
||||||
|
RegionRefAttr regionRefAttr = lastInsn.get(AType.REGION_REF);
|
||||||
|
return regionRefAttr != null && regionRefAttr.getRegion() == currentSwitch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeBreak(IBlock breakBlock, IContainer parentContainer) {
|
||||||
|
List<InsnNode> instructions = breakBlock.getInstructions();
|
||||||
|
InsnNode last = ListUtils.last(instructions);
|
||||||
|
if (last != null && last.getType() == InsnType.BREAK) {
|
||||||
|
ListUtils.removeLast(instructions);
|
||||||
|
if (instructions.isEmpty()) {
|
||||||
|
breakBlock.add(AFlag.REMOVE);
|
||||||
|
cleanupSet.add(parentContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "SwitchBreakVisitor";
|
||||||
|
}
|
||||||
|
}
|
||||||
+122
-73
@@ -13,6 +13,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedType;
|
import jadx.api.plugins.input.data.annotations.EncodedType;
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.IAttributeNode;
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
@@ -23,17 +24,20 @@ import jadx.core.dex.instructions.IfNode;
|
|||||||
import jadx.core.dex.instructions.IfOp;
|
import jadx.core.dex.instructions.IfOp;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
import jadx.core.dex.instructions.InvokeNode;
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
|
import jadx.core.dex.instructions.PhiInsn;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||||
import jadx.core.dex.instructions.args.LiteralArg;
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
import jadx.core.dex.instructions.args.SSAVar;
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.IContainer;
|
import jadx.core.dex.nodes.IContainer;
|
||||||
import jadx.core.dex.nodes.IRegion;
|
import jadx.core.dex.nodes.IRegion;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.regions.SwitchRegion;
|
import jadx.core.dex.regions.SwitchRegion;
|
||||||
|
import jadx.core.dex.regions.conditions.Compare;
|
||||||
import jadx.core.dex.regions.conditions.IfCondition;
|
import jadx.core.dex.regions.conditions.IfCondition;
|
||||||
import jadx.core.dex.regions.conditions.IfRegion;
|
import jadx.core.dex.regions.conditions.IfRegion;
|
||||||
import jadx.core.dex.visitors.AbstractVisitor;
|
import jadx.core.dex.visitors.AbstractVisitor;
|
||||||
@@ -42,6 +46,7 @@ import jadx.core.utils.BlockUtils;
|
|||||||
import jadx.core.utils.InsnRemover;
|
import jadx.core.utils.InsnRemover;
|
||||||
import jadx.core.utils.InsnUtils;
|
import jadx.core.utils.InsnUtils;
|
||||||
import jadx.core.utils.RegionUtils;
|
import jadx.core.utils.RegionUtils;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
@JadxVisitor(
|
@JadxVisitor(
|
||||||
@@ -79,19 +84,21 @@ public class SwitchOverStringVisitor extends AbstractVisitor implements IRegionI
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int casesCount = switchRegion.getCases().size();
|
int casesCount = switchRegion.getCases().size();
|
||||||
|
boolean defaultCaseAdded = switchRegion.getCases().stream().anyMatch(SwitchRegion.CaseInfo::isDefaultCase);
|
||||||
|
int casesWithString = defaultCaseAdded ? casesCount - 1 : casesCount;
|
||||||
SSAVar strVar = strArg.getSVar();
|
SSAVar strVar = strArg.getSVar();
|
||||||
if (strVar.getUseCount() - 1 < casesCount) {
|
if (strVar.getUseCount() - 1 < casesWithString) {
|
||||||
// one 'hashCode' invoke and at least one 'equals' per case
|
// one 'hashCode' invoke and at least one 'equals' per case
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// quick checks done, start collecting data to create a new switch region
|
// quick checks done, start collecting data to create a new switch region
|
||||||
Map<InsnNode, String> strEqInsns = collectEqualsInsns(mth, strVar);
|
Map<InsnNode, String> strEqInsns = collectEqualsInsns(mth, strVar);
|
||||||
if (strEqInsns.size() < casesCount) {
|
if (strEqInsns.size() < casesWithString) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
SwitchData switchData = new SwitchData(mth, switchRegion);
|
SwitchData switchData = new SwitchData(mth, switchRegion);
|
||||||
switchData.setStrEqInsns(strEqInsns);
|
switchData.setStrEqInsns(strEqInsns);
|
||||||
switchData.setCases(new ArrayList<>(strEqInsns.size()));
|
switchData.setCases(new ArrayList<>(casesCount));
|
||||||
for (SwitchRegion.CaseInfo swCaseInfo : switchRegion.getCases()) {
|
for (SwitchRegion.CaseInfo swCaseInfo : switchRegion.getCases()) {
|
||||||
if (!processCase(switchData, swCaseInfo)) {
|
if (!processCase(switchData, swCaseInfo)) {
|
||||||
mth.addWarnComment("Failed to restore switch over string. Please report as a decompilation issue");
|
mth.addWarnComment("Failed to restore switch over string. Please report as a decompilation issue");
|
||||||
@@ -118,7 +125,7 @@ public class SwitchOverStringVisitor extends AbstractVisitor implements IRegionI
|
|||||||
// use string arg directly in switch
|
// use string arg directly in switch
|
||||||
swInsn.replaceArg(swInsn.getArg(0), strArg.duplicate());
|
swInsn.replaceArg(swInsn.getArg(0), strArg.duplicate());
|
||||||
return true;
|
return true;
|
||||||
} catch (Throwable e) {
|
} catch (StackOverflowError | Exception e) {
|
||||||
mth.addWarnComment("Failed to restore switch over string. Please report as a decompilation issue", e);
|
mth.addWarnComment("Failed to restore switch over string. Please report as a decompilation issue", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -151,38 +158,33 @@ public class SwitchOverStringVisitor extends AbstractVisitor implements IRegionI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
InsnRemover.removeAllMarked(mth);
|
InsnRemover.removeAllMarked(mth);
|
||||||
} catch (Throwable e) {
|
} catch (StackOverflowError | Exception e) {
|
||||||
mth.addWarnComment("Failed to clean up code after switch over string restore", e);
|
mth.addWarnComment("Failed to clean up code after switch over string restore", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean mergeWithCode(SwitchData switchData) {
|
private boolean mergeWithCode(SwitchData switchData) {
|
||||||
|
// check for second switch
|
||||||
|
IContainer nextContainer = RegionUtils.getNextContainer(switchData.getMth(), switchData.getSwitchRegion());
|
||||||
|
if (!(nextContainer instanceof SwitchRegion)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SwitchRegion codeSwitch = (SwitchRegion) nextContainer;
|
||||||
|
InsnNode swInsn = BlockUtils.getLastInsnWithType(codeSwitch.getHeader(), InsnType.SWITCH);
|
||||||
|
if (swInsn == null || !swInsn.getArg(0).isRegister()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RegisterArg numArg = (RegisterArg) swInsn.getArg(0);
|
||||||
|
|
||||||
List<CaseData> cases = switchData.getCases();
|
List<CaseData> cases = switchData.getCases();
|
||||||
// search index assign in cases code
|
// search index assign in cases code
|
||||||
RegisterArg numArg = null;
|
|
||||||
int extracted = 0;
|
int extracted = 0;
|
||||||
for (CaseData caseData : cases) {
|
for (CaseData caseData : cases) {
|
||||||
IContainer container = caseData.getCode();
|
InsnNode numInsn = searchConstInsn(switchData, caseData, swInsn);
|
||||||
List<InsnNode> insns = RegionUtils.collectInsns(switchData.getMth(), container);
|
Integer num = extractConstNumber(switchData, numInsn, numArg);
|
||||||
insns.removeIf(i -> i.getType() == InsnType.BREAK);
|
if (num != null) {
|
||||||
if (insns.size() != 1) {
|
caseData.setCodeNum(num);
|
||||||
continue;
|
extracted++;
|
||||||
}
|
|
||||||
InsnNode numInsn = insns.get(0);
|
|
||||||
if (numInsn.getArgsCount() == 1) {
|
|
||||||
Object constVal = InsnUtils.getConstValueByArg(switchData.getMth().root(), numInsn.getArg(0));
|
|
||||||
if (constVal instanceof LiteralArg) {
|
|
||||||
if (numArg == null) {
|
|
||||||
numArg = numInsn.getResult();
|
|
||||||
} else {
|
|
||||||
if (!numArg.sameCodeVar(numInsn.getResult())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int num = (int) ((LiteralArg) constVal).getLiteral();
|
|
||||||
caseData.setCodeNum(num);
|
|
||||||
extracted++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (extracted == 0) {
|
if (extracted == 0) {
|
||||||
@@ -195,16 +197,7 @@ public class SwitchOverStringVisitor extends AbstractVisitor implements IRegionI
|
|||||||
// TODO: additional checks for found index numbers
|
// TODO: additional checks for found index numbers
|
||||||
cases.sort(Comparator.comparingInt(CaseData::getCodeNum));
|
cases.sort(Comparator.comparingInt(CaseData::getCodeNum));
|
||||||
|
|
||||||
// extract complete, second switch on 'numArg' should be the next region
|
// extract complete
|
||||||
IContainer nextContainer = RegionUtils.getNextContainer(switchData.getMth(), switchData.getSwitchRegion());
|
|
||||||
if (!(nextContainer instanceof SwitchRegion)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
SwitchRegion codeSwitch = (SwitchRegion) nextContainer;
|
|
||||||
InsnNode swInsn = BlockUtils.getLastInsnWithType(codeSwitch.getHeader(), InsnType.SWITCH);
|
|
||||||
if (swInsn == null || !swInsn.getArg(0).isSameCodeVar(numArg)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Map<Integer, CaseData> casesMap = new HashMap<>(cases.size());
|
Map<Integer, CaseData> casesMap = new HashMap<>(cases.size());
|
||||||
for (CaseData caseData : cases) {
|
for (CaseData caseData : cases) {
|
||||||
CaseData prev = casesMap.put(caseData.getCodeNum(), caseData);
|
CaseData prev = casesMap.put(caseData.getCodeNum(), caseData);
|
||||||
@@ -215,42 +208,39 @@ public class SwitchOverStringVisitor extends AbstractVisitor implements IRegionI
|
|||||||
block -> switchData.getToRemove().add(block));
|
block -> switchData.getToRemove().add(block));
|
||||||
}
|
}
|
||||||
|
|
||||||
final var newCases = new ArrayList<SwitchRegion.CaseInfo>();
|
List<SwitchRegion.CaseInfo> newCases = new ArrayList<>();
|
||||||
for (SwitchRegion.CaseInfo caseInfo : codeSwitch.getCases()) {
|
for (SwitchRegion.CaseInfo caseInfo : codeSwitch.getCases()) {
|
||||||
SwitchRegion.CaseInfo newCase = null;
|
SwitchRegion.CaseInfo newCase = null;
|
||||||
for (Object key : caseInfo.getKeys()) {
|
for (Object key : caseInfo.getKeys()) {
|
||||||
final Integer intKey = unwrapIntKey(key);
|
Integer intKey = unwrapIntKey(key);
|
||||||
if (intKey != null) {
|
if (intKey != null) {
|
||||||
final var caseData = casesMap.remove(intKey);
|
CaseData caseData = casesMap.remove(intKey);
|
||||||
if (caseData == null) {
|
if (caseData == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (newCase == null) {
|
if (newCase == null) {
|
||||||
final List<Object> keys = new ArrayList<>(caseData.getStrValues());
|
List<Object> keys = new ArrayList<>(caseData.getStrValues());
|
||||||
newCase = new SwitchRegion.CaseInfo(keys, caseInfo.getContainer());
|
newCase = new SwitchRegion.CaseInfo(keys, caseInfo.getContainer());
|
||||||
} else {
|
} else {
|
||||||
// merge cases
|
// merge cases
|
||||||
newCase.getKeys().addAll(caseData.getStrValues());
|
newCase.getKeys().addAll(caseData.getStrValues());
|
||||||
}
|
}
|
||||||
} else if (key == SwitchRegion.DEFAULT_CASE_KEY) {
|
} else if (key == SwitchRegion.DEFAULT_CASE_KEY) {
|
||||||
final var iterator = casesMap.entrySet().iterator();
|
var iterator = casesMap.entrySet().iterator();
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
final var caseData = iterator.next().getValue();
|
CaseData caseData = iterator.next().getValue();
|
||||||
if (newCase == null) {
|
if (newCase == null) {
|
||||||
final List<Object> keys = new ArrayList<>(caseData.getStrValues());
|
List<Object> keys = new ArrayList<>(caseData.getStrValues());
|
||||||
newCase = new SwitchRegion.CaseInfo(keys, caseInfo.getContainer());
|
newCase = new SwitchRegion.CaseInfo(keys, caseInfo.getContainer());
|
||||||
} else {
|
} else {
|
||||||
// merge cases
|
// merge cases
|
||||||
newCase.getKeys().addAll(caseData.getStrValues());
|
newCase.getKeys().addAll(caseData.getStrValues());
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newCase == null) {
|
if (newCase == null) {
|
||||||
newCase = new SwitchRegion.CaseInfo(new ArrayList<>(), caseInfo.getContainer());
|
newCase = new SwitchRegion.CaseInfo(new ArrayList<>(), caseInfo.getContainer());
|
||||||
}
|
}
|
||||||
|
|
||||||
newCase.getKeys().add(SwitchRegion.DEFAULT_CASE_KEY);
|
newCase.getKeys().add(SwitchRegion.DEFAULT_CASE_KEY);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@@ -258,25 +248,61 @@ public class SwitchOverStringVisitor extends AbstractVisitor implements IRegionI
|
|||||||
}
|
}
|
||||||
newCases.add(newCase);
|
newCases.add(newCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
switchData.setCodeSwitch(codeSwitch);
|
switchData.setCodeSwitch(codeSwitch);
|
||||||
switchData.setNumArg(numArg);
|
switchData.setNumArg(numArg);
|
||||||
switchData.setNewCases(newCases);
|
switchData.setNewCases(newCases);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @Nullable Integer extractConstNumber(SwitchData switchData, @Nullable InsnNode numInsn, RegisterArg numArg) {
|
||||||
|
if (numInsn == null || numInsn.getArgsCount() != 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object constVal = InsnUtils.getConstValueByArg(switchData.getMth().root(), numInsn.getArg(0));
|
||||||
|
if (constVal instanceof LiteralArg) {
|
||||||
|
if (numArg.sameCodeVar(numInsn.getResult())) {
|
||||||
|
return (int) ((LiteralArg) constVal).getLiteral();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable InsnNode searchConstInsn(SwitchData switchData, CaseData caseData, InsnNode swInsn) {
|
||||||
|
IContainer container = caseData.getCode();
|
||||||
|
if (container != null) {
|
||||||
|
List<InsnNode> insns = RegionUtils.collectInsns(switchData.getMth(), container);
|
||||||
|
insns.removeIf(i -> i.getType() == InsnType.BREAK);
|
||||||
|
if (insns.size() == 1) {
|
||||||
|
return insns.get(0);
|
||||||
|
}
|
||||||
|
} else if (caseData.getBlockRef() != null) {
|
||||||
|
// variable used unchanged on path from block ref
|
||||||
|
BlockNode blockRef = caseData.getBlockRef();
|
||||||
|
InsnArg swArg = swInsn.getArg(0);
|
||||||
|
if (swArg.isRegister()) {
|
||||||
|
InsnNode assignInsn = ((RegisterArg) swArg).getSVar().getAssignInsn();
|
||||||
|
if (assignInsn != null && assignInsn.getType() == InsnType.PHI) {
|
||||||
|
RegisterArg arg = ((PhiInsn) assignInsn).getArgByBlock(blockRef);
|
||||||
|
if (arg != null) {
|
||||||
|
return arg.getAssignInsn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private Integer unwrapIntKey(Object key) {
|
private Integer unwrapIntKey(Object key) {
|
||||||
if (key instanceof Integer) {
|
if (key instanceof Integer) {
|
||||||
return (Integer) key;
|
return (Integer) key;
|
||||||
} else if (key instanceof FieldNode) {
|
}
|
||||||
final var encodedValue = ((FieldNode) key).get(JadxAttrType.CONSTANT_VALUE);
|
if (key instanceof FieldNode) {
|
||||||
|
EncodedValue encodedValue = ((FieldNode) key).get(JadxAttrType.CONSTANT_VALUE);
|
||||||
if (encodedValue != null && encodedValue.getType() == EncodedType.ENCODED_INT) {
|
if (encodedValue != null && encodedValue.getType() == EncodedType.ENCODED_INT) {
|
||||||
return (Integer) encodedValue.getValue();
|
return (Integer) encodedValue.getValue();
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +325,11 @@ public class SwitchOverStringVisitor extends AbstractVisitor implements IRegionI
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean processCase(SwitchData switchData, SwitchRegion.CaseInfo caseInfo) {
|
private boolean processCase(SwitchData switchData, SwitchRegion.CaseInfo caseInfo) {
|
||||||
|
if (caseInfo.isDefaultCase()) {
|
||||||
|
CaseData caseData = new CaseData();
|
||||||
|
caseData.setCode(caseInfo.getContainer());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
AtomicBoolean fail = new AtomicBoolean(false);
|
AtomicBoolean fail = new AtomicBoolean(false);
|
||||||
RegionUtils.visitRegions(switchData.getMth(), caseInfo.getContainer(), region -> {
|
RegionUtils.visitRegions(switchData.getMth(), caseInfo.getContainer(), region -> {
|
||||||
if (fail.get()) {
|
if (fail.get()) {
|
||||||
@@ -324,30 +355,39 @@ public class SwitchOverStringVisitor extends AbstractVisitor implements IRegionI
|
|||||||
condition = condition.getArgs().get(0);
|
condition = condition.getArgs().get(0);
|
||||||
neg = true;
|
neg = true;
|
||||||
}
|
}
|
||||||
|
Compare compare = condition.getCompare();
|
||||||
|
if (compare == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
IfNode ifInsn = compare.getInsn();
|
||||||
|
InsnArg firstArg = ifInsn.getArg(0);
|
||||||
String str = null;
|
String str = null;
|
||||||
if (condition.isCompare()) {
|
if (firstArg.isInsnWrap()) {
|
||||||
IfNode ifInsn = condition.getCompare().getInsn();
|
str = switchData.getStrEqInsns().get(((InsnWrapArg) firstArg).getWrapInsn());
|
||||||
InsnArg firstArg = ifInsn.getArg(0);
|
|
||||||
if (firstArg.isInsnWrap()) {
|
|
||||||
str = switchData.getStrEqInsns().get(((InsnWrapArg) firstArg).getWrapInsn());
|
|
||||||
}
|
|
||||||
if (ifInsn.getOp() == IfOp.NE && ifInsn.getArg(1).isTrue()) {
|
|
||||||
neg = true;
|
|
||||||
}
|
|
||||||
if (ifInsn.getOp() == IfOp.EQ && ifInsn.getArg(1).isFalse()) {
|
|
||||||
neg = true;
|
|
||||||
}
|
|
||||||
if (str != null) {
|
|
||||||
switchData.getToRemove().add(ifInsn);
|
|
||||||
switchData.getToRemove().addAll(ifRegion.getConditionBlocks());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (str == null) {
|
if (str == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (ifInsn.getOp() == IfOp.NE && ifInsn.getArg(1).isTrue()) {
|
||||||
|
neg = true;
|
||||||
|
}
|
||||||
|
if (ifInsn.getOp() == IfOp.EQ && ifInsn.getArg(1).isFalse()) {
|
||||||
|
neg = true;
|
||||||
|
}
|
||||||
|
switchData.getToRemove().add(ifInsn);
|
||||||
|
switchData.getToRemove().addAll(ifRegion.getConditionBlocks());
|
||||||
|
|
||||||
CaseData caseData = new CaseData();
|
CaseData caseData = new CaseData();
|
||||||
caseData.getStrValues().add(str);
|
caseData.getStrValues().add(str);
|
||||||
caseData.setCode(neg ? ifRegion.getElseRegion() : ifRegion.getThenRegion());
|
|
||||||
|
IContainer codeContainer = neg ? ifRegion.getElseRegion() : ifRegion.getThenRegion();
|
||||||
|
if (codeContainer == null) {
|
||||||
|
// no code
|
||||||
|
// use last condition block for later data tracing
|
||||||
|
caseData.setBlockRef(Utils.last(ifRegion.getConditionBlocks()));
|
||||||
|
} else {
|
||||||
|
caseData.setCode(codeContainer);
|
||||||
|
}
|
||||||
return caseData;
|
return caseData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,21 +487,30 @@ public class SwitchOverStringVisitor extends AbstractVisitor implements IRegionI
|
|||||||
|
|
||||||
private static final class CaseData {
|
private static final class CaseData {
|
||||||
private final List<String> strValues = new ArrayList<>();
|
private final List<String> strValues = new ArrayList<>();
|
||||||
private IContainer code = null;
|
private @Nullable IContainer code = null;
|
||||||
|
private @Nullable BlockNode blockRef = null;
|
||||||
private int codeNum = -1;
|
private int codeNum = -1;
|
||||||
|
|
||||||
public List<String> getStrValues() {
|
public List<String> getStrValues() {
|
||||||
return strValues;
|
return strValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IContainer getCode() {
|
public @Nullable IContainer getCode() {
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCode(IContainer code) {
|
public void setCode(@Nullable IContainer code) {
|
||||||
this.code = code;
|
this.code = code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable BlockNode getBlockRef() {
|
||||||
|
return blockRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlockRef(@Nullable BlockNode blockRef) {
|
||||||
|
this.blockRef = blockRef;
|
||||||
|
}
|
||||||
|
|
||||||
public int getCodeNum() {
|
public int getCodeNum() {
|
||||||
return codeNum;
|
return codeNum;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
|||||||
int branchLine = Math.max(thenInsn.getSourceLine(), elseInsn.getSourceLine());
|
int branchLine = Math.max(thenInsn.getSourceLine(), elseInsn.getSourceLine());
|
||||||
ternInsn.setSourceLine(Math.max(ifRegion.getSourceLine(), branchLine));
|
ternInsn.setSourceLine(Math.max(ifRegion.getSourceLine(), branchLine));
|
||||||
|
|
||||||
|
thenInsn.setResult(null); // unset without unbind, SSA var still in use
|
||||||
InsnRemover.unbindResult(mth, elseInsn);
|
InsnRemover.unbindResult(mth, elseInsn);
|
||||||
|
|
||||||
// remove 'if' instruction
|
// remove 'if' instruction
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ public abstract class TracedRegionVisitor implements IRegionVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processBlock(MethodNode mth, IBlock container) {
|
public void processBlock(MethodNode mth, IBlock block) {
|
||||||
IRegion curRegion = regionStack.peek();
|
IRegion curRegion = regionStack.peek();
|
||||||
processBlockTraced(mth, container, curRegion);
|
processBlockTraced(mth, block, curRegion);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void processBlockTraced(MethodNode mth, IBlock container, IRegion currentRegion);
|
public abstract void processBlockTraced(MethodNode mth, IBlock block, IRegion parentRegion);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void leaveRegion(MethodNode mth, IRegion region) {
|
public void leaveRegion(MethodNode mth, IRegion region) {
|
||||||
|
|||||||
+12
-2
@@ -132,8 +132,18 @@ public class ExcHandlersRegionMaker {
|
|||||||
if (dom.contains(AFlag.REMOVE)) {
|
if (dom.contains(AFlag.REMOVE)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BitSet domFrontier = dom.getDomFrontier();
|
List<BlockNode> handlerExits = new ArrayList<>();
|
||||||
List<BlockNode> handlerExits = BlockUtils.bitSetToBlocks(mth, domFrontier);
|
|
||||||
|
BlockNode handlerOutBlock = BlockUtils.getTryAndHandlerCrossBlock(mth, handler);
|
||||||
|
if (handlerOutBlock != null) {
|
||||||
|
// ensure frontier's other predecessors comes from try end
|
||||||
|
handlerExits.add(handlerOutBlock);
|
||||||
|
} else {
|
||||||
|
// fallback to simple frontier
|
||||||
|
BitSet domFrontier = dom.getDomFrontier();
|
||||||
|
handlerExits.addAll(BlockUtils.bitSetToBlocks(mth, domFrontier));
|
||||||
|
}
|
||||||
|
|
||||||
boolean inLoop = mth.getLoopForBlock(start) != null;
|
boolean inLoop = mth.getLoopForBlock(start) != null;
|
||||||
for (BlockNode exit : handlerExits) {
|
for (BlockNode exit : handlerExits) {
|
||||||
if ((!inLoop || BlockUtils.isPathExists(start, exit))
|
if ((!inLoop || BlockUtils.isPathExists(start, exit))
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import jadx.core.dex.regions.conditions.IfCondition;
|
|||||||
import jadx.core.dex.regions.conditions.IfInfo;
|
import jadx.core.dex.regions.conditions.IfInfo;
|
||||||
import jadx.core.dex.regions.conditions.IfRegion;
|
import jadx.core.dex.regions.conditions.IfRegion;
|
||||||
import jadx.core.dex.regions.loops.LoopRegion;
|
import jadx.core.dex.regions.loops.LoopRegion;
|
||||||
|
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||||
import jadx.core.utils.BlockUtils;
|
import jadx.core.utils.BlockUtils;
|
||||||
import jadx.core.utils.blocks.BlockSet;
|
import jadx.core.utils.blocks.BlockSet;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
@@ -197,6 +198,21 @@ final class IfRegionMaker {
|
|||||||
info = new IfInfo(info, elseBlock, null);
|
info = new IfInfo(info, elseBlock, null);
|
||||||
info.setOutBlock(thenBlock);
|
info.setOutBlock(thenBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getPathCross may not find outBlock (e.g. one branch has return, outBlock definitely is
|
||||||
|
// null), so should check further
|
||||||
|
if (info.getOutBlock() == null) {
|
||||||
|
BlockNode scopeOutBlockThen = findScopeOutBlock(mth, info.getThenBlock());
|
||||||
|
BlockNode scopeOutBlockElse = findScopeOutBlock(mth, info.getElseBlock());
|
||||||
|
if (scopeOutBlockThen == null && scopeOutBlockElse != null) {
|
||||||
|
info.setOutBlock(scopeOutBlockElse);
|
||||||
|
} else if (scopeOutBlockThen != null && scopeOutBlockElse == null) {
|
||||||
|
info.setOutBlock(scopeOutBlockThen);
|
||||||
|
} else if (scopeOutBlockThen != null && scopeOutBlockThen == scopeOutBlockElse) {
|
||||||
|
info.setOutBlock(scopeOutBlockThen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (BlockUtils.isBackEdge(block, info.getOutBlock())) {
|
if (BlockUtils.isBackEdge(block, info.getOutBlock())) {
|
||||||
info.setOutBlock(null);
|
info.setOutBlock(null);
|
||||||
}
|
}
|
||||||
@@ -243,6 +259,33 @@ final class IfRegionMaker {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if startBlock is in a (try) scope, find the scope end as outBlock
|
||||||
|
*/
|
||||||
|
private @Nullable static BlockNode findScopeOutBlock(MethodNode mth, BlockNode startBlock) {
|
||||||
|
if (startBlock == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<BlockNode> domFrontiers = BlockUtils.bitSetToBlocks(mth, startBlock.getDomFrontier());
|
||||||
|
BlockNode scopeOutBlock = null;
|
||||||
|
|
||||||
|
// find handler from domFrontier(could be scope end), if domFrontier is handler
|
||||||
|
// and its topSplitter dominates branch block, then branch should end
|
||||||
|
for (BlockNode domFrontier : domFrontiers) {
|
||||||
|
ExcHandlerAttr handler = domFrontier.get(AType.EXC_HANDLER);
|
||||||
|
if (handler == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
BlockNode topSplitter = handler.getTryBlock().getTopSplitter();
|
||||||
|
if (startBlock.isDominator(topSplitter)) {
|
||||||
|
scopeOutBlock = BlockUtils.getTryAndHandlerCrossBlock(mth, handler.getHandler());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scopeOutBlock;
|
||||||
|
}
|
||||||
|
|
||||||
static IfInfo mergeNestedIfNodes(IfInfo currentIf) {
|
static IfInfo mergeNestedIfNodes(IfInfo currentIf) {
|
||||||
BlockNode curThen = currentIf.getThenBlock();
|
BlockNode curThen = currentIf.getThenBlock();
|
||||||
BlockNode curElse = currentIf.getElseBlock();
|
BlockNode curElse = currentIf.getElseBlock();
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import jadx.core.dex.trycatch.ExceptionHandler;
|
|||||||
import jadx.core.utils.BlockUtils;
|
import jadx.core.utils.BlockUtils;
|
||||||
import jadx.core.utils.ListUtils;
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.core.utils.RegionUtils;
|
import jadx.core.utils.RegionUtils;
|
||||||
|
import jadx.core.utils.blocks.BlockSet;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
import static jadx.core.utils.BlockUtils.getNextBlock;
|
import static jadx.core.utils.BlockUtils.getNextBlock;
|
||||||
@@ -349,7 +350,12 @@ final class LoopRegionMaker {
|
|||||||
|
|
||||||
if (!confirm) {
|
if (!confirm) {
|
||||||
BlockNode insertBlock = null;
|
BlockNode insertBlock = null;
|
||||||
while (exit != null) {
|
BlockSet visited = new BlockSet(mth);
|
||||||
|
while (true) {
|
||||||
|
if (exit == null || visited.contains(exit)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
visited.add(exit);
|
||||||
if (insertBlock != null && isPathExists(loopExit, exit)) {
|
if (insertBlock != null && isPathExists(loopExit, exit)) {
|
||||||
// found cross
|
// found cross
|
||||||
if (canInsertBreak(insertBlock)) {
|
if (canInsertBreak(insertBlock)) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.util.Deque;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ final class RegionStack {
|
|||||||
*
|
*
|
||||||
* @param exit boundary node, null will be ignored
|
* @param exit boundary node, null will be ignored
|
||||||
*/
|
*/
|
||||||
public void addExit(BlockNode exit) {
|
public void addExit(@Nullable BlockNode exit) {
|
||||||
if (exit != null) {
|
if (exit != null) {
|
||||||
curState.exits.add(exit);
|
curState.exits.add(exit);
|
||||||
}
|
}
|
||||||
@@ -95,7 +96,7 @@ final class RegionStack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeExit(BlockNode exit) {
|
public void removeExit(@Nullable BlockNode exit) {
|
||||||
if (exit != null) {
|
if (exit != null) {
|
||||||
curState.exits.remove(exit);
|
curState.exits.remove(exit);
|
||||||
}
|
}
|
||||||
|
|||||||
+73
-11
@@ -19,17 +19,24 @@ import jadx.core.dex.instructions.args.ArgType;
|
|||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.IContainer;
|
||||||
import jadx.core.dex.nodes.IRegion;
|
import jadx.core.dex.nodes.IRegion;
|
||||||
|
import jadx.core.dex.nodes.InsnContainer;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.regions.Region;
|
import jadx.core.dex.regions.Region;
|
||||||
import jadx.core.dex.regions.SwitchRegion;
|
import jadx.core.dex.regions.SwitchRegion;
|
||||||
|
import jadx.core.dex.visitors.regions.AbstractRegionVisitor;
|
||||||
|
import jadx.core.dex.visitors.regions.DepthRegionTraversal;
|
||||||
|
import jadx.core.dex.visitors.regions.SwitchBreakVisitor;
|
||||||
import jadx.core.utils.BlockUtils;
|
import jadx.core.utils.BlockUtils;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.core.utils.RegionUtils;
|
import jadx.core.utils.RegionUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
import jadx.core.utils.blocks.BlockSet;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
final class SwitchRegionMaker {
|
public final class SwitchRegionMaker {
|
||||||
private final MethodNode mth;
|
private final MethodNode mth;
|
||||||
private final RegionMaker regionMaker;
|
private final RegionMaker regionMaker;
|
||||||
|
|
||||||
@@ -61,21 +68,32 @@ final class SwitchRegionMaker {
|
|||||||
BlockNode out = calcSwitchOut(block, insn, stack);
|
BlockNode out = calcSwitchOut(block, insn, stack);
|
||||||
stack.addExit(out);
|
stack.addExit(out);
|
||||||
|
|
||||||
processFallThroughCases(sw, out, stack, blocksMap);
|
addCases(sw, out, stack, blocksMap);
|
||||||
removeEmptyCases(insn, sw, defCase);
|
removeEmptyCases(insn, sw, defCase);
|
||||||
|
|
||||||
stack.pop();
|
stack.pop();
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processFallThroughCases(SwitchRegion sw, @Nullable BlockNode out,
|
/**
|
||||||
|
* Insert 'break' for all cases in switch region
|
||||||
|
* Executed in {@link jadx.core.dex.visitors.regions.PostProcessRegions} after try/catch wrap to
|
||||||
|
* handle all blocks
|
||||||
|
*/
|
||||||
|
public static void insertBreaks(MethodNode mth, SwitchRegion sw) {
|
||||||
|
for (SwitchRegion.CaseInfo caseInfo : sw.getCases()) {
|
||||||
|
insertBreaksForCase(mth, sw, caseInfo.getContainer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCases(SwitchRegion sw, @Nullable BlockNode out,
|
||||||
RegionStack stack, Map<BlockNode, List<Object>> blocksMap) {
|
RegionStack stack, Map<BlockNode, List<Object>> blocksMap) {
|
||||||
Map<BlockNode, BlockNode> fallThroughCases = new LinkedHashMap<>();
|
Map<BlockNode, BlockNode> fallThroughCases = new LinkedHashMap<>();
|
||||||
if (out != null) {
|
if (out != null) {
|
||||||
// detect fallthrough cases
|
// detect fallthrough cases
|
||||||
BitSet caseBlocks = BlockUtils.blocksToBitSet(mth, blocksMap.keySet());
|
BitSet caseBlocks = BlockUtils.blocksToBitSet(mth, blocksMap.keySet());
|
||||||
caseBlocks.clear(out.getId());
|
caseBlocks.clear(out.getPos());
|
||||||
for (BlockNode successor : sw.getHeader().getCleanSuccessors()) {
|
for (BlockNode successor : sw.getHeader().getSuccessors()) {
|
||||||
BitSet df = successor.getDomFrontier();
|
BitSet df = successor.getDomFrontier();
|
||||||
if (df.intersects(caseBlocks)) {
|
if (df.intersects(caseBlocks)) {
|
||||||
BlockNode fallThroughBlock = getOneIntersectionBlock(out, caseBlocks, df);
|
BlockNode fallThroughBlock = getOneIntersectionBlock(out, caseBlocks, df);
|
||||||
@@ -93,31 +111,30 @@ final class SwitchRegionMaker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Map.Entry<BlockNode, List<Object>> entry : blocksMap.entrySet()) {
|
for (Map.Entry<BlockNode, List<Object>> entry : blocksMap.entrySet()) {
|
||||||
List<Object> keysList = entry.getValue();
|
List<Object> keysList = entry.getValue();
|
||||||
BlockNode caseBlock = entry.getKey();
|
BlockNode caseBlock = entry.getKey();
|
||||||
|
Region caseRegion;
|
||||||
if (stack.containsExit(caseBlock)) {
|
if (stack.containsExit(caseBlock)) {
|
||||||
sw.addCase(keysList, new Region(stack.peekRegion()));
|
caseRegion = new Region(stack.peekRegion());
|
||||||
} else {
|
} else {
|
||||||
BlockNode next = fallThroughCases.get(caseBlock);
|
BlockNode next = fallThroughCases.get(caseBlock);
|
||||||
stack.addExit(next);
|
stack.addExit(next);
|
||||||
Region caseRegion = regionMaker.makeRegion(caseBlock);
|
caseRegion = regionMaker.makeRegion(caseBlock);
|
||||||
stack.removeExit(next);
|
stack.removeExit(next);
|
||||||
if (next != null) {
|
if (next != null) {
|
||||||
next.add(AFlag.FALL_THROUGH);
|
next.add(AFlag.FALL_THROUGH);
|
||||||
caseRegion.add(AFlag.FALL_THROUGH);
|
caseRegion.add(AFlag.FALL_THROUGH);
|
||||||
}
|
}
|
||||||
sw.addCase(keysList, caseRegion);
|
|
||||||
// 'break' instruction will be inserted in RegionMakerVisitor.PostRegionVisitor
|
|
||||||
}
|
}
|
||||||
|
sw.addCase(keysList, caseRegion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private BlockNode getOneIntersectionBlock(BlockNode out, BitSet caseBlocks, BitSet fallThroughSet) {
|
private BlockNode getOneIntersectionBlock(BlockNode out, BitSet caseBlocks, BitSet fallThroughSet) {
|
||||||
BitSet caseExits = BlockUtils.copyBlocksBitSet(mth, fallThroughSet);
|
BitSet caseExits = BlockUtils.copyBlocksBitSet(mth, fallThroughSet);
|
||||||
caseExits.clear(out.getId());
|
caseExits.clear(out.getPos());
|
||||||
caseExits.and(caseBlocks);
|
caseExits.and(caseBlocks);
|
||||||
return BlockUtils.bitSetToOneBlock(mth, caseExits);
|
return BlockUtils.bitSetToOneBlock(mth, caseExits);
|
||||||
}
|
}
|
||||||
@@ -341,4 +358,49 @@ final class SwitchRegionMaker {
|
|||||||
}
|
}
|
||||||
return inserted;
|
return inserted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add break to every exit edge from 'case' region.
|
||||||
|
* 'Break' optimizations (code duplication, unreachable, etc.) will be done at
|
||||||
|
* {@link SwitchBreakVisitor}
|
||||||
|
*/
|
||||||
|
private static void insertBreaksForCase(MethodNode mth, SwitchRegion switchRegion, IContainer caseContainer) {
|
||||||
|
BlockSet caseBlocks = new BlockSet(mth);
|
||||||
|
RegionUtils.visitBlockNodes(mth, caseContainer, caseBlocks::add);
|
||||||
|
DepthRegionTraversal.traverse(mth, caseContainer, new AbstractRegionVisitor() {
|
||||||
|
@Override
|
||||||
|
public void leaveRegion(MethodNode mth, IRegion region) {
|
||||||
|
boolean insertBreak = false;
|
||||||
|
if (region == caseContainer) {
|
||||||
|
// top region
|
||||||
|
insertBreak = true;
|
||||||
|
} else {
|
||||||
|
IContainer lastContainer = ListUtils.last(region.getSubBlocks());
|
||||||
|
if (lastContainer instanceof BlockNode) {
|
||||||
|
BlockNode lastBlock = (BlockNode) lastContainer;
|
||||||
|
for (BlockNode successor : lastBlock.getSuccessors()) {
|
||||||
|
if (!caseBlocks.contains(successor)) {
|
||||||
|
insertBreak = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (insertBreak && canAppendBreak(region)) {
|
||||||
|
region.getSubBlocks().add(buildBreakContainer(switchRegion));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean canAppendBreak(IRegion region) {
|
||||||
|
return !region.contains(AFlag.FALL_THROUGH) && !RegionUtils.hasExitBlock(region);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InsnContainer buildBreakContainer(SwitchRegion switchRegion) {
|
||||||
|
InsnNode breakInsn = new InsnNode(InsnType.BREAK, 0);
|
||||||
|
breakInsn.add(AFlag.SYNTHETIC);
|
||||||
|
breakInsn.addAttr(new RegionRefAttr(switchRegion));
|
||||||
|
return new InsnContainer(breakInsn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+142
-19
@@ -23,6 +23,7 @@ import jadx.core.dex.instructions.ArithNode;
|
|||||||
import jadx.core.dex.instructions.ArithOp;
|
import jadx.core.dex.instructions.ArithOp;
|
||||||
import jadx.core.dex.instructions.IndexInsnNode;
|
import jadx.core.dex.instructions.IndexInsnNode;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
import jadx.core.dex.instructions.PhiInsn;
|
import jadx.core.dex.instructions.PhiInsn;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
@@ -32,6 +33,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
|
|||||||
import jadx.core.dex.instructions.args.SSAVar;
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
|
import jadx.core.dex.nodes.IMethodDetails;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
@@ -70,6 +72,7 @@ public final class FixTypesVisitor extends AbstractVisitor {
|
|||||||
this.typeUpdate = root.getTypeUpdate();
|
this.typeUpdate = root.getTypeUpdate();
|
||||||
this.typeInference.init(root);
|
this.typeInference.init(root);
|
||||||
this.resolvers = Arrays.asList(
|
this.resolvers = Arrays.asList(
|
||||||
|
this::applyFieldType,
|
||||||
this::tryRestoreTypeVarCasts,
|
this::tryRestoreTypeVarCasts,
|
||||||
this::tryInsertCasts,
|
this::tryInsertCasts,
|
||||||
this::tryDeduceTypes,
|
this::tryDeduceTypes,
|
||||||
@@ -292,6 +295,117 @@ public final class FixTypesVisitor extends AbstractVisitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use type for var assigned from field (IGET or SGET).
|
||||||
|
* Insert additional casts at var use places.
|
||||||
|
*/
|
||||||
|
private Boolean applyFieldType(MethodNode mth) {
|
||||||
|
try {
|
||||||
|
boolean changed = false;
|
||||||
|
// will add new SSA vars, can't use for-each loop
|
||||||
|
List<SSAVar> sVars = mth.getSVars();
|
||||||
|
for (int i = 0, varsCount = sVars.size(); i < varsCount; i++) {
|
||||||
|
SSAVar ssaVar = sVars.get(i);
|
||||||
|
if (tryFieldTypeWithNewCasts(mth, ssaVar, true)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!changed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// rerun full type inference
|
||||||
|
InitCodeVariables.rerun(mth);
|
||||||
|
typeInference.initTypeBounds(mth);
|
||||||
|
typeInference.runTypePropagation(mth);
|
||||||
|
|
||||||
|
// check if changed var types are fixed
|
||||||
|
boolean success = true;
|
||||||
|
for (SSAVar ssaVar : mth.getSVars()) {
|
||||||
|
if (tryFieldTypeWithNewCasts(mth, ssaVar, false)) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
typeInference.initTypeBounds(mth);
|
||||||
|
typeInference.runTypePropagation(mth);
|
||||||
|
mth.addWarnComment("Type inference incomplete: some casts might be missing");
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
} catch (Exception e) {
|
||||||
|
mth.addWarnComment("Type inference fix 'apply assigned field type' failed", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryFieldTypeWithNewCasts(MethodNode mth, SSAVar ssaVar, boolean insertCasts) {
|
||||||
|
ArgType type = ssaVar.getTypeInfo().getType();
|
||||||
|
if (type.isTypeKnown() || ssaVar.isTypeImmutable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InsnNode assignInsn = ssaVar.getAssignInsn();
|
||||||
|
if (assignInsn == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InsnType insnType = assignInsn.getType();
|
||||||
|
if (insnType != InsnType.IGET && insnType != InsnType.SGET) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ArgType fieldType = assignInsn.getResult().getInitType();
|
||||||
|
// field type should be used
|
||||||
|
if (insertCasts) {
|
||||||
|
// try to find a use place and insert cast
|
||||||
|
boolean inserted = false;
|
||||||
|
for (RegisterArg useArg : ssaVar.getUseList()) {
|
||||||
|
if (insertExplicitUseCast(mth, ssaVar, useArg, fieldType)) {
|
||||||
|
inserted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
// force field type, will make type inference incomplete,
|
||||||
|
// but it is better that completely unknown type
|
||||||
|
ssaVar.setType(fieldType);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean insertExplicitUseCast(MethodNode mth, SSAVar ssaVar, RegisterArg useArg, ArgType fieldType) {
|
||||||
|
InsnNode parentInsn = useArg.getParentInsn();
|
||||||
|
if (!InsnUtils.isInsnType(parentInsn, InsnType.INVOKE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InvokeNode invoke = (InvokeNode) parentInsn;
|
||||||
|
InsnArg instanceArg = invoke.getInstanceArg();
|
||||||
|
if (instanceArg == null || !instanceArg.isSameVar(ssaVar)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
IMethodDetails details = mth.root().getMethodUtils().getMethodDetails(invoke);
|
||||||
|
if (details == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int newCasts = 0;
|
||||||
|
int k = -1;
|
||||||
|
for (InsnArg invArg : invoke.getArgList()) {
|
||||||
|
if (invArg == instanceArg) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
if (!invArg.isRegister()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ArgType detailsArg = details.getArgTypes().get(k);
|
||||||
|
ArgType invArgType = invArg.getType();
|
||||||
|
ArgType resolvedType = mth.root().getTypeUtils().replaceClassGenerics(fieldType, invArgType, detailsArg);
|
||||||
|
if (resolvedType != null && !resolvedType.equals(invArgType)) {
|
||||||
|
IndexInsnNode castInsn = insertUseCast(mth, (RegisterArg) invArg, resolvedType);
|
||||||
|
if (castInsn != null) {
|
||||||
|
castInsn.add(AFlag.EXPLICIT_CAST);
|
||||||
|
newCasts++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newCasts > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fix check casts to type var extend type:
|
* Fix check casts to type var extend type:
|
||||||
* <br>
|
* <br>
|
||||||
@@ -372,7 +486,9 @@ public final class FixTypesVisitor extends AbstractVisitor {
|
|||||||
&& !boundType.equals(var.getTypeInfo().getType())
|
&& !boundType.equals(var.getTypeInfo().getType())
|
||||||
&& boundType.containsTypeVariable()
|
&& boundType.containsTypeVariable()
|
||||||
&& !mth.root().getTypeUtils().containsUnknownTypeVar(mth, boundType)) {
|
&& !mth.root().getTypeUtils().containsUnknownTypeVar(mth, boundType)) {
|
||||||
if (insertAssignCast(mth, var, boundType)) {
|
IndexInsnNode castInsn = insertAssignCast(mth, var, boundType);
|
||||||
|
if (castInsn != null) {
|
||||||
|
castInsn.add(AFlag.SOFT_CAST);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return insertUseCasts(mth, var);
|
return insertUseCasts(mth, var);
|
||||||
@@ -388,58 +504,65 @@ public final class FixTypesVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
int useCasts = 0;
|
int useCasts = 0;
|
||||||
for (RegisterArg useReg : new ArrayList<>(useList)) {
|
for (RegisterArg useReg : new ArrayList<>(useList)) {
|
||||||
if (insertSoftUseCast(mth, useReg)) {
|
IndexInsnNode castInsn = insertUseCast(mth, useReg, useReg.getInitType());
|
||||||
|
if (castInsn != null) {
|
||||||
|
castInsn.add(AFlag.SOFT_CAST);
|
||||||
useCasts++;
|
useCasts++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return useCasts;
|
return useCasts;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean insertAssignCast(MethodNode mth, SSAVar var, ArgType castType) {
|
private @Nullable IndexInsnNode insertAssignCast(MethodNode mth, SSAVar var, ArgType castType) {
|
||||||
RegisterArg assignArg = var.getAssign();
|
RegisterArg assignArg = var.getAssign();
|
||||||
InsnNode assignInsn = assignArg.getParentInsn();
|
InsnNode assignInsn = assignArg.getParentInsn();
|
||||||
if (assignInsn == null || assignInsn.getType() == InsnType.PHI) {
|
if (assignInsn == null || assignInsn.getType() == InsnType.PHI) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn);
|
BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn);
|
||||||
if (assignBlock == null) {
|
if (assignBlock == null) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
assignInsn.setResult(assignArg.duplicateWithNewSSAVar(mth));
|
assignInsn.setResult(assignArg.duplicateWithNewSSAVar(mth));
|
||||||
IndexInsnNode castInsn = makeSoftCastInsn(assignArg.duplicate(), assignInsn.getResult().duplicate(), castType);
|
IndexInsnNode castInsn = makeCastInsn(assignArg.duplicate(), assignInsn.getResult().duplicate(), castType);
|
||||||
return BlockUtils.insertAfterInsn(assignBlock, assignInsn, castInsn);
|
if (!BlockUtils.insertAfterInsn(assignBlock, assignInsn, castInsn)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return castInsn;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean insertSoftUseCast(MethodNode mth, RegisterArg useArg) {
|
private @Nullable IndexInsnNode insertUseCast(MethodNode mth, RegisterArg useArg, ArgType castType) {
|
||||||
InsnNode useInsn = useArg.getParentInsn();
|
InsnNode useInsn = useArg.getParentInsn();
|
||||||
if (useInsn == null || useInsn.getType() == InsnType.PHI) {
|
if (useInsn == null || useInsn.getType() == InsnType.PHI) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
if (useInsn.getType() == InsnType.IF && useInsn.getArg(1).isZeroConst()) {
|
if (useInsn.getType() == InsnType.IF && useInsn.getArg(1).isZeroConst()) {
|
||||||
// cast isn't needed if compare with null
|
// cast isn't needed if compare with null
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
BlockNode useBlock = BlockUtils.getBlockByInsn(mth, useInsn);
|
BlockNode useBlock = BlockUtils.getBlockByInsn(mth, useInsn);
|
||||||
if (useBlock == null) {
|
if (useBlock == null) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
IndexInsnNode castInsn = makeSoftCastInsn(
|
IndexInsnNode castInsn = makeCastInsn(
|
||||||
useArg.duplicateWithNewSSAVar(mth),
|
useArg.duplicateWithNewSSAVar(mth),
|
||||||
useArg.duplicate(),
|
useArg.duplicate(),
|
||||||
useArg.getInitType());
|
castType);
|
||||||
useInsn.replaceArg(useArg, castInsn.getResult().duplicate());
|
useInsn.replaceArg(useArg, castInsn.getResult().duplicate());
|
||||||
boolean success = BlockUtils.insertBeforeInsn(useBlock, useInsn, castInsn);
|
boolean inserted = BlockUtils.insertBeforeInsn(useBlock, useInsn, castInsn);
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE && success) {
|
if (!inserted) {
|
||||||
LOG.info("Insert soft cast for {} before {} in {}", useArg, useInsn, useBlock);
|
return null;
|
||||||
}
|
}
|
||||||
return success;
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
|
LOG.info("Insert cast for {} before {} in {}", useArg, useInsn, useBlock);
|
||||||
|
}
|
||||||
|
return castInsn;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IndexInsnNode makeSoftCastInsn(RegisterArg result, RegisterArg arg, ArgType castType) {
|
private IndexInsnNode makeCastInsn(RegisterArg result, RegisterArg arg, ArgType castType) {
|
||||||
IndexInsnNode castInsn = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1);
|
IndexInsnNode castInsn = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1);
|
||||||
castInsn.setResult(result);
|
castInsn.setResult(result);
|
||||||
castInsn.addArg(arg);
|
castInsn.addArg(arg);
|
||||||
castInsn.add(AFlag.SOFT_CAST);
|
|
||||||
castInsn.add(AFlag.SYNTHETIC);
|
castInsn.add(AFlag.SYNTHETIC);
|
||||||
return castInsn;
|
return castInsn;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
|||||||
assignImmutableTypes(mth);
|
assignImmutableTypes(mth);
|
||||||
initTypeBounds(mth);
|
initTypeBounds(mth);
|
||||||
runTypePropagation(mth);
|
runTypePropagation(mth);
|
||||||
|
} catch (StackOverflowError | BootstrapMethodError e) {
|
||||||
|
mth.addError("Type inference failed with stack overflow", new JadxOverflowException(e.getMessage()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
mth.addError("Type inference failed", e);
|
mth.addError("Type inference failed", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.clsp.ClspClass;
|
import jadx.core.clsp.ClspClass;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
@@ -29,7 +30,6 @@ import jadx.core.dex.nodes.InsnNode;
|
|||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.nodes.utils.TypeUtils;
|
import jadx.core.dex.nodes.utils.TypeUtils;
|
||||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
import static jadx.core.dex.visitors.typeinference.TypeUpdateResult.CHANGED;
|
import static jadx.core.dex.visitors.typeinference.TypeUpdateResult.CHANGED;
|
||||||
@@ -42,9 +42,11 @@ public final class TypeUpdate {
|
|||||||
private final RootNode root;
|
private final RootNode root;
|
||||||
private final Map<InsnType, ITypeListener> listenerRegistry;
|
private final Map<InsnType, ITypeListener> listenerRegistry;
|
||||||
private final TypeCompare comparator;
|
private final TypeCompare comparator;
|
||||||
|
private final JadxArgs args;
|
||||||
|
|
||||||
public TypeUpdate(RootNode root) {
|
public TypeUpdate(RootNode root) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
|
this.args = root.getArgs();
|
||||||
this.listenerRegistry = initListenerRegistry();
|
this.listenerRegistry = initListenerRegistry();
|
||||||
this.comparator = new TypeCompare(root);
|
this.comparator = new TypeCompare(root);
|
||||||
}
|
}
|
||||||
@@ -70,30 +72,35 @@ public final class TypeUpdate {
|
|||||||
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNORE_SAME);
|
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNORE_SAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypeUpdateResult applyWithWiderIgnoreUnknown(MethodNode mth, SSAVar ssaVar, ArgType candidateType) {
|
public TypeUpdateResult applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType candidateType) {
|
||||||
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNORE_UNKNOWN);
|
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_APPLY_DEBUG);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeUpdateResult apply(MethodNode mth, SSAVar ssaVar, ArgType candidateType, TypeUpdateFlags flags) {
|
private TypeUpdateResult apply(MethodNode mth, SSAVar ssaVar, ArgType candidateType, TypeUpdateFlags flags) {
|
||||||
if (candidateType == null || !candidateType.isTypeKnown()) {
|
try {
|
||||||
|
if (candidateType == null || !candidateType.isTypeKnown()) {
|
||||||
|
return REJECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeUpdateInfo updateInfo = new TypeUpdateInfo(mth, flags, args);
|
||||||
|
TypeUpdateResult result = updateTypeChecked(updateInfo, ssaVar.getAssign(), candidateType);
|
||||||
|
if (result == REJECT) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (updateInfo.isEmpty()) {
|
||||||
|
return SAME;
|
||||||
|
}
|
||||||
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
|
LOG.debug("Applying type {} to {}:", candidateType, ssaVar.toShortString());
|
||||||
|
updateInfo.getSortedUpdates().forEach(upd -> LOG.debug(" {} -> {} in {}",
|
||||||
|
upd.getType(), upd.getArg().toShortString(), upd.getArg().getParentInsn()));
|
||||||
|
}
|
||||||
|
updateInfo.applyUpdates();
|
||||||
|
return CHANGED;
|
||||||
|
} catch (Exception e) {
|
||||||
|
mth.addWarnComment("Type update failed for variable: " + ssaVar + ", new type: " + candidateType, e);
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeUpdateInfo updateInfo = new TypeUpdateInfo(mth, flags);
|
|
||||||
TypeUpdateResult result = updateTypeChecked(updateInfo, ssaVar.getAssign(), candidateType);
|
|
||||||
if (result == REJECT) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if (updateInfo.isEmpty()) {
|
|
||||||
return SAME;
|
|
||||||
}
|
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
|
||||||
LOG.debug("Applying type {} to {}:", candidateType, ssaVar.toShortString());
|
|
||||||
updateInfo.getSortedUpdates().forEach(upd -> LOG.debug(" {} -> {} in {}",
|
|
||||||
upd.getType(), upd.getArg().toShortString(), upd.getArg().getParentInsn()));
|
|
||||||
}
|
|
||||||
updateInfo.applyUpdates();
|
|
||||||
return CHANGED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeUpdateResult updateTypeChecked(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
private TypeUpdateResult updateTypeChecked(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
||||||
@@ -116,8 +123,9 @@ public final class TypeUpdate {
|
|||||||
|
|
||||||
private @Nullable TypeUpdateResult verifyType(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
private @Nullable TypeUpdateResult verifyType(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
||||||
ArgType currentType = arg.getType();
|
ArgType currentType = arg.getType();
|
||||||
|
TypeUpdateFlags typeUpdateFlags = updateInfo.getFlags();
|
||||||
if (Objects.equals(currentType, candidateType)) {
|
if (Objects.equals(currentType, candidateType)) {
|
||||||
if (!updateInfo.getFlags().isIgnoreSame()) {
|
if (!typeUpdateFlags.isIgnoreSame()) {
|
||||||
return SAME;
|
return SAME;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -135,7 +143,7 @@ public final class TypeUpdate {
|
|||||||
}
|
}
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
if (compareResult == TypeCompareEnum.UNKNOWN && updateInfo.getFlags().isIgnoreUnknown()) {
|
if (compareResult == TypeCompareEnum.UNKNOWN && typeUpdateFlags.isIgnoreUnknown()) {
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) {
|
if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) {
|
||||||
@@ -148,7 +156,13 @@ public final class TypeUpdate {
|
|||||||
}
|
}
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
if (compareResult.isWider() && !updateInfo.getFlags().isAllowWider()) {
|
if (compareResult == TypeCompareEnum.WIDER_BY_GENERIC && typeUpdateFlags.isKeepGenerics()) {
|
||||||
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
|
LOG.debug("Type rejected for {}: candidate={} is removing generic from current={}", arg, candidateType, currentType);
|
||||||
|
}
|
||||||
|
return REJECT;
|
||||||
|
}
|
||||||
|
if (compareResult.isWider() && !typeUpdateFlags.isAllowWider()) {
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
LOG.debug("Type rejected for {}: candidate={} is wider than current={}", arg, candidateType, currentType);
|
LOG.debug("Type rejected for {}: candidate={} is wider than current={}", arg, candidateType, currentType);
|
||||||
}
|
}
|
||||||
@@ -208,16 +222,11 @@ public final class TypeUpdate {
|
|||||||
return CHANGED;
|
return CHANGED;
|
||||||
}
|
}
|
||||||
updateInfo.requestUpdate(arg, candidateType);
|
updateInfo.requestUpdate(arg, candidateType);
|
||||||
try {
|
TypeUpdateResult result = runListeners(updateInfo, arg, candidateType);
|
||||||
TypeUpdateResult result = runListeners(updateInfo, arg, candidateType);
|
if (result == REJECT) {
|
||||||
if (result == REJECT) {
|
updateInfo.rollbackUpdate(arg);
|
||||||
updateInfo.rollbackUpdate(arg);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (StackOverflowError | BootstrapMethodError error) {
|
|
||||||
throw new JadxOverflowException("Type update terminated with stack overflow, arg: " + arg
|
|
||||||
+ ", method size: " + updateInfo.getMth().getInsnsCount());
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeUpdateResult runListeners(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
private TypeUpdateResult runListeners(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
||||||
@@ -432,17 +441,15 @@ public final class TypeUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TypeUpdateResult moveListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
private TypeUpdateResult moveListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
||||||
|
if (insn.getResult() == null) {
|
||||||
|
return CHANGED;
|
||||||
|
}
|
||||||
boolean assignChanged = isAssign(insn, arg);
|
boolean assignChanged = isAssign(insn, arg);
|
||||||
InsnArg changeArg = assignChanged ? insn.getArg(0) : insn.getResult();
|
InsnArg changeArg = assignChanged ? insn.getArg(0) : insn.getResult();
|
||||||
|
|
||||||
boolean correctType;
|
// allow result to be wider
|
||||||
if (changeArg.getType().isTypeKnown()) {
|
TypeCompareEnum cmp = comparator.compareTypes(candidateType, changeArg.getType());
|
||||||
// allow result to be wider
|
boolean correctType = cmp.isEqual() || (assignChanged ? cmp.isWider() : cmp.isNarrow());
|
||||||
TypeCompareEnum cmp = comparator.compareTypes(candidateType, changeArg.getType());
|
|
||||||
correctType = cmp.isEqual() || (assignChanged ? cmp.isWider() : cmp.isNarrow());
|
|
||||||
} else {
|
|
||||||
correctType = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeUpdateResult result = updateTypeChecked(updateInfo, changeArg, candidateType);
|
TypeUpdateResult result = updateTypeChecked(updateInfo, changeArg, candidateType);
|
||||||
if (result == SAME && !correctType) {
|
if (result == SAME && !correctType) {
|
||||||
|
|||||||
@@ -1,55 +1,61 @@
|
|||||||
package jadx.core.dex.visitors.typeinference;
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static jadx.core.dex.visitors.typeinference.TypeUpdateFlags.FlagsEnum.ALLOW_WIDER;
|
||||||
|
import static jadx.core.dex.visitors.typeinference.TypeUpdateFlags.FlagsEnum.IGNORE_SAME;
|
||||||
|
import static jadx.core.dex.visitors.typeinference.TypeUpdateFlags.FlagsEnum.IGNORE_UNKNOWN;
|
||||||
|
import static jadx.core.dex.visitors.typeinference.TypeUpdateFlags.FlagsEnum.KEEP_GENERICS;
|
||||||
|
|
||||||
public class TypeUpdateFlags {
|
public class TypeUpdateFlags {
|
||||||
private static final int ALLOW_WIDER = 1;
|
enum FlagsEnum {
|
||||||
private static final int IGNORE_SAME = 2;
|
ALLOW_WIDER,
|
||||||
private static final int IGNORE_UNKNOWN = 4;
|
IGNORE_SAME,
|
||||||
|
IGNORE_UNKNOWN,
|
||||||
public static final TypeUpdateFlags FLAGS_EMPTY = build(0);
|
KEEP_GENERICS,
|
||||||
public static final TypeUpdateFlags FLAGS_WIDER = build(ALLOW_WIDER);
|
|
||||||
public static final TypeUpdateFlags FLAGS_WIDER_IGNORE_SAME = build(ALLOW_WIDER | IGNORE_SAME);
|
|
||||||
public static final TypeUpdateFlags FLAGS_WIDER_IGNORE_UNKNOWN = build(ALLOW_WIDER | IGNORE_UNKNOWN);
|
|
||||||
|
|
||||||
private final int flags;
|
|
||||||
|
|
||||||
private static TypeUpdateFlags build(int flags) {
|
|
||||||
return new TypeUpdateFlags(flags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeUpdateFlags(int flags) {
|
static final TypeUpdateFlags FLAGS_EMPTY = build();
|
||||||
|
static final TypeUpdateFlags FLAGS_WIDER = build(ALLOW_WIDER);
|
||||||
|
static final TypeUpdateFlags FLAGS_WIDER_IGNORE_SAME = build(ALLOW_WIDER, IGNORE_SAME);
|
||||||
|
static final TypeUpdateFlags FLAGS_APPLY_DEBUG = build(ALLOW_WIDER, KEEP_GENERICS, IGNORE_UNKNOWN);
|
||||||
|
|
||||||
|
private final Set<FlagsEnum> flags;
|
||||||
|
|
||||||
|
private static TypeUpdateFlags build(FlagsEnum... flags) {
|
||||||
|
EnumSet<FlagsEnum> set;
|
||||||
|
if (flags.length == 0) {
|
||||||
|
set = EnumSet.noneOf(FlagsEnum.class);
|
||||||
|
} else {
|
||||||
|
set = EnumSet.copyOf(List.of(flags));
|
||||||
|
}
|
||||||
|
return new TypeUpdateFlags(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeUpdateFlags(Set<FlagsEnum> flags) {
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAllowWider() {
|
public boolean isAllowWider() {
|
||||||
return (flags & ALLOW_WIDER) != 0;
|
return flags.contains(ALLOW_WIDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isIgnoreSame() {
|
public boolean isIgnoreSame() {
|
||||||
return (flags & IGNORE_SAME) != 0;
|
return flags.contains(IGNORE_SAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isIgnoreUnknown() {
|
public boolean isIgnoreUnknown() {
|
||||||
return (flags & IGNORE_UNKNOWN) != 0;
|
return flags.contains(IGNORE_UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isKeepGenerics() {
|
||||||
|
return flags.contains(KEEP_GENERICS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
return flags.toString();
|
||||||
if (isAllowWider()) {
|
|
||||||
sb.append("ALLOW_WIDER");
|
|
||||||
}
|
|
||||||
if (isIgnoreSame()) {
|
|
||||||
if (sb.length() != 0) {
|
|
||||||
sb.append('|');
|
|
||||||
}
|
|
||||||
sb.append("IGNORE_SAME");
|
|
||||||
}
|
|
||||||
if (isIgnoreUnknown()) {
|
|
||||||
if (sb.length() != 0) {
|
|
||||||
sb.append('|');
|
|
||||||
}
|
|
||||||
sb.append("IGNORE_UNKNOWN");
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
@@ -18,10 +20,10 @@ public class TypeUpdateInfo {
|
|||||||
private final int updatesLimitCount;
|
private final int updatesLimitCount;
|
||||||
private int updateSeq = 0;
|
private int updateSeq = 0;
|
||||||
|
|
||||||
public TypeUpdateInfo(MethodNode mth, TypeUpdateFlags flags) {
|
public TypeUpdateInfo(MethodNode mth, TypeUpdateFlags flags, JadxArgs args) {
|
||||||
this.mth = mth;
|
this.mth = mth;
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
this.updatesLimitCount = mth.getInsnsCount() * 10;
|
this.updatesLimitCount = mth.getInsnsCount() * args.getTypeUpdatesLimitCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void requestUpdate(InsnArg arg, ArgType changeType) {
|
public void requestUpdate(InsnArg arg, ArgType changeType) {
|
||||||
@@ -32,7 +34,12 @@ public class TypeUpdateInfo {
|
|||||||
+ ", insn: " + arg.getParentInsn());
|
+ ", insn: " + arg.getParentInsn());
|
||||||
}
|
}
|
||||||
if (updateSeq > updatesLimitCount) {
|
if (updateSeq > updatesLimitCount) {
|
||||||
throw new JadxOverflowException("Type inference error: updates count limit reached");
|
throw new JadxOverflowException("Type inference error: updates count limit reached"
|
||||||
|
+ " with updateSeq = " + updateSeq + ". Try increasing type updates limit count.");
|
||||||
|
}
|
||||||
|
if (updateSeq % 100 == 0) {
|
||||||
|
// check for interruption sometimes (every update is too often)
|
||||||
|
Utils.checkThreadInterrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,8 @@ public class UsageInfoVisitor extends AbstractVisitor {
|
|||||||
try {
|
try {
|
||||||
processInsn(root, mth, insnData, usageInfo);
|
processInsn(root, mth, insnData, usageInfo);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
mth.addError("Dependency scan failed at insn: " + insnData, e);
|
throw new JadxRuntimeException(
|
||||||
|
"Usage info collection failed with error: " + e.getMessage() + " at insn: " + insnData, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ public class AndroidGradleGenerator implements IExportGradleGenerator {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(AndroidGradleGenerator.class);
|
private static final Logger LOG = LoggerFactory.getLogger(AndroidGradleGenerator.class);
|
||||||
private static final Pattern ILLEGAL_GRADLE_CHARS = Pattern.compile("[/\\\\:>\"?*|]");
|
private static final Pattern ILLEGAL_GRADLE_CHARS = Pattern.compile("[/\\\\:>\"?*|]");
|
||||||
|
|
||||||
private static final ApplicationParams UNKNOWN_APP_PARAMS = new ApplicationParams("UNKNOWN", 0, 0, 0, "UNKNOWN", "UNKNOWN", "UNKNOWN");
|
private static final ApplicationParams UNKNOWN_APP_PARAMS =
|
||||||
|
new ApplicationParams("UNKNOWN", 0, 0, 0, 0, "UNKNOWN", "UNKNOWN", "UNKNOWN");
|
||||||
|
|
||||||
private final RootNode root;
|
private final RootNode root;
|
||||||
private final File projectDir;
|
private final File projectDir;
|
||||||
@@ -107,6 +108,7 @@ public class AndroidGradleGenerator implements IExportGradleGenerator {
|
|||||||
if (exportApp) {
|
if (exportApp) {
|
||||||
attrs.add(AppAttribute.APPLICATION_LABEL);
|
attrs.add(AppAttribute.APPLICATION_LABEL);
|
||||||
attrs.add(AppAttribute.TARGET_SDK_VERSION);
|
attrs.add(AppAttribute.TARGET_SDK_VERSION);
|
||||||
|
attrs.add(AppAttribute.COMPILE_SDK_VERSION);
|
||||||
attrs.add(AppAttribute.VERSION_NAME);
|
attrs.add(AppAttribute.VERSION_NAME);
|
||||||
attrs.add(AppAttribute.VERSION_CODE);
|
attrs.add(AppAttribute.VERSION_CODE);
|
||||||
}
|
}
|
||||||
@@ -114,7 +116,7 @@ public class AndroidGradleGenerator implements IExportGradleGenerator {
|
|||||||
IJadxSecurity security = root.getArgs().getSecurity();
|
IJadxSecurity security = root.getArgs().getSecurity();
|
||||||
AndroidManifestParser parser = new AndroidManifestParser(androidManifest, strings, attrs, security);
|
AndroidManifestParser parser = new AndroidManifestParser(androidManifest, strings, attrs, security);
|
||||||
return parser.parse();
|
return parser.parse();
|
||||||
} catch (Throwable t) {
|
} catch (Exception t) {
|
||||||
LOG.warn("Failed to parse AndroidManifest.xml", t);
|
LOG.warn("Failed to parse AndroidManifest.xml", t);
|
||||||
return UNKNOWN_APP_PARAMS;
|
return UNKNOWN_APP_PARAMS;
|
||||||
}
|
}
|
||||||
@@ -160,6 +162,7 @@ public class AndroidGradleGenerator implements IExportGradleGenerator {
|
|||||||
TemplateFile tmpl = TemplateFile.fromResources("/export/android/app.build.gradle.tmpl");
|
TemplateFile tmpl = TemplateFile.fromResources("/export/android/app.build.gradle.tmpl");
|
||||||
tmpl.add("applicationId", appPackage);
|
tmpl.add("applicationId", appPackage);
|
||||||
tmpl.add("minSdkVersion", minSdkVersion);
|
tmpl.add("minSdkVersion", minSdkVersion);
|
||||||
|
tmpl.add("compileSdkVersion", applicationParams.getCompileSdkVersion());
|
||||||
tmpl.add("targetSdkVersion", applicationParams.getTargetSdkVersion());
|
tmpl.add("targetSdkVersion", applicationParams.getTargetSdkVersion());
|
||||||
tmpl.add("versionCode", applicationParams.getVersionCode());
|
tmpl.add("versionCode", applicationParams.getVersionCode());
|
||||||
tmpl.add("versionName", applicationParams.getVersionName());
|
tmpl.add("versionName", applicationParams.getVersionName());
|
||||||
@@ -174,6 +177,7 @@ public class AndroidGradleGenerator implements IExportGradleGenerator {
|
|||||||
TemplateFile tmpl = TemplateFile.fromResources("/export/android/lib.build.gradle.tmpl");
|
TemplateFile tmpl = TemplateFile.fromResources("/export/android/lib.build.gradle.tmpl");
|
||||||
tmpl.add("packageId", pkg);
|
tmpl.add("packageId", pkg);
|
||||||
tmpl.add("minSdkVersion", minSdkVersion);
|
tmpl.add("minSdkVersion", minSdkVersion);
|
||||||
|
tmpl.add("compileSdkVersion", applicationParams.getCompileSdkVersion());
|
||||||
tmpl.add("additionalOptions", genAdditionalAndroidPluginOptions(minSdkVersion));
|
tmpl.add("additionalOptions", genAdditionalAndroidPluginOptions(minSdkVersion));
|
||||||
|
|
||||||
tmpl.save(new File(baseDir, "build.gradle"));
|
tmpl.save(new File(baseDir, "build.gradle"));
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ public class JadxPluginManager {
|
|||||||
}
|
}
|
||||||
context.init();
|
context.init();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.warn("Failed to init plugin: {}", context.getPluginId(), e);
|
LOG.error("Failed to init plugin: {}", context.getPluginId(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (PluginContext context : pluginContexts) {
|
for (PluginContext context : pluginContexts) {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ public class PluginContext implements JadxPluginContext, JadxPluginRuntimeData,
|
|||||||
private final JadxPluginsData pluginsData;
|
private final JadxPluginsData pluginsData;
|
||||||
private final JadxPlugin plugin;
|
private final JadxPlugin plugin;
|
||||||
private final JadxPluginInfo pluginInfo;
|
private final JadxPluginInfo pluginInfo;
|
||||||
|
private final ClassLoader pluginClassLoader;
|
||||||
|
|
||||||
private AppContext appContext;
|
private AppContext appContext;
|
||||||
|
|
||||||
@@ -53,16 +54,30 @@ public class PluginContext implements JadxPluginContext, JadxPluginRuntimeData,
|
|||||||
this.pluginsData = pluginsData;
|
this.pluginsData = pluginsData;
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.pluginInfo = plugin.getPluginInfo();
|
this.pluginInfo = plugin.getPluginInfo();
|
||||||
|
this.pluginClassLoader = plugin.getClass().getClassLoader();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
plugin.init(this);
|
classLoaderWrap(() -> {
|
||||||
initialized = true;
|
plugin.init(this);
|
||||||
|
initialized = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unload() {
|
public void unload() {
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
plugin.unload();
|
classLoaderWrap(plugin::unload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void classLoaderWrap(Runnable task) {
|
||||||
|
Thread thread = Thread.currentThread();
|
||||||
|
ClassLoader prevClassLoader = thread.getContextClassLoader();
|
||||||
|
thread.setContextClassLoader(pluginClassLoader);
|
||||||
|
try {
|
||||||
|
task.run();
|
||||||
|
} finally {
|
||||||
|
thread.setContextClassLoader(prevClassLoader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,19 @@ package jadx.core.utils;
|
|||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.IBlock;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
|
||||||
public class BlockInsnPair {
|
public class BlockInsnPair {
|
||||||
private final BlockNode block;
|
private final IBlock block;
|
||||||
private final InsnNode insn;
|
private final InsnNode insn;
|
||||||
|
|
||||||
public BlockInsnPair(BlockNode block, InsnNode insn) {
|
public BlockInsnPair(IBlock block, InsnNode insn) {
|
||||||
this.block = block;
|
this.block = block;
|
||||||
this.insn = insn;
|
this.insn = insn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockNode getBlock() {
|
public IBlock getBlock() {
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package jadx.core.utils;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import jadx.core.dex.nodes.IBlock;
|
||||||
|
import jadx.core.dex.nodes.IContainer;
|
||||||
|
|
||||||
|
public class BlockParentContainer {
|
||||||
|
|
||||||
|
private final IContainer parent;
|
||||||
|
private final IBlock block;
|
||||||
|
|
||||||
|
public BlockParentContainer(IContainer parent, IBlock block) {
|
||||||
|
this.parent = Objects.requireNonNull(parent);
|
||||||
|
this.block = Objects.requireNonNull(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBlock getBlock() {
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IContainer getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "BlockParentContainer{" + block + ", parent=" + parent + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -599,7 +599,11 @@ public class BlockUtils {
|
|||||||
if (s == until) {
|
if (s == until) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
int id = s.getId();
|
if (s == from) {
|
||||||
|
// ignore possible block self loop
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int id = s.getPos();
|
||||||
if (!visited.get(id)) {
|
if (!visited.get(id)) {
|
||||||
visited.set(id);
|
visited.set(id);
|
||||||
if (until.isDominator(s)) {
|
if (until.isDominator(s)) {
|
||||||
@@ -1180,6 +1184,47 @@ public class BlockUtils {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return out block of try catch, by finding where try branch meets catch branch.
|
||||||
|
* It traverse domFrontier start from handler block, find the first frontier
|
||||||
|
* whose predecessor is try end.
|
||||||
|
* <br>
|
||||||
|
* It could return null if they never meets, but this doesn't mean that catch
|
||||||
|
* ends at the method exit.
|
||||||
|
* (see TestSwitchWithTryCatch and ExcHandlersRegionMaker#processExcHandler).
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static BlockNode getTryAndHandlerCrossBlock(MethodNode mth, ExceptionHandler handler) {
|
||||||
|
BlockNode start = handler.getHandlerBlock();
|
||||||
|
BlockNode topSplitter = BlockUtils.getTopSplitterForHandler(start);
|
||||||
|
List<ExceptionHandler> allHandlers = handler.getTryBlock().getHandlers();
|
||||||
|
List<BlockNode> handlerExitsCandidate = new ArrayList<>(BlockUtils.bitSetToBlocks(mth, start.getDomFrontier()));
|
||||||
|
BitSet visited = newBlocksBitSet(mth);
|
||||||
|
while (!handlerExitsCandidate.isEmpty()) {
|
||||||
|
BlockNode frontier = handlerExitsCandidate.remove(0);
|
||||||
|
if (visited.get(frontier.getPos())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
visited.set(frontier.getPos());
|
||||||
|
// In some cases, handler's domFrontier is in the half of catch block
|
||||||
|
// instead of the end, so we need to make sure frontier's predecessor
|
||||||
|
// comes from try branch end:
|
||||||
|
// 1. not from handler branch, doesn't exist path from handler to pred
|
||||||
|
// 2. from try branch, exists path from topSplitter to pred
|
||||||
|
// 3. skip method exit
|
||||||
|
for (BlockNode pred : frontier.getPredecessors()) {
|
||||||
|
boolean predFromHandler = allHandlers.stream().anyMatch(h -> isPathExists(h.getHandlerBlock(), pred));
|
||||||
|
if (!predFromHandler && BlockUtils.isPathExists(topSplitter, pred)
|
||||||
|
&& frontier != mth.getExitBlock()) {
|
||||||
|
return frontier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if not found, add this frontier's frontier to candidate list
|
||||||
|
handlerExitsCandidate.addAll(BlockUtils.bitSetToBlocks(mth, frontier.getDomFrontier()));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static BlockNode getBlockWithFlag(List<BlockNode> blocks, AFlag flag) {
|
public static BlockNode getBlockWithFlag(List<BlockNode> blocks, AFlag flag) {
|
||||||
for (BlockNode block : blocks) {
|
for (BlockNode block : blocks) {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public class DebugChecks {
|
|||||||
try {
|
try {
|
||||||
checkMethod(mth);
|
checkMethod(mth);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new JadxRuntimeException("Debug check failed after visitor: " + visitor, e);
|
mth.addError("Debug check failed after visitor: " + visitor, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class DebugChecksPass extends AbstractVisitor {
|
|||||||
if (!mth.contains(AType.JADX_ERROR)) {
|
if (!mth.contains(AType.JADX_ERROR)) {
|
||||||
try {
|
try {
|
||||||
DebugChecks.runChecksAfterVisitor(mth, visitorName);
|
DebugChecks.runChecksAfterVisitor(mth, visitorName);
|
||||||
} catch (Throwable e) {
|
} catch (Exception e) {
|
||||||
mth.addError("Check error", e);
|
mth.addError("Check error", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ import jadx.core.utils.exceptions.JadxException;
|
|||||||
public class DebugUtils {
|
public class DebugUtils {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DebugUtils.class);
|
private static final Logger LOG = LoggerFactory.getLogger(DebugUtils.class);
|
||||||
|
|
||||||
|
public static final Predicate<MethodNode> TEST_MTH_FILTER = mth -> mth.getName().equals("test");
|
||||||
|
|
||||||
private DebugUtils() {
|
private DebugUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +65,7 @@ public class DebugUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void dumpRawTest(MethodNode mth, String desc) {
|
public static void dumpRawTest(MethodNode mth, String desc) {
|
||||||
dumpRaw(mth, desc, method -> method.getName().equals("test"));
|
dumpRaw(mth, desc, TEST_MTH_FILTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void dumpRaw(MethodNode mth, String desc) {
|
public static void dumpRaw(MethodNode mth, String desc) {
|
||||||
@@ -91,6 +93,10 @@ public class DebugUtils {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IDexTreeVisitor dumpRawTestVisitor(String desc) {
|
||||||
|
return dumpRawVisitor(desc, TEST_MTH_FILTER);
|
||||||
|
}
|
||||||
|
|
||||||
public static void dump(MethodNode mth, String desc) {
|
public static void dump(MethodNode mth, String desc) {
|
||||||
File out = new File("test-graph-" + desc + "-tmp");
|
File out = new File("test-graph-" + desc + "-tmp");
|
||||||
DotGraphVisitor.dump().save(out, mth);
|
DotGraphVisitor.dump().save(out, mth);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user