Compare commits
198 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fdf170529f | |||
| 50283ab543 | |||
| 3fa3e5acec | |||
| 4230cd5b5a | |||
| 1ad6527de5 | |||
| 0421ad80c1 | |||
| 35e0201f06 | |||
| 118eea5e77 | |||
| 7f317be325 | |||
| e1aa9f6de4 | |||
| 058a5e3bb2 | |||
| 92b49ec2b5 | |||
| 583a04b092 | |||
| 444a04e2f7 | |||
| 157e702ffd | |||
| 77892f41ec | |||
| 6ba0e1dbf6 | |||
| 950fbbaa83 | |||
| 912c431511 | |||
| 5d6b82724a | |||
| 78c976ad4f | |||
| fbdfd135da | |||
| dd51783d9e | |||
| 158fc2fca3 | |||
| 24284a6f3a | |||
| 85c2c63aa3 | |||
| f354f7de63 | |||
| 540c0a8100 | |||
| 4d00fede56 | |||
| b1bc5c08ff | |||
| 305d4f4fe5 | |||
| 2d149e9a5d | |||
| 87b9ff3c35 | |||
| 1c36b3c74c | |||
| 068e4b8e3d | |||
| df38a6424f | |||
| 5d186e56a5 | |||
| 0fafcfa006 | |||
| e3fdbafd86 | |||
| 07c2b14479 | |||
| cdc844aaf3 | |||
| e1b7d361b9 | |||
| 12ef29bebc | |||
| 22ed241d50 | |||
| 28e5a3c5be | |||
| bb4d88cc68 | |||
| 4aaea2b93f | |||
| bc8d7c4fc3 | |||
| 5ea6c46778 | |||
| b28f8ba85b | |||
| 4db50fb749 | |||
| 1dd0c90a04 | |||
| 2bace2bde2 | |||
| 1a9cb832ab | |||
| 6844a46c93 | |||
| e9e45707da | |||
| b9d02ff4c4 | |||
| 29b64300bc | |||
| 777355e86e | |||
| 620a177ce8 | |||
| 683c2dfbeb | |||
| 266cbcc6f4 | |||
| 8a45602ae6 | |||
| 711419a797 | |||
| 603f3057eb | |||
| fa6fc1f871 | |||
| 49fa320989 | |||
| 2f301bf150 | |||
| b4892ce17f | |||
| 151c171616 | |||
| 79477a2de3 | |||
| 78aadda931 | |||
| b50706505f | |||
| 9114821fb1 | |||
| 1195582da8 | |||
| 258987b0ff | |||
| a6a734c70d | |||
| d6c23a2a9b | |||
| db028904d7 | |||
| 63a571306c | |||
| bc4db61e25 | |||
| c7e6e28830 | |||
| 1d7b6fdb2c | |||
| ce5d8eeff8 | |||
| 894e0e6132 | |||
| 127f0ecf3f | |||
| cf7767e702 | |||
| e0aedc7949 | |||
| bad78de74c | |||
| 12df8a169f | |||
| 15c9d33339 | |||
| 7e0fafbaf1 | |||
| 57b9c1dd7a | |||
| 8ba0c17259 | |||
| cd32151083 | |||
| 75b52d672e | |||
| 11d04508f7 | |||
| e641b773b5 | |||
| 6e5899c654 | |||
| c66ffaa7f9 | |||
| 5193c6a5d8 | |||
| e7212af547 | |||
| 3ca1357af4 | |||
| 90e95213e4 | |||
| ae2d4da585 | |||
| 691d5cd1e6 | |||
| 58a46c6417 | |||
| d3f6160e62 | |||
| 03e4afb12f | |||
| 6802f6028e | |||
| 5ca61cfe18 | |||
| 32d55b48f2 | |||
| ab4b6f9e54 | |||
| 9100ad1220 | |||
| 8b4f8fb572 | |||
| 87e0e5bf16 | |||
| e4c2d6cf6e | |||
| fb0bdb5112 | |||
| f4b3645435 | |||
| c27f2badf7 | |||
| 1a877d6535 | |||
| 5ada9331b6 | |||
| a0f4ccb7a4 | |||
| 5b5524a7dd | |||
| 3cc464c9c9 | |||
| 51555667cf | |||
| e01ea7010f | |||
| 77732c83c9 | |||
| a67fc83949 | |||
| 3d920725aa | |||
| 2f2fbea558 | |||
| e7a86a2960 | |||
| b282d97ffe | |||
| e4ca52a95f | |||
| d972d9ec74 | |||
| 0721a6b050 | |||
| 762ee6550e | |||
| 18070eb7a6 | |||
| 8486891728 | |||
| 4679172d4f | |||
| 92a6c333d8 | |||
| 358adbdd65 | |||
| 65f7c80222 | |||
| d2e6bb236e | |||
| eaeb114258 | |||
| 1533b7fe6e | |||
| a2cd8e1ead | |||
| 4edb512121 | |||
| 702b88228c | |||
| 14fd88b2f8 | |||
| 20657e8bb5 | |||
| 93d3194e3b | |||
| 39331d9120 | |||
| b4fa6644bc | |||
| 0b2e2ed034 | |||
| 81231206f3 | |||
| 49d0e76272 | |||
| 0809993b37 | |||
| 0c3afcc24c | |||
| d6c851eed4 | |||
| dcf4a7c4e3 | |||
| 9ba07b986b | |||
| e6b6b93cbb | |||
| fcd58ae76f | |||
| df380dea27 | |||
| 9d88592391 | |||
| c906c11b0f | |||
| 4fbc56cdb0 | |||
| 98c0416b20 | |||
| fa41874e30 | |||
| 2aa6c99c90 | |||
| 5f60c0f1bb | |||
| cb741db623 | |||
| 1df217c4a0 | |||
| 81f209ba9e | |||
| 34a31aa7df | |||
| 5099e02c9b | |||
| f364b39b29 | |||
| 4cd4746f9a | |||
| 6448f0e32b | |||
| e07332d49a | |||
| bd8a44c4c9 | |||
| 21e94d8d5c | |||
| 7b1c7b967a | |||
| e4b19ab560 | |||
| 49137c9751 | |||
| 0606c90f22 | |||
| 65ade379a6 | |||
| a06df187c9 | |||
| e784c7f7df | |||
| a717392379 | |||
| a71b3a71d8 | |||
| 3366bf3dec | |||
| a505534197 | |||
| 357706b070 | |||
| e02434d135 | |||
| 4586015fc0 | |||
| 1832f2aee3 |
@@ -0,0 +1,7 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
# Set update schedule for GitHub Actions
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
name: Build Artifacts
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master, build-test ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: 'adopt'
|
||||||
|
java-version: 8
|
||||||
|
|
||||||
|
- name: Set jadx version
|
||||||
|
run: |
|
||||||
|
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||||
|
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
||||||
|
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: burrunan/gradle-cache-action@v1
|
||||||
|
name: Build with Gradle
|
||||||
|
env:
|
||||||
|
TERM: dumb
|
||||||
|
with:
|
||||||
|
arguments: clean dist copyExe
|
||||||
|
|
||||||
|
- name: Save bundle artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
|
||||||
|
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
|
||||||
|
# Upload unpacked files for now
|
||||||
|
path: build/jadx/**/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Save exe artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
|
||||||
|
path: build/*.exe
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
build-win-bundle:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: oracle-actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
release: 17
|
||||||
|
|
||||||
|
- name: Print Java version
|
||||||
|
shell: bash
|
||||||
|
run: java -version
|
||||||
|
|
||||||
|
- name: Set jadx version
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||||
|
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
||||||
|
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: gradle/gradle-build-action@v2
|
||||||
|
name: Build with Gradle
|
||||||
|
env:
|
||||||
|
TERM: dumb
|
||||||
|
with:
|
||||||
|
arguments: clean dist -PbundleJRE=true
|
||||||
|
|
||||||
|
- name: Save exe bundle artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
||||||
|
path: jadx-gui/build/*-with-jre-win/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 30
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
name: Build Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master, build-test ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: 'adopt'
|
||||||
|
java-version: 8
|
||||||
|
|
||||||
|
- uses: burrunan/gradle-cache-action@v1
|
||||||
|
name: Build with Gradle
|
||||||
|
env:
|
||||||
|
TERM: dumb
|
||||||
|
with:
|
||||||
|
arguments: clean build dist copyExe --warning-mode=all
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
name: Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ master ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set up JDK
|
|
||||||
uses: actions/setup-java@v1
|
|
||||||
with:
|
|
||||||
java-version: 8
|
|
||||||
|
|
||||||
- name: Set jadx version
|
|
||||||
run: |
|
|
||||||
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
|
||||||
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
|
||||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- uses: burrunan/gradle-cache-action@v1
|
|
||||||
name: Build with Gradle
|
|
||||||
env:
|
|
||||||
TERM: dumb
|
|
||||||
TEST_INPUT_PLUGIN: dx
|
|
||||||
with:
|
|
||||||
arguments: clean build dist copyExe --warning-mode=all
|
|
||||||
|
|
||||||
- name: Save bundle artifact
|
|
||||||
if: success() && github.event_name == 'push'
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
|
|
||||||
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
|
|
||||||
# Upload unpacked files for now
|
|
||||||
path: build/jadx/**/*
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: Save exe artifact
|
|
||||||
if: success() && github.event_name == 'push'
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
|
|
||||||
path: build/*.exe
|
|
||||||
if-no-files-found: error
|
|
||||||
@@ -25,10 +25,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v2
|
||||||
with:
|
with:
|
||||||
queries: +security-extended
|
queries: +security-extended
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
@@ -38,4 +38,4 @@ jobs:
|
|||||||
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
|
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v2
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ jobs:
|
|||||||
name: "Validation"
|
name: "Validation"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: gradle/wrapper-validation-action@v1
|
- uses: gradle/wrapper-validation-action@v1
|
||||||
|
|||||||
+2
-1
@@ -35,4 +35,5 @@ jadx-output/
|
|||||||
*.orig
|
*.orig
|
||||||
quark.json
|
quark.json
|
||||||
|
|
||||||
cliff.toml
|
cliff.toml
|
||||||
|
jadx-gui/src/main/resources/logback.xml
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
## JADX
|
## JADX
|
||||||
|
|
||||||
[](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
|
[](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
|
||||||
[](https://lgtm.com/projects/g/skylot/jadx/alerts/)
|

|
||||||
[](https://github.com/semantic-release/semantic-release)
|

|
||||||
|

|
||||||
|

|
||||||
[](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
|
[](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
|
||||||
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||||
|
|
||||||
@@ -33,8 +35,9 @@ See these features in action here: [jadx-gui features overview](https://github.c
|
|||||||
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
|
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
- release from [github: ](https://github.com/skylot/jadx/releases/latest)
|
- release
|
||||||
- latest [unstable build](https://nightly.link/skylot/jadx/workflows/build/master)
|
from [github: ](https://github.com/skylot/jadx/releases/latest)
|
||||||
|
- latest [unstable build ](https://nightly.link/skylot/jadx/workflows/build-artifacts/master)
|
||||||
|
|
||||||
After download unpack zip file go to `bin` directory and run:
|
After download unpack zip file go to `bin` directory and run:
|
||||||
- `jadx` - command line version
|
- `jadx` - command line version
|
||||||
@@ -45,14 +48,18 @@ On Windows run `.bat` files with double-click\
|
|||||||
For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
|
For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
1. Arch linux
|
1. Arch linux 
|
||||||
```bash
|
```bash
|
||||||
sudo pacman -S jadx
|
sudo pacman -S jadx
|
||||||
```
|
```
|
||||||
2. macOS
|
2. macOS 
|
||||||
```bash
|
```bash
|
||||||
brew install jadx
|
brew install jadx
|
||||||
```
|
```
|
||||||
|
3. [Flathub ](https://flathub.org/apps/details/com.github.skylot.jadx)
|
||||||
|
```bash
|
||||||
|
flatpak install flathub com.github.skylot.jadx
|
||||||
|
```
|
||||||
|
|
||||||
### Use jadx as a library
|
### Use jadx as a library
|
||||||
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
|
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
|
||||||
@@ -95,6 +102,7 @@ options:
|
|||||||
--add-debug-lines - add comments with debug line numbers if available
|
--add-debug-lines - add comments with debug line numbers if available
|
||||||
--no-inline-anonymous - disable anonymous classes inline
|
--no-inline-anonymous - disable anonymous classes inline
|
||||||
--no-inline-methods - disable methods inline
|
--no-inline-methods - disable methods inline
|
||||||
|
--no-finally - don't extract finally block
|
||||||
--no-replace-consts - don't replace constant value with matching constant field
|
--no-replace-consts - don't replace constant value with matching constant field
|
||||||
--escape-unicode - escape non latin characters in strings (with \u)
|
--escape-unicode - escape non latin characters in strings (with \u)
|
||||||
--respect-bytecode-access-modifiers - don't change original access modifiers
|
--respect-bytecode-access-modifiers - don't change original access modifiers
|
||||||
@@ -107,9 +115,12 @@ options:
|
|||||||
'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-rewrite-cfg - set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)
|
|
||||||
--deobf-use-sourcename - use source file name as class name alias
|
--deobf-use-sourcename - use source file name as class name alias
|
||||||
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
||||||
|
--deobf-res-name-source - better name source for resources:
|
||||||
|
'auto' - automatically select best name (default)
|
||||||
|
'resources' - use resources names
|
||||||
|
'code' - use R class fields names
|
||||||
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
||||||
--rename-flags - fix options (comma-separated list of):
|
--rename-flags - fix options (comma-separated list of):
|
||||||
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
||||||
@@ -130,11 +141,11 @@ options:
|
|||||||
-h, --help - print this help
|
-h, --help - print this help
|
||||||
|
|
||||||
Plugin options (-P<name>=<value>):
|
Plugin options (-P<name>=<value>):
|
||||||
1) dex-input (Load .dex and .apk files)
|
1) dex-input: Load .dex and .apk files
|
||||||
-Pdex-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
|
||||||
2) java-convert (Convert .jar and .class files to dex)
|
2) java-convert: Convert .class, .jar and .aar files to dex
|
||||||
-Pjava-convert.mode - Convert mode, values: [dx, d8, both], default: both
|
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
|
||||||
-Pjava-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
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
jadx -d out classes.dex
|
jadx -d out classes.dex
|
||||||
|
|||||||
-33
@@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<component type="desktop">
|
|
||||||
<id>com.github.skylot.jadx</id>
|
|
||||||
<metadata_license>CC0-1.0</metadata_license>
|
|
||||||
<project_license>Apache-2.0</project_license>
|
|
||||||
<name>JADX</name>
|
|
||||||
<summary>Dex to Java decompiler</summary>
|
|
||||||
<description>
|
|
||||||
<p>Command line and GUI tools for producing Java source code from Android Dex and Apk files</p>
|
|
||||||
<ul>
|
|
||||||
<li>decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files</li>
|
|
||||||
<li>decode AndroidManifest.xml and other resources from resources.arsc</li>
|
|
||||||
<li>deobfuscator included</li>
|
|
||||||
<li>view decompiled code with highlighted syntax</li>
|
|
||||||
<li>jump to declaration</li>
|
|
||||||
<li>find usage</li>
|
|
||||||
<li>full text search</li>
|
|
||||||
<li>smali debugger</li>
|
|
||||||
</ul>
|
|
||||||
</description>
|
|
||||||
<screenshots>
|
|
||||||
<screenshot type="default">
|
|
||||||
<image>https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png</image>
|
|
||||||
</screenshot>
|
|
||||||
</screenshots>
|
|
||||||
<content_rating type="oars-1.1" />
|
|
||||||
<launchable type="desktop-id">com.github.skylot.jadx.desktop</launchable>
|
|
||||||
<url type="homepage">https://github.com/skylot/jadx</url>
|
|
||||||
<url type="bugtracker">https://github.com/skylot/jadx/issues</url>
|
|
||||||
<releases>
|
|
||||||
<release version="1.3.4" date="2022-03-20" />
|
|
||||||
</releases>
|
|
||||||
</component>
|
|
||||||
+16
-18
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
id 'com.github.ben-manes.versions' version '0.45.0'
|
||||||
id 'com.diffplug.spotless' version '6.4.2'
|
id 'com.diffplug.spotless' version '6.13.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||||
@@ -14,7 +14,6 @@ allprojects {
|
|||||||
version = jadxVersion
|
version = jadxVersion
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
|
|
||||||
compileJava {
|
compileJava {
|
||||||
options.encoding = "UTF-8"
|
options.encoding = "UTF-8"
|
||||||
@@ -27,18 +26,18 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.slf4j:slf4j-api:1.7.36'
|
implementation 'org.slf4j:slf4j-api:2.0.6'
|
||||||
compileOnly 'org.jetbrains:annotations:23.0.0'
|
compileOnly 'org.jetbrains:annotations:24.0.0'
|
||||||
|
|
||||||
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
testImplementation 'ch.qos.logback:logback-classic:1.3.5'
|
||||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||||
testImplementation 'org.mockito:mockito-core:4.4.0'
|
testImplementation 'org.mockito:mockito-core:4.10.0'
|
||||||
testImplementation 'org.assertj:assertj-core:3.22.0'
|
testImplementation 'org.assertj:assertj-core:3.24.2'
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2'
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
|
||||||
|
|
||||||
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
testCompileOnly 'org.jetbrains:annotations:24.0.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@@ -50,6 +49,11 @@ allprojects {
|
|||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
|
// Commented out for now since we're using a local mapping-io fork atm.
|
||||||
|
// maven {
|
||||||
|
// name 'FabricMC'
|
||||||
|
// url 'https://maven.fabricmc.net/'
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,13 +68,7 @@ spotless {
|
|||||||
|
|
||||||
importOrderFile 'config/code-formatter/eclipse.importorder'
|
importOrderFile 'config/code-formatter/eclipse.importorder'
|
||||||
eclipse().configFile 'config/code-formatter/eclipse.xml'
|
eclipse().configFile 'config/code-formatter/eclipse.xml'
|
||||||
if (JavaVersion.current() < JavaVersion.VERSION_16) {
|
removeUnusedImports()
|
||||||
removeUnusedImports()
|
|
||||||
} else {
|
|
||||||
// google-format on Java 16+ issue: https://github.com/diffplug/spotless/issues/834
|
|
||||||
println('Warning! Unused imports remove is disabled for Java 16+'
|
|
||||||
+ ' (use workaround from https://github.com/diffplug/spotless/tree/main/plugin-gradle#google-java-format)')
|
|
||||||
}
|
|
||||||
|
|
||||||
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
||||||
encoding("UTF-8")
|
encoding("UTF-8")
|
||||||
|
|||||||
@@ -1,2 +1,11 @@
|
|||||||
org.gradle.warning.mode=all
|
org.gradle.warning.mode=all
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
|
|
||||||
|
# Flags for google-java-format (optimize imports by spotless) for Java >= 16.
|
||||||
|
# Java < 9 will ignore unsupported flags (thanks to -XX:+IgnoreUnrecognizedVMOptions)
|
||||||
|
org.gradle.jvmargs=-XX:+IgnoreUnrecognizedVMOptions \
|
||||||
|
--add-exports='jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' \
|
||||||
|
--add-exports='jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED' \
|
||||||
|
--add-exports='jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED' \
|
||||||
|
--add-exports='jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED' \
|
||||||
|
--add-exports='jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda
|
distributionSha256Sum=7ba68c54029790ab444b39d7e293d3236b2632631fb5f2e012bb28b4ff669e4b
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -205,6 +205,12 @@ set -- \
|
|||||||
org.gradle.wrapper.GradleWrapperMain \
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
echo "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
|||||||
Vendored
+8
-6
@@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ dependencies {
|
|||||||
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||||
|
|
||||||
implementation 'com.beust:jcommander:1.82'
|
implementation 'com.beust:jcommander:1.82'
|
||||||
implementation 'ch.qos.logback:logback-classic:1.2.11'
|
implementation 'ch.qos.logback:logback-classic:1.3.5'
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ import jadx.api.plugins.JadxPluginInfo;
|
|||||||
import jadx.api.plugins.JadxPluginManager;
|
import jadx.api.plugins.JadxPluginManager;
|
||||||
import jadx.api.plugins.options.JadxPluginOptions;
|
import jadx.api.plugins.options.JadxPluginOptions;
|
||||||
import jadx.api.plugins.options.OptionDescription;
|
import jadx.api.plugins.options.OptionDescription;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public class JCommanderWrapper<T> {
|
public class JCommanderWrapper<T> {
|
||||||
private final JCommander jc;
|
private final JCommander jc;
|
||||||
@@ -50,12 +52,24 @@ public class JCommanderWrapper<T> {
|
|||||||
if (parameter.isAssigned()) {
|
if (parameter.isAssigned()) {
|
||||||
// copy assigned field value to obj
|
// copy assigned field value to obj
|
||||||
Parameterized parameterized = parameter.getParameterized();
|
Parameterized parameterized = parameter.getParameterized();
|
||||||
Object val = parameterized.get(parameter.getObject());
|
Object providedValue = parameterized.get(parameter.getObject());
|
||||||
parameterized.set(obj, val);
|
Object newValue = mergeValues(parameterized.getType(), providedValue, () -> parameterized.get(obj));
|
||||||
|
parameterized.set(obj, newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
|
private static Object mergeValues(Class<?> type, Object value, Supplier<Object> prevValueProvider) {
|
||||||
|
if (type.isAssignableFrom(Map.class)) {
|
||||||
|
// merge maps instead replacing whole map
|
||||||
|
Map prevMap = (Map) prevValueProvider.get();
|
||||||
|
return Utils.mergeMaps(prevMap, (Map) value); // value map will override keys in prevMap
|
||||||
|
}
|
||||||
|
// simple override
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
public void printUsage() {
|
public void printUsage() {
|
||||||
// print usage in not sorted fields order (by default its sorted by description)
|
// print usage in not sorted fields order (by default its sorted by description)
|
||||||
PrintStream out = System.out;
|
PrintStream out = System.out;
|
||||||
@@ -179,11 +193,11 @@ public class JCommanderWrapper<T> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
JadxPluginInfo pluginInfo = plugin.getPluginInfo();
|
JadxPluginInfo pluginInfo = plugin.getPluginInfo();
|
||||||
out.append("\n ").append(k).append(") ");
|
out.append("\n ").append(k).append(") ");
|
||||||
out.append(pluginInfo.getPluginId()).append(" (").append(pluginInfo.getDescription()).append(") ");
|
out.append(pluginInfo.getPluginId()).append(": ").append(pluginInfo.getDescription());
|
||||||
for (OptionDescription desc : descs) {
|
for (OptionDescription desc : descs) {
|
||||||
StringBuilder opt = new StringBuilder();
|
StringBuilder opt = new StringBuilder();
|
||||||
opt.append(" -P").append(desc.name());
|
opt.append(" - ").append(desc.name());
|
||||||
addSpaces(opt, maxNamesLen - opt.length());
|
addSpaces(opt, maxNamesLen - opt.length());
|
||||||
opt.append("- ").append(desc.description());
|
opt.append("- ").append(desc.description());
|
||||||
if (!desc.values().isEmpty()) {
|
if (!desc.values().isEmpty()) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class JadxCLI {
|
|||||||
} catch (JadxArgsValidateException e) {
|
} catch (JadxArgsValidateException e) {
|
||||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||||
result = 1;
|
result = 1;
|
||||||
} catch (Exception e) {
|
} catch (Throwable e) {
|
||||||
LOG.error("Process error:", e);
|
LOG.error("Process error:", e);
|
||||||
result = 1;
|
result = 1;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -66,8 +66,14 @@ public class JadxCLI {
|
|||||||
|
|
||||||
private static boolean checkForErrors(JadxDecompiler jadx) {
|
private static boolean checkForErrors(JadxDecompiler jadx) {
|
||||||
if (jadx.getRoot().getClasses().isEmpty()) {
|
if (jadx.getRoot().getClasses().isEmpty()) {
|
||||||
LOG.error("Load failed! No classes for decompile!");
|
if (jadx.getArgs().isSkipResources()) {
|
||||||
return true;
|
LOG.error("Load failed! No classes for decompile!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!jadx.getArgs().isSkipSources()) {
|
||||||
|
LOG.warn("No classes to decompile; decoding resources only");
|
||||||
|
jadx.getArgs().setSkipSources(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (jadx.getErrorsCount() > 0) {
|
if (jadx.getErrorsCount() > 0) {
|
||||||
LOG.error("Load with errors! Check log for details");
|
LOG.error("Load with errors! Check log for details");
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -21,6 +23,7 @@ import jadx.api.JadxArgs.RenameEnum;
|
|||||||
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.args.DeobfuscationMapFileMode;
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
|
import jadx.api.args.ResourceNameSource;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
@@ -88,6 +91,12 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
|
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
|
||||||
protected boolean inlineMethods = true;
|
protected boolean inlineMethods = true;
|
||||||
|
|
||||||
|
@Parameter(names = { "--no-inline-kotlin-lambda" }, description = "disable inline for Kotlin lambdas")
|
||||||
|
protected boolean allowInlineKotlinLambda = true;
|
||||||
|
|
||||||
|
@Parameter(names = "--no-finally", description = "don't extract finally block")
|
||||||
|
protected boolean extractFinally = true;
|
||||||
|
|
||||||
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
|
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
|
||||||
protected boolean replaceConsts = true;
|
protected boolean replaceConsts = true;
|
||||||
|
|
||||||
@@ -123,15 +132,22 @@ public class JadxCLIArgs {
|
|||||||
)
|
)
|
||||||
protected DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
protected DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||||
|
|
||||||
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "set '--deobf-cfg-file-mode' to 'overwrite' (deprecated)")
|
|
||||||
protected boolean deobfuscationForceSave = false;
|
|
||||||
|
|
||||||
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
||||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
||||||
|
|
||||||
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
|
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
|
||||||
protected boolean deobfuscationParseKotlinMetadata = false;
|
protected boolean deobfuscationParseKotlinMetadata = false;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--deobf-res-name-source" },
|
||||||
|
description = "better name source for resources:"
|
||||||
|
+ "\n 'auto' - automatically select best name (default)"
|
||||||
|
+ "\n 'resources' - use resources names"
|
||||||
|
+ "\n 'code' - use R class fields names",
|
||||||
|
converter = ResourceNameSourceConverter.class
|
||||||
|
)
|
||||||
|
protected ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
|
||||||
|
|
||||||
@Parameter(
|
@Parameter(
|
||||||
names = { "--use-kotlin-methods-for-var-names" },
|
names = { "--use-kotlin-methods-for-var-names" },
|
||||||
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
|
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
|
||||||
@@ -176,7 +192,7 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(
|
@Parameter(
|
||||||
names = { "--log-level" },
|
names = { "--log-level" },
|
||||||
description = "set log level, values: quiet, progress, error, warn, info, debug",
|
description = "set log level, values: quiet, progress, error, warn, info, debug",
|
||||||
converter = LogHelper.LogLevelConverter.class
|
converter = LogLevelConverter.class
|
||||||
)
|
)
|
||||||
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
|
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
|
||||||
|
|
||||||
@@ -259,16 +275,13 @@ public class JadxCLIArgs {
|
|||||||
args.setReplaceConsts(replaceConsts);
|
args.setReplaceConsts(replaceConsts);
|
||||||
args.setDeobfuscationOn(deobfuscationOn);
|
args.setDeobfuscationOn(deobfuscationOn);
|
||||||
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
|
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
|
||||||
if (deobfuscationForceSave) {
|
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
|
||||||
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
|
|
||||||
} else {
|
|
||||||
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode);
|
|
||||||
}
|
|
||||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||||
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
||||||
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
|
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
|
||||||
|
args.setResourceNameSource(resourceNameSource);
|
||||||
args.setEscapeUnicode(escapeUnicode);
|
args.setEscapeUnicode(escapeUnicode);
|
||||||
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||||
args.setExportAsGradleProject(exportAsGradleProject);
|
args.setExportAsGradleProject(exportAsGradleProject);
|
||||||
@@ -277,6 +290,8 @@ public class JadxCLIArgs {
|
|||||||
args.setInsertDebugLines(addDebugLines);
|
args.setInsertDebugLines(addDebugLines);
|
||||||
args.setInlineAnonymousClasses(inlineAnonymousClasses);
|
args.setInlineAnonymousClasses(inlineAnonymousClasses);
|
||||||
args.setInlineMethods(inlineMethods);
|
args.setInlineMethods(inlineMethods);
|
||||||
|
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
|
||||||
|
args.setExtractFinally(extractFinally);
|
||||||
args.setRenameFlags(renameFlags);
|
args.setRenameFlags(renameFlags);
|
||||||
args.setFsCaseSensitive(fsCaseSensitive);
|
args.setFsCaseSensitive(fsCaseSensitive);
|
||||||
args.setCommentsLevel(commentsLevel);
|
args.setCommentsLevel(commentsLevel);
|
||||||
@@ -357,6 +372,14 @@ public class JadxCLIArgs {
|
|||||||
return inlineMethods;
|
return inlineMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAllowInlineKotlinLambda() {
|
||||||
|
return allowInlineKotlinLambda;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExtractFinally() {
|
||||||
|
return extractFinally;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDeobfuscationOn() {
|
public boolean isDeobfuscationOn() {
|
||||||
return deobfuscationOn;
|
return deobfuscationOn;
|
||||||
}
|
}
|
||||||
@@ -377,10 +400,6 @@ public class JadxCLIArgs {
|
|||||||
return deobfuscationMapFileMode;
|
return deobfuscationMapFileMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDeobfuscationForceSave() {
|
|
||||||
return deobfuscationForceSave;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDeobfuscationUseSourceNameAsAlias() {
|
public boolean isDeobfuscationUseSourceNameAsAlias() {
|
||||||
return deobfuscationUseSourceNameAsAlias;
|
return deobfuscationUseSourceNameAsAlias;
|
||||||
}
|
}
|
||||||
@@ -389,6 +408,10 @@ public class JadxCLIArgs {
|
|||||||
return deobfuscationParseKotlinMetadata;
|
return deobfuscationParseKotlinMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResourceNameSource getResourceNameSource() {
|
||||||
|
return resourceNameSource;
|
||||||
|
}
|
||||||
|
|
||||||
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
||||||
return useKotlinMethodsForVarNames;
|
return useKotlinMethodsForVarNames;
|
||||||
}
|
}
|
||||||
@@ -474,54 +497,58 @@ public class JadxCLIArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CommentsLevelConverter implements IStringConverter<CommentsLevel> {
|
public static class CommentsLevelConverter extends BaseEnumConverter<CommentsLevel> {
|
||||||
@Override
|
public CommentsLevelConverter() {
|
||||||
public CommentsLevel convert(String value) {
|
super(CommentsLevel::valueOf, CommentsLevel::values);
|
||||||
try {
|
|
||||||
return CommentsLevel.valueOf(value.toUpperCase());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
'\'' + value + "' is unknown comments level, possible values are: "
|
|
||||||
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class UseKotlinMethodsForVarNamesConverter implements IStringConverter<UseKotlinMethodsForVarNames> {
|
public static class UseKotlinMethodsForVarNamesConverter extends BaseEnumConverter<UseKotlinMethodsForVarNames> {
|
||||||
@Override
|
public UseKotlinMethodsForVarNamesConverter() {
|
||||||
public UseKotlinMethodsForVarNames convert(String value) {
|
super(UseKotlinMethodsForVarNames::valueOf, UseKotlinMethodsForVarNames::values);
|
||||||
try {
|
|
||||||
return UseKotlinMethodsForVarNames.valueOf(value.replace('-', '_').toUpperCase());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
'\'' + value + "' is unknown, possible values are: "
|
|
||||||
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DeobfuscationMapFileModeConverter implements IStringConverter<DeobfuscationMapFileMode> {
|
public static class DeobfuscationMapFileModeConverter extends BaseEnumConverter<DeobfuscationMapFileMode> {
|
||||||
@Override
|
public DeobfuscationMapFileModeConverter() {
|
||||||
public DeobfuscationMapFileMode convert(String value) {
|
super(DeobfuscationMapFileMode::valueOf, DeobfuscationMapFileMode::values);
|
||||||
try {
|
|
||||||
return DeobfuscationMapFileMode.valueOf(value.toUpperCase());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
'\'' + value + "' is unknown, possible values are: "
|
|
||||||
+ JadxCLIArgs.enumValuesString(DeobfuscationMapFileMode.values()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DecompilationModeConverter implements IStringConverter<DecompilationMode> {
|
public static class ResourceNameSourceConverter extends BaseEnumConverter<ResourceNameSource> {
|
||||||
|
public ResourceNameSourceConverter() {
|
||||||
|
super(ResourceNameSource::valueOf, ResourceNameSource::values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DecompilationModeConverter extends BaseEnumConverter<DecompilationMode> {
|
||||||
|
public DecompilationModeConverter() {
|
||||||
|
super(DecompilationMode::valueOf, DecompilationMode::values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LogLevelConverter extends BaseEnumConverter<LogHelper.LogLevelEnum> {
|
||||||
|
public LogLevelConverter() {
|
||||||
|
super(LogHelper.LogLevelEnum::valueOf, LogHelper.LogLevelEnum::values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class BaseEnumConverter<E extends Enum<E>> implements IStringConverter<E> {
|
||||||
|
private final Function<String, E> parse;
|
||||||
|
private final Supplier<E[]> values;
|
||||||
|
|
||||||
|
public BaseEnumConverter(Function<String, E> parse, Supplier<E[]> values) {
|
||||||
|
this.parse = parse;
|
||||||
|
this.values = values;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DecompilationMode convert(String value) {
|
public E convert(String value) {
|
||||||
try {
|
try {
|
||||||
return DecompilationMode.valueOf(value.toUpperCase());
|
return parse.apply(stringAsEnumName(value));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
'\'' + value + "' is unknown, possible values are: "
|
'\'' + value + "' is unknown, possible values are: " + enumValuesString(values.get()));
|
||||||
+ JadxCLIArgs.enumValuesString(DecompilationMode.values()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -531,4 +558,9 @@ public class JadxCLIArgs {
|
|||||||
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
|
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
|
||||||
.collect(Collectors.joining(", "));
|
.collect(Collectors.joining(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String stringAsEnumName(String value) {
|
||||||
|
// inverse of enumValuesString conversion
|
||||||
|
return value.replace('-', '_').toUpperCase(Locale.ROOT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.beust.jcommander.IStringConverter;
|
|
||||||
|
|
||||||
import ch.qos.logback.classic.Level;
|
import ch.qos.logback.classic.Level;
|
||||||
import ch.qos.logback.classic.Logger;
|
import ch.qos.logback.classic.Logger;
|
||||||
|
|
||||||
@@ -119,18 +117,4 @@ public class LogHelper {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class LogLevelConverter implements IStringConverter<LogLevelEnum> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LogLevelEnum convert(String value) {
|
|
||||||
try {
|
|
||||||
return LogLevelEnum.valueOf(value.toUpperCase());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
'\'' + value + "' is unknown log level, possible values are "
|
|
||||||
+ JadxCLIArgs.enumValuesString(LogLevelEnum.values()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
package jadx.cli;
|
package jadx.cli;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static jadx.core.utils.Utils.newConstStringMap;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
@@ -47,6 +52,40 @@ public class JadxCLIArgsTest {
|
|||||||
assertThat(override(args, "").isUseImports(), is(false));
|
assertThat(override(args, "").isUseImports(), is(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPluginOptionsOverride() {
|
||||||
|
// add key to empty base map
|
||||||
|
checkPluginOptionsMerge(
|
||||||
|
Collections.emptyMap(),
|
||||||
|
"-Poption=otherValue",
|
||||||
|
newConstStringMap("option", "otherValue"));
|
||||||
|
|
||||||
|
// override one key
|
||||||
|
checkPluginOptionsMerge(
|
||||||
|
newConstStringMap("option", "value"),
|
||||||
|
"-Poption=otherValue",
|
||||||
|
newConstStringMap("option", "otherValue"));
|
||||||
|
|
||||||
|
// merge different keys
|
||||||
|
checkPluginOptionsMerge(
|
||||||
|
Collections.singletonMap("option1", "value1"),
|
||||||
|
"-Poption2=otherValue2",
|
||||||
|
newConstStringMap("option1", "value1", "option2", "otherValue2"));
|
||||||
|
|
||||||
|
// merge and override
|
||||||
|
checkPluginOptionsMerge(
|
||||||
|
newConstStringMap("option1", "value1", "option2", "value2"),
|
||||||
|
"-Poption2=otherValue2",
|
||||||
|
newConstStringMap("option1", "value1", "option2", "otherValue2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPluginOptionsMerge(Map<String, String> baseMap, String providedArgs, Map<String, String> expectedMap) {
|
||||||
|
JadxCLIArgs args = new JadxCLIArgs();
|
||||||
|
args.pluginOptions = baseMap;
|
||||||
|
Map<String, String> resultMap = override(args, providedArgs).getPluginOptions();
|
||||||
|
assertThat(resultMap, Matchers.equalTo(expectedMap));
|
||||||
|
}
|
||||||
|
|
||||||
private JadxCLIArgs parse(String... args) {
|
private JadxCLIArgs parse(String... args) {
|
||||||
return parse(new JadxCLIArgs(), args);
|
return parse(new JadxCLIArgs(), args);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,33 @@ public class TestInput {
|
|||||||
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
|
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResourceOnly() throws Exception {
|
||||||
|
decode("resourceOnly", "samples/resources-only.apk");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decode(String tmpDirName, String apkSample) throws URISyntaxException, IOException {
|
||||||
|
List<String> args = new ArrayList<>();
|
||||||
|
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
||||||
|
args.add("-v");
|
||||||
|
args.add("-d");
|
||||||
|
args.add(tempDir.toAbsolutePath().toString());
|
||||||
|
|
||||||
|
URL resource = getClass().getClassLoader().getResource(apkSample);
|
||||||
|
assertThat(resource).isNotNull();
|
||||||
|
String sampleFile = resource.toURI().getRawPath();
|
||||||
|
args.add(sampleFile);
|
||||||
|
|
||||||
|
int result = JadxCLI.execute(args.toArray(new String[0]));
|
||||||
|
assertThat(result).isEqualTo(0);
|
||||||
|
List<Path> files = Files.find(
|
||||||
|
tempDir,
|
||||||
|
3,
|
||||||
|
(file, attr) -> file.getFileName().toString().equalsIgnoreCase("AndroidManifest.xml"))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
assertThat(files.isEmpty()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
|
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
|
||||||
List<String> args = new ArrayList<>();
|
List<String> args = new ArrayList<>();
|
||||||
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
||||||
|
|||||||
Binary file not shown.
@@ -5,23 +5,22 @@ plugins {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api(project(':jadx-plugins:jadx-plugins-api'))
|
api(project(':jadx-plugins:jadx-plugins-api'))
|
||||||
|
|
||||||
implementation 'com.google.code.gson:gson:2.9.0'
|
implementation 'com.google.code.gson:gson:2.10.1'
|
||||||
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
|
|
||||||
constraints {
|
// TODO: move resources decoding to separate plugin module
|
||||||
// Force protobuf version to prevent Java-7 issue
|
implementation 'com.android.tools.build:aapt2-proto:7.3.1-8691043'
|
||||||
implementation 'com.google.protobuf:protobuf-java:3.11.4'
|
implementation 'com.google.protobuf:protobuf-java:3.21.12' // forcing latest version
|
||||||
}
|
|
||||||
|
|
||||||
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||||
|
|
||||||
testRuntimeOnly(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'))
|
||||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||||
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
|
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
|
||||||
|
|
||||||
testImplementation 'org.eclipse.jdt:ecj:3.29.0'
|
testImplementation 'org.eclipse.jdt:ecj:3.32.0'
|
||||||
testImplementation 'tools.profiler:async-profiler:1.8.3'
|
testImplementation 'tools.profiler:async-profiler:2.9'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
package jadx.api;
|
|
||||||
|
|
||||||
public final class CodePosition {
|
|
||||||
|
|
||||||
private final int line;
|
|
||||||
private final int offset;
|
|
||||||
private final int pos;
|
|
||||||
|
|
||||||
public CodePosition(int line, int offset, int pos) {
|
|
||||||
this.line = line;
|
|
||||||
this.offset = offset;
|
|
||||||
this.pos = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CodePosition(int line) {
|
|
||||||
this(line, 0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public CodePosition(int line, int offset) {
|
|
||||||
this(line, offset, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPos() {
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLine() {
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getOffset() {
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
CodePosition that = (CodePosition) o;
|
|
||||||
return line == that.line && offset == that.offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return line + 31 * offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(line);
|
|
||||||
if (offset != 0) {
|
|
||||||
sb.append(':').append(offset);
|
|
||||||
}
|
|
||||||
if (pos > 0) {
|
|
||||||
sb.append('@').append(pos);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,21 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public interface ICodeCache {
|
public interface ICodeCache extends Closeable {
|
||||||
|
|
||||||
void add(String clsFullName, ICodeInfo codeInfo);
|
void add(String clsFullName, ICodeInfo codeInfo);
|
||||||
|
|
||||||
void remove(String clsFullName);
|
void remove(String clsFullName);
|
||||||
|
|
||||||
@Nullable
|
@NotNull
|
||||||
ICodeInfo get(String clsFullName);
|
ICodeInfo get(String clsFullName);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getCode(String clsFullName);
|
||||||
|
|
||||||
|
boolean contains(String clsFullName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import jadx.api.impl.SimpleCodeInfo;
|
import jadx.api.impl.SimpleCodeInfo;
|
||||||
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
|
|
||||||
public interface ICodeInfo {
|
public interface ICodeInfo {
|
||||||
|
|
||||||
@@ -10,7 +9,7 @@ public interface ICodeInfo {
|
|||||||
|
|
||||||
String getCodeStr();
|
String getCodeStr();
|
||||||
|
|
||||||
Map<Integer, Integer> getLineMapping();
|
ICodeMetadata getCodeMetadata();
|
||||||
|
|
||||||
Map<CodePosition, Object> getAnnotations();
|
boolean hasMetadata();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package jadx.api;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
|
||||||
public interface ICodeWriter {
|
public interface ICodeWriter {
|
||||||
String NL = System.getProperty("line.separator");
|
String NL = System.getProperty("line.separator");
|
||||||
@@ -38,13 +41,21 @@ public interface ICodeWriter {
|
|||||||
|
|
||||||
void setIndent(int indent);
|
void setIndent(int indent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return current line (only if metadata is supported)
|
||||||
|
*/
|
||||||
int getLine();
|
int getLine();
|
||||||
|
|
||||||
void attachDefinition(ILineAttributeNode obj);
|
/**
|
||||||
|
* Return start line position (only if metadata is supported)
|
||||||
|
*/
|
||||||
|
int getLineStartPos();
|
||||||
|
|
||||||
void attachAnnotation(Object obj);
|
void attachDefinition(ICodeNodeRef obj);
|
||||||
|
|
||||||
void attachLineAnnotation(Object obj);
|
void attachAnnotation(ICodeAnnotation obj);
|
||||||
|
|
||||||
|
void attachLineAnnotation(ICodeAnnotation obj);
|
||||||
|
|
||||||
void attachSourceLine(int sourceLine);
|
void attachSourceLine(int sourceLine);
|
||||||
|
|
||||||
@@ -56,5 +67,6 @@ public interface ICodeWriter {
|
|||||||
|
|
||||||
StringBuilder getRawBuf();
|
StringBuilder getRawBuf();
|
||||||
|
|
||||||
Map<CodePosition, Object> getRawAnnotations();
|
@ApiStatus.Internal
|
||||||
|
Map<Integer, ICodeAnnotation> getRawAnnotations();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
@@ -11,12 +12,18 @@ import java.util.Set;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.args.DeobfuscationMapFileMode;
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
|
import jadx.api.args.ResourceNameSource;
|
||||||
import jadx.api.data.ICodeData;
|
import jadx.api.data.ICodeData;
|
||||||
import jadx.api.impl.AnnotatedCodeWriter;
|
import jadx.api.impl.AnnotatedCodeWriter;
|
||||||
import jadx.api.impl.InMemoryCodeCache;
|
import jadx.api.impl.InMemoryCodeCache;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
public class JadxArgs {
|
public class JadxArgs {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(JadxArgs.class);
|
||||||
|
|
||||||
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
|
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
|
||||||
|
|
||||||
@@ -46,6 +53,7 @@ public class JadxArgs {
|
|||||||
private boolean extractFinally = true;
|
private boolean extractFinally = true;
|
||||||
private boolean inlineAnonymousClasses = true;
|
private boolean inlineAnonymousClasses = true;
|
||||||
private boolean inlineMethods = true;
|
private boolean inlineMethods = true;
|
||||||
|
private boolean allowInlineKotlinLambda = true;
|
||||||
|
|
||||||
private boolean skipResources = false;
|
private boolean skipResources = false;
|
||||||
private boolean skipSources = false;
|
private boolean skipSources = false;
|
||||||
@@ -55,12 +63,18 @@ public class JadxArgs {
|
|||||||
*/
|
*/
|
||||||
private Predicate<String> classFilter = null;
|
private Predicate<String> classFilter = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save dependencies for classes accepted by {@code classFilter}
|
||||||
|
*/
|
||||||
|
private boolean includeDependencies = false;
|
||||||
|
|
||||||
private boolean deobfuscationOn = false;
|
private boolean deobfuscationOn = false;
|
||||||
private boolean useSourceNameAsClassAlias = false;
|
private boolean useSourceNameAsClassAlias = false;
|
||||||
private boolean parseKotlinMetadata = false;
|
private boolean parseKotlinMetadata = false;
|
||||||
private File deobfuscationMapFile = null;
|
private File deobfuscationMapFile = null;
|
||||||
|
|
||||||
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||||
|
private ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
|
||||||
|
|
||||||
private int deobfuscationMinLength = 0;
|
private int deobfuscationMinLength = 0;
|
||||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||||
@@ -115,6 +129,19 @@ public class JadxArgs {
|
|||||||
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
|
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
inputFiles = null;
|
||||||
|
if (codeCache != null) {
|
||||||
|
codeCache.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Failed to close JadxArgs", e);
|
||||||
|
} finally {
|
||||||
|
codeCache = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<File> getInputFiles() {
|
public List<File> getInputFiles() {
|
||||||
return inputFiles;
|
return inputFiles;
|
||||||
}
|
}
|
||||||
@@ -237,6 +264,14 @@ public class JadxArgs {
|
|||||||
this.inlineMethods = inlineMethods;
|
this.inlineMethods = inlineMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAllowInlineKotlinLambda() {
|
||||||
|
return allowInlineKotlinLambda;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowInlineKotlinLambda(boolean allowInlineKotlinLambda) {
|
||||||
|
this.allowInlineKotlinLambda = allowInlineKotlinLambda;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isExtractFinally() {
|
public boolean isExtractFinally() {
|
||||||
return extractFinally;
|
return extractFinally;
|
||||||
}
|
}
|
||||||
@@ -261,6 +296,14 @@ public class JadxArgs {
|
|||||||
this.skipSources = skipSources;
|
this.skipSources = skipSources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setIncludeDependencies(boolean includeDependencies) {
|
||||||
|
this.includeDependencies = includeDependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIncludeDependencies() {
|
||||||
|
return includeDependencies;
|
||||||
|
}
|
||||||
|
|
||||||
public Predicate<String> getClassFilter() {
|
public Predicate<String> getClassFilter() {
|
||||||
return classFilter;
|
return classFilter;
|
||||||
}
|
}
|
||||||
@@ -337,6 +380,14 @@ public class JadxArgs {
|
|||||||
this.deobfuscationMapFile = deobfuscationMapFile;
|
this.deobfuscationMapFile = deobfuscationMapFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResourceNameSource getResourceNameSource() {
|
||||||
|
return resourceNameSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceNameSource(ResourceNameSource resourceNameSource) {
|
||||||
|
this.resourceNameSource = resourceNameSource;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEscapeUnicode() {
|
public boolean isEscapeUnicode() {
|
||||||
return escapeUnicode;
|
return escapeUnicode;
|
||||||
}
|
}
|
||||||
@@ -501,6 +552,22 @@ public class JadxArgs {
|
|||||||
this.pluginOptions = pluginOptions;
|
this.pluginOptions = pluginOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash of all options that can change result code
|
||||||
|
*/
|
||||||
|
public String makeCodeArgsHash() {
|
||||||
|
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
|
||||||
|
+ inlineAnonymousClasses + inlineMethods
|
||||||
|
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
|
||||||
|
+ resourceNameSource
|
||||||
|
+ parseKotlinMetadata + useKotlinMethodsForVarNames
|
||||||
|
+ insertDebugLines + extractFinally
|
||||||
|
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
|
||||||
|
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
|
||||||
|
+ commentsLevel + useDxInput + pluginOptions;
|
||||||
|
return FileUtils.md5Sum(argStr.getBytes(StandardCharsets.US_ASCII));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "JadxArgs{" + "inputFiles=" + inputFiles
|
return "JadxArgs{" + "inputFiles=" + inputFiles
|
||||||
@@ -513,12 +580,16 @@ public class JadxArgs {
|
|||||||
+ ", useImports=" + useImports
|
+ ", useImports=" + useImports
|
||||||
+ ", skipResources=" + skipResources
|
+ ", skipResources=" + skipResources
|
||||||
+ ", skipSources=" + skipSources
|
+ ", skipSources=" + skipSources
|
||||||
|
+ ", includeDependencies=" + includeDependencies
|
||||||
+ ", deobfuscationOn=" + deobfuscationOn
|
+ ", deobfuscationOn=" + deobfuscationOn
|
||||||
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
||||||
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
|
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
|
||||||
|
+ ", resourceNameSource=" + resourceNameSource
|
||||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||||
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||||
|
+ ", insertDebugLines=" + insertDebugLines
|
||||||
|
+ ", extractFinally=" + extractFinally
|
||||||
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
||||||
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
||||||
+ ", escapeUnicode=" + escapeUnicode
|
+ ", escapeUnicode=" + escapeUnicode
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ public class JadxArgsValidator {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class);
|
||||||
|
|
||||||
public static void validate(JadxArgs args) {
|
public static void validate(JadxDecompiler jadx) {
|
||||||
checkInputFiles(args);
|
JadxArgs args = jadx.getArgs();
|
||||||
|
checkInputFiles(jadx, args);
|
||||||
validateOutDirs(args);
|
validateOutDirs(args);
|
||||||
|
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
@@ -22,9 +23,9 @@ public class JadxArgsValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkInputFiles(JadxArgs args) {
|
private static void checkInputFiles(JadxDecompiler jadx, JadxArgs args) {
|
||||||
List<File> inputFiles = args.getInputFiles();
|
List<File> inputFiles = args.getInputFiles();
|
||||||
if (inputFiles.isEmpty()) {
|
if (inputFiles.isEmpty() && jadx.getCustomLoads().isEmpty()) {
|
||||||
throw new JadxArgsValidateException("Please specify input file");
|
throw new JadxArgsValidateException("Please specify input file");
|
||||||
}
|
}
|
||||||
for (File inputFile : inputFiles) {
|
for (File inputFile : inputFiles) {
|
||||||
@@ -66,19 +67,22 @@ public class JadxArgsValidator {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static File makeDirFromInput(JadxArgs args) {
|
private static File makeDirFromInput(JadxArgs args) {
|
||||||
File outDir;
|
|
||||||
String outDirName;
|
String outDirName;
|
||||||
File file = args.getInputFiles().get(0);
|
List<File> inputFiles = args.getInputFiles();
|
||||||
String name = file.getName();
|
if (inputFiles.isEmpty()) {
|
||||||
int pos = name.lastIndexOf('.');
|
outDirName = JadxArgs.DEFAULT_OUT_DIR;
|
||||||
if (pos != -1) {
|
|
||||||
outDirName = name.substring(0, pos);
|
|
||||||
} else {
|
} else {
|
||||||
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
|
File file = inputFiles.get(0);
|
||||||
|
String name = file.getName();
|
||||||
|
int pos = name.lastIndexOf('.');
|
||||||
|
if (pos != -1) {
|
||||||
|
outDirName = name.substring(0, pos);
|
||||||
|
} else {
|
||||||
|
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
LOG.info("output directory: {}", outDirName);
|
LOG.info("output directory: {}", outDirName);
|
||||||
outDir = new File(outDirName);
|
return new File(outDirName);
|
||||||
return outDir;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkFile(File file) {
|
private static void checkFile(File file) {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
@@ -25,7 +24,11 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.data.annotations.VarRef;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||||
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
|
import jadx.api.metadata.annotations.VarRef;
|
||||||
import jadx.api.plugins.JadxPlugin;
|
import jadx.api.plugins.JadxPlugin;
|
||||||
import jadx.api.plugins.JadxPluginManager;
|
import jadx.api.plugins.JadxPluginManager;
|
||||||
import jadx.api.plugins.input.JadxInputPlugin;
|
import jadx.api.plugins.input.JadxInputPlugin;
|
||||||
@@ -33,9 +36,6 @@ import jadx.api.plugins.input.data.ILoadResult;
|
|||||||
import jadx.api.plugins.options.JadxPluginOptions;
|
import jadx.api.plugins.options.JadxPluginOptions;
|
||||||
import jadx.core.Jadx;
|
import jadx.core.Jadx;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
|
||||||
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
|
||||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
@@ -92,11 +92,9 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
private BinaryXMLParser binaryXmlParser;
|
private BinaryXMLParser binaryXmlParser;
|
||||||
private ProtoXMLParser protoXmlParser;
|
private ProtoXMLParser protoXmlParser;
|
||||||
|
|
||||||
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
|
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
|
||||||
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
|
||||||
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
|
private final List<ILoadResult> customLoads = new ArrayList<>();
|
||||||
|
|
||||||
public JadxDecompiler() {
|
public JadxDecompiler() {
|
||||||
this(new JadxArgs());
|
this(new JadxArgs());
|
||||||
@@ -108,7 +106,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
|
|
||||||
public void load() {
|
public void load() {
|
||||||
reset();
|
reset();
|
||||||
JadxArgsValidator.validate(args);
|
JadxArgsValidator.validate(this);
|
||||||
LOG.info("loading ...");
|
LOG.info("loading ...");
|
||||||
loadPlugins(args);
|
loadPlugins(args);
|
||||||
loadInputFiles();
|
loadInputFiles();
|
||||||
@@ -132,23 +130,33 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
loadedInputs.add(loadResult);
|
loadedInputs.add(loadResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
loadedInputs.addAll(customLoads);
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
|
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addCustomLoad(ILoadResult customLoad) {
|
||||||
|
customLoads.add(customLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ILoadResult> getCustomLoads() {
|
||||||
|
return customLoads;
|
||||||
|
}
|
||||||
|
|
||||||
private void reset() {
|
private void reset() {
|
||||||
root = null;
|
root = null;
|
||||||
classes = null;
|
classes = null;
|
||||||
resources = null;
|
resources = null;
|
||||||
binaryXmlParser = null;
|
binaryXmlParser = null;
|
||||||
protoXmlParser = null;
|
protoXmlParser = null;
|
||||||
|
}
|
||||||
|
|
||||||
classesMap.clear();
|
@Override
|
||||||
methodsMap.clear();
|
public void close() {
|
||||||
fieldsMap.clear();
|
reset();
|
||||||
|
|
||||||
closeInputs();
|
closeInputs();
|
||||||
|
args.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeInputs() {
|
private void closeInputs() {
|
||||||
@@ -162,11 +170,6 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
loadedInputs.clear();
|
loadedInputs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadPlugins(JadxArgs args) {
|
private void loadPlugins(JadxArgs args) {
|
||||||
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
|
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
|
||||||
pluginManager.load();
|
pluginManager.load();
|
||||||
@@ -188,6 +191,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public void registerPlugin(JadxPlugin plugin) {
|
public void registerPlugin(JadxPlugin plugin) {
|
||||||
pluginManager.register(plugin);
|
pluginManager.register(plugin);
|
||||||
}
|
}
|
||||||
@@ -229,6 +233,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
save(false, true);
|
save(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
private void save(boolean saveSources, boolean saveResources) {
|
private void save(boolean saveSources, boolean saveResources) {
|
||||||
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
||||||
ex.shutdown();
|
ex.shutdown();
|
||||||
@@ -304,9 +309,21 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
if (args.isSkipFilesSave()) {
|
if (args.isSkipFilesSave()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// process AndroidManifest.xml first to load complete resource ids table
|
||||||
|
for (ResourceFile resourceFile : getResources()) {
|
||||||
|
if (resourceFile.getType() == ResourceType.MANIFEST) {
|
||||||
|
new ResourcesSaver(outDir, resourceFile).run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
|
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
|
||||||
for (ResourceFile resourceFile : getResources()) {
|
for (ResourceFile resourceFile : getResources()) {
|
||||||
if (resourceFile.getType() != ResourceType.ARSC
|
ResourceType resType = resourceFile.getType();
|
||||||
|
if (resType == ResourceType.MANIFEST) {
|
||||||
|
// already processed
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (resType != ResourceType.ARSC
|
||||||
&& inputFileNames.contains(resourceFile.getOriginalName())) {
|
&& inputFileNames.contains(resourceFile.getOriginalName())) {
|
||||||
// ignore resource made from input file
|
// ignore resource made from input file
|
||||||
continue;
|
continue;
|
||||||
@@ -320,10 +337,14 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
List<JavaClass> classes = getClasses();
|
List<JavaClass> classes = getClasses();
|
||||||
List<JavaClass> processQueue = new ArrayList<>(classes.size());
|
List<JavaClass> processQueue = new ArrayList<>(classes.size());
|
||||||
for (JavaClass cls : classes) {
|
for (JavaClass cls : classes) {
|
||||||
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
|
ClassNode clsNode = cls.getClassNode();
|
||||||
|
if (clsNode.contains(AFlag.DONT_GENERATE)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (classFilter != null && !classFilter.test(cls.getFullName())) {
|
if (classFilter != null && !classFilter.test(clsNode.getClassInfo().getFullName())) {
|
||||||
|
if (!args.isIncludeDependencies()) {
|
||||||
|
clsNode.add(AFlag.DONT_GENERATE);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
processQueue.add(cls);
|
processQueue.add(cls);
|
||||||
@@ -338,8 +359,9 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
tasks.add(() -> {
|
tasks.add(() -> {
|
||||||
for (JavaClass cls : decompileBatch) {
|
for (JavaClass cls : decompileBatch) {
|
||||||
try {
|
try {
|
||||||
ICodeInfo code = cls.getCodeInfo();
|
ClassNode clsNode = cls.getClassNode();
|
||||||
SaveCode.save(outDir, cls.getClassNode(), code);
|
ICodeInfo code = clsNode.getCode();
|
||||||
|
SaveCode.save(outDir, clsNode, code);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Error saving class: {}", cls, e);
|
LOG.error("Error saving class: {}", cls, e);
|
||||||
}
|
}
|
||||||
@@ -353,14 +375,14 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
if (classes == null) {
|
if (classes == null) {
|
||||||
List<ClassNode> classNodeList = root.getClasses(false);
|
List<ClassNode> classNodeList = root.getClasses();
|
||||||
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
||||||
classesMap.clear();
|
|
||||||
for (ClassNode classNode : classNodeList) {
|
for (ClassNode classNode : classNodeList) {
|
||||||
if (!classNode.contains(AFlag.DONT_GENERATE)) {
|
if (classNode.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaClass javaClass = new JavaClass(classNode, this);
|
continue;
|
||||||
clsList.add(javaClass);
|
}
|
||||||
classesMap.put(classNode, javaClass);
|
if (!classNode.getClassInfo().isInner()) {
|
||||||
|
clsList.add(convertClassNode(classNode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
classes = Collections.unmodifiableList(clsList);
|
classes = Collections.unmodifiableList(clsList);
|
||||||
@@ -368,7 +390,11 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ResourceFile> getResources() {
|
public List<JavaClass> getClassesWithInners() {
|
||||||
|
return Utils.collectionMap(root.getClasses(), this::convertClassNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized List<ResourceFile> getResources() {
|
||||||
if (resources == null) {
|
if (resources == null) {
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
@@ -425,6 +451,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
/**
|
/**
|
||||||
* Internal API. Not Stable!
|
* Internal API. Not Stable!
|
||||||
*/
|
*/
|
||||||
|
@ApiStatus.Internal
|
||||||
public RootNode getRoot() {
|
public RootNode getRoot() {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
@@ -443,128 +470,40 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return protoXmlParser;
|
return protoXmlParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadJavaClass(JavaClass javaClass) {
|
|
||||||
javaClass.getMethods().forEach(mth -> methodsMap.put(mth.getMethodNode(), mth));
|
|
||||||
javaClass.getFields().forEach(fld -> fieldsMap.put(fld.getFieldNode(), fld));
|
|
||||||
|
|
||||||
for (JavaClass innerCls : javaClass.getInnerClasses()) {
|
|
||||||
classesMap.put(innerCls.getClassNode(), innerCls);
|
|
||||||
loadJavaClass(innerCls);
|
|
||||||
}
|
|
||||||
for (JavaClass inlinedCls : javaClass.getInlinedClasses()) {
|
|
||||||
classesMap.put(inlinedCls.getClassNode(), inlinedCls);
|
|
||||||
loadJavaClass(inlinedCls);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get JavaClass by ClassNode without loading and decompilation
|
* Get JavaClass by ClassNode without loading and decompilation
|
||||||
*/
|
*/
|
||||||
JavaClass convertClassNode(ClassNode cls) {
|
|
||||||
return classesMap.compute(cls, (node, prevJavaCls) -> {
|
|
||||||
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) {
|
|
||||||
// keep previous variable
|
|
||||||
return prevJavaCls;
|
|
||||||
}
|
|
||||||
if (cls.isInner()) {
|
|
||||||
return new JavaClass(cls, convertClassNode(cls.getParentClass()));
|
|
||||||
}
|
|
||||||
return new JavaClass(cls, this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable("For not generated classes")
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
public JavaClass getJavaClassByNode(ClassNode cls) {
|
synchronized JavaClass convertClassNode(ClassNode cls) {
|
||||||
JavaClass javaClass = classesMap.get(cls);
|
JavaClass javaClass = cls.getJavaNode();
|
||||||
if (javaClass != null && javaClass.getClassNode() == cls) {
|
|
||||||
return javaClass;
|
|
||||||
}
|
|
||||||
// load parent class if inner
|
|
||||||
ClassNode parentClass = cls.getTopParentClass();
|
|
||||||
if (parentClass.contains(AFlag.DONT_GENERATE)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JavaClass parentJavaClass = classesMap.get(parentClass);
|
|
||||||
if (parentJavaClass == null) {
|
|
||||||
getClasses();
|
|
||||||
parentJavaClass = classesMap.get(parentClass);
|
|
||||||
}
|
|
||||||
if (parentJavaClass != null) {
|
|
||||||
loadJavaClass(parentJavaClass);
|
|
||||||
javaClass = classesMap.get(cls);
|
|
||||||
if (javaClass != null) {
|
|
||||||
return javaClass;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// class or parent classes can be excluded from generation
|
|
||||||
if (cls.hasNotGeneratedParent()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
throw new JadxRuntimeException("JavaClass not found by ClassNode: " + cls);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private JavaMethod getJavaMethodByNode(MethodNode mth) {
|
|
||||||
JavaMethod javaMethod = methodsMap.get(mth);
|
|
||||||
if (javaMethod != null && javaMethod.getMethodNode() == mth) {
|
|
||||||
return javaMethod;
|
|
||||||
}
|
|
||||||
if (mth.contains(AFlag.DONT_GENERATE)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// parent class not loaded yet
|
|
||||||
ClassNode parentClass = mth.getParentClass();
|
|
||||||
ClassNode codeCls = getCodeParentClass(parentClass);
|
|
||||||
JavaClass javaClass = getJavaClassByNode(codeCls);
|
|
||||||
if (javaClass == null) {
|
if (javaClass == null) {
|
||||||
return null;
|
javaClass = cls.isInner()
|
||||||
|
? new JavaClass(cls, convertClassNode(cls.getParentClass()))
|
||||||
|
: new JavaClass(cls, this);
|
||||||
|
cls.setJavaNode(javaClass);
|
||||||
}
|
}
|
||||||
loadJavaClass(javaClass);
|
return javaClass;
|
||||||
javaMethod = methodsMap.get(mth);
|
|
||||||
if (javaMethod != null) {
|
|
||||||
return javaMethod;
|
|
||||||
}
|
|
||||||
if (parentClass.hasNotGeneratedParent()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClassNode getCodeParentClass(ClassNode cls) {
|
@ApiStatus.Internal
|
||||||
ClassNode codeCls;
|
synchronized JavaField convertFieldNode(FieldNode fld) {
|
||||||
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
|
JavaField javaField = fld.getJavaNode();
|
||||||
if (inlinedAttr != null) {
|
if (javaField == null) {
|
||||||
codeCls = inlinedAttr.getInlineCls().getTopParentClass();
|
JavaClass parentCls = convertClassNode(fld.getParentClass());
|
||||||
} else {
|
javaField = new JavaField(parentCls, fld);
|
||||||
codeCls = cls.getTopParentClass();
|
fld.setJavaNode(javaField);
|
||||||
}
|
}
|
||||||
if (codeCls == cls) {
|
return javaField;
|
||||||
return codeCls;
|
|
||||||
}
|
|
||||||
return getCodeParentClass(codeCls);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@ApiStatus.Internal
|
||||||
private JavaField getJavaFieldByNode(FieldNode fld) {
|
synchronized JavaMethod convertMethodNode(MethodNode mth) {
|
||||||
JavaField javaField = fieldsMap.get(fld);
|
JavaMethod javaMethod = mth.getJavaNode();
|
||||||
if (javaField != null && javaField.getFieldNode() == fld) {
|
if (javaMethod == null) {
|
||||||
return javaField;
|
javaMethod = new JavaMethod(convertClassNode(mth.getParentClass()), mth);
|
||||||
|
mth.setJavaNode(javaMethod);
|
||||||
}
|
}
|
||||||
// parent class not loaded yet
|
return javaMethod;
|
||||||
JavaClass javaClass = getJavaClassByNode(fld.getParentClass().getTopParentClass());
|
|
||||||
if (javaClass == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
loadJavaClass(javaClass);
|
|
||||||
javaField = fieldsMap.get(fld);
|
|
||||||
if (javaField != null) {
|
|
||||||
return javaField;
|
|
||||||
}
|
|
||||||
if (fld.getParentClass().hasNotGeneratedParent()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -572,7 +511,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return getRoot().getClasses().stream()
|
return getRoot().getClasses().stream()
|
||||||
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
|
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.map(this::getJavaClassByNode)
|
.map(this::convertClassNode)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -593,9 +532,9 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
if (node.contains(AFlag.DONT_GENERATE)) {
|
if (node.contains(AFlag.DONT_GENERATE)) {
|
||||||
return getJavaClassByNode(node.getTopParentClass());
|
return convertClassNode(node.getTopParentClass());
|
||||||
} else {
|
} else {
|
||||||
return getJavaClassByNode(node);
|
return convertClassNode(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -606,86 +545,87 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return getRoot().getClasses().stream()
|
return getRoot().getClasses().stream()
|
||||||
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
|
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.map(this::getJavaClassByNode)
|
.map(this::convertClassNode)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
JavaNode convertNode(Object obj) {
|
public JavaNode getJavaNodeByRef(ICodeNodeRef ann) {
|
||||||
if (obj instanceof VarRef) {
|
return getJavaNodeByCodeAnnotation(null, ann);
|
||||||
VarRef varRef = (VarRef) obj;
|
}
|
||||||
MethodNode mthNode = varRef.getMth();
|
|
||||||
JavaMethod mth = getJavaMethodByNode(mthNode);
|
@Nullable
|
||||||
if (mth == null) {
|
public JavaNode getJavaNodeByCodeAnnotation(@Nullable ICodeInfo codeInfo, @Nullable ICodeAnnotation ann) {
|
||||||
|
if (ann == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
switch (ann.getAnnType()) {
|
||||||
|
case CLASS:
|
||||||
|
return convertClassNode((ClassNode) ann);
|
||||||
|
case METHOD:
|
||||||
|
return convertMethodNode((MethodNode) ann);
|
||||||
|
case FIELD:
|
||||||
|
return convertFieldNode((FieldNode) ann);
|
||||||
|
case DECLARATION:
|
||||||
|
return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode());
|
||||||
|
case VAR:
|
||||||
|
return resolveVarNode((VarNode) ann);
|
||||||
|
case VAR_REF:
|
||||||
|
return resolveVarRef(codeInfo, (VarRef) ann);
|
||||||
|
case OFFSET:
|
||||||
|
// offset annotation don't have java node object
|
||||||
return null;
|
return null;
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unknown annotation type: " + ann.getAnnType() + ", class: " + ann.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JavaVariable resolveVarNode(VarNode varNode) {
|
||||||
|
JavaMethod javaNode = convertMethodNode(varNode.getMth());
|
||||||
|
return new JavaVariable(javaNode, varNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private JavaVariable resolveVarRef(ICodeInfo codeInfo, VarRef varRef) {
|
||||||
|
if (codeInfo == null) {
|
||||||
|
throw new JadxRuntimeException("Missing code info for resolve VarRef: " + varRef);
|
||||||
|
}
|
||||||
|
ICodeAnnotation varNodeAnn = codeInfo.getCodeMetadata().getAt(varRef.getRefPos());
|
||||||
|
if (varNodeAnn != null && varNodeAnn.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) {
|
||||||
|
ICodeNodeRef nodeRef = ((NodeDeclareRef) varNodeAnn).getNode();
|
||||||
|
if (nodeRef.getAnnType() == ICodeAnnotation.AnnType.VAR) {
|
||||||
|
return resolveVarNode((VarNode) nodeRef);
|
||||||
}
|
}
|
||||||
return new JavaVariable(mth, varRef);
|
|
||||||
}
|
}
|
||||||
if (!(obj instanceof LineAttrNode)) {
|
return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
LineAttrNode node = (LineAttrNode) obj;
|
|
||||||
if (node.contains(AFlag.DONT_GENERATE)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (obj instanceof ClassNode) {
|
|
||||||
return convertClassNode((ClassNode) obj);
|
|
||||||
}
|
|
||||||
if (obj instanceof MethodNode) {
|
|
||||||
return getJavaMethodByNode(((MethodNode) obj));
|
|
||||||
}
|
|
||||||
if (obj instanceof FieldNode) {
|
|
||||||
return getJavaFieldByNode((FieldNode) obj);
|
|
||||||
}
|
|
||||||
throw new JadxRuntimeException("Unexpected node type: " + obj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make interface for all nodes in code annotations and add common method instead this
|
List<JavaNode> convertNodes(Collection<? extends ICodeNodeRef> nodesList) {
|
||||||
Object getInternalNode(JavaNode javaNode) {
|
|
||||||
if (javaNode instanceof JavaClass) {
|
|
||||||
return ((JavaClass) javaNode).getClassNode();
|
|
||||||
}
|
|
||||||
if (javaNode instanceof JavaMethod) {
|
|
||||||
return ((JavaMethod) javaNode).getMethodNode();
|
|
||||||
}
|
|
||||||
if (javaNode instanceof JavaField) {
|
|
||||||
return ((JavaField) javaNode).getFieldNode();
|
|
||||||
}
|
|
||||||
if (javaNode instanceof JavaVariable) {
|
|
||||||
return ((JavaVariable) javaNode).getVarRef();
|
|
||||||
}
|
|
||||||
throw new JadxRuntimeException("Unexpected node type: " + javaNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<JavaNode> convertNodes(Collection<?> nodesList) {
|
|
||||||
return nodesList.stream()
|
return nodesList.stream()
|
||||||
.map(this::convertNode)
|
.map(this::getJavaNodeByRef)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
|
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int pos) {
|
||||||
Map<CodePosition, Object> map = codeInfo.getAnnotations();
|
ICodeAnnotation ann = codeInfo.getCodeMetadata().getAt(pos);
|
||||||
if (map.isEmpty()) {
|
return getJavaNodeByCodeAnnotation(codeInfo, ann);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Object obj = map.get(new CodePosition(line, offset));
|
|
||||||
if (obj == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return convertNode(obj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public CodePosition getDefinitionPosition(JavaNode javaNode) {
|
public JavaNode getClosestJavaNode(ICodeInfo codeInfo, int pos) {
|
||||||
JavaClass jCls = javaNode.getTopParentClass();
|
ICodeAnnotation ann = codeInfo.getCodeMetadata().getClosestUp(pos);
|
||||||
jCls.decompile();
|
return getJavaNodeByCodeAnnotation(codeInfo, ann);
|
||||||
int defLine = javaNode.getDecompiledLine();
|
}
|
||||||
if (defLine == 0) {
|
|
||||||
|
@Nullable
|
||||||
|
public JavaNode getEnclosingNode(ICodeInfo codeInfo, int pos) {
|
||||||
|
ICodeNodeRef obj = codeInfo.getCodeMetadata().getNodeAt(pos);
|
||||||
|
if (obj == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new CodePosition(defLine, 0, javaNode.getDefPos());
|
return getJavaNodeByRef(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reloadCodeData() {
|
public void reloadCodeData() {
|
||||||
|
|||||||
@@ -8,17 +8,25 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
|
|
||||||
public final class JavaClass implements JavaNode {
|
public final class JavaClass implements JavaNode {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(JavaClass.class);
|
||||||
|
|
||||||
private final JadxDecompiler decompiler;
|
private final JadxDecompiler decompiler;
|
||||||
private final ClassNode cls;
|
private final ClassNode cls;
|
||||||
@@ -46,24 +54,24 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getCode() {
|
public String getCode() {
|
||||||
ICodeInfo code = getCodeInfo();
|
return getCodeInfo().getCodeStr();
|
||||||
if (code == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return code.getCodeStr();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICodeInfo getCodeInfo() {
|
public @NotNull ICodeInfo getCodeInfo() {
|
||||||
|
ICodeInfo code = load();
|
||||||
|
if (code != null) {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
return cls.decompile();
|
return cls.decompile();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void decompile() {
|
public void decompile() {
|
||||||
cls.decompile();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void reload() {
|
public synchronized ICodeInfo reload() {
|
||||||
listsLoaded = false;
|
listsLoaded = false;
|
||||||
cls.reloadCode();
|
return cls.reloadCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unload() {
|
public void unload() {
|
||||||
@@ -75,10 +83,22 @@ public final class JavaClass implements JavaNode {
|
|||||||
return cls.contains(AFlag.DONT_GENERATE);
|
return cls.contains(AFlag.DONT_GENERATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInner() {
|
||||||
|
return cls.isInner();
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized String getSmali() {
|
public synchronized String getSmali() {
|
||||||
return cls.getDisassembledCode();
|
return cls.getDisassembledCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||||
|
if (ann.getAnnType() == ICodeAnnotation.AnnType.CLASS) {
|
||||||
|
return ann.equals(cls);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal API. Not Stable!
|
* Internal API. Not Stable!
|
||||||
*/
|
*/
|
||||||
@@ -87,21 +107,34 @@ public final class JavaClass implements JavaNode {
|
|||||||
return cls;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void loadLists() {
|
/**
|
||||||
|
* Decompile class and loads internal lists of fields, methods, etc.
|
||||||
|
* Do nothing if already loaded.
|
||||||
|
*
|
||||||
|
* @return code info if decompilation was executed, null otherwise
|
||||||
|
*/
|
||||||
|
private synchronized @Nullable ICodeInfo load() {
|
||||||
if (listsLoaded) {
|
if (listsLoaded) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
listsLoaded = true;
|
listsLoaded = true;
|
||||||
decompile();
|
|
||||||
JadxDecompiler rootDecompiler = getRootDecompiler();
|
|
||||||
|
|
||||||
|
ICodeInfo code;
|
||||||
|
if (cls.getState().isProcessComplete()) {
|
||||||
|
// already decompiled -> class internals loaded
|
||||||
|
code = null;
|
||||||
|
} else {
|
||||||
|
code = cls.decompile();
|
||||||
|
}
|
||||||
|
|
||||||
|
JadxDecompiler rootDecompiler = getRootDecompiler();
|
||||||
int inClsCount = cls.getInnerClasses().size();
|
int inClsCount = cls.getInnerClasses().size();
|
||||||
if (inClsCount != 0) {
|
if (inClsCount != 0) {
|
||||||
List<JavaClass> list = new ArrayList<>(inClsCount);
|
List<JavaClass> list = new ArrayList<>(inClsCount);
|
||||||
for (ClassNode inner : cls.getInnerClasses()) {
|
for (ClassNode inner : cls.getInnerClasses()) {
|
||||||
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
||||||
javaClass.loadLists();
|
javaClass.load();
|
||||||
list.add(javaClass);
|
list.add(javaClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +145,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
|
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
|
||||||
for (ClassNode inner : cls.getInlinedClasses()) {
|
for (ClassNode inner : cls.getInlinedClasses()) {
|
||||||
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
||||||
javaClass.loadLists();
|
javaClass.load();
|
||||||
list.add(javaClass);
|
list.add(javaClass);
|
||||||
}
|
}
|
||||||
this.inlinedClasses = Collections.unmodifiableList(list);
|
this.inlinedClasses = Collections.unmodifiableList(list);
|
||||||
@@ -123,8 +156,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
||||||
for (FieldNode f : cls.getFields()) {
|
for (FieldNode f : cls.getFields()) {
|
||||||
if (!f.contains(AFlag.DONT_GENERATE)) {
|
if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaField javaField = new JavaField(this, f);
|
flds.add(rootDecompiler.convertFieldNode(f));
|
||||||
flds.add(javaField);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.fields = Collections.unmodifiableList(flds);
|
this.fields = Collections.unmodifiableList(flds);
|
||||||
@@ -135,65 +167,56 @@ public final class JavaClass implements JavaNode {
|
|||||||
List<JavaMethod> mths = new ArrayList<>(methodsCount);
|
List<JavaMethod> mths = new ArrayList<>(methodsCount);
|
||||||
for (MethodNode m : cls.getMethods()) {
|
for (MethodNode m : cls.getMethods()) {
|
||||||
if (!m.contains(AFlag.DONT_GENERATE)) {
|
if (!m.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaMethod javaMethod = new JavaMethod(this, m);
|
mths.add(rootDecompiler.convertMethodNode(m));
|
||||||
mths.add(javaMethod);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mths.sort(Comparator.comparing(JavaMethod::getName));
|
mths.sort(Comparator.comparing(JavaMethod::getName));
|
||||||
this.methods = Collections.unmodifiableList(mths);
|
this.methods = Collections.unmodifiableList(mths);
|
||||||
}
|
}
|
||||||
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected JadxDecompiler getRootDecompiler() {
|
JadxDecompiler getRootDecompiler() {
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
return parent.getRootDecompiler();
|
return parent.getRootDecompiler();
|
||||||
}
|
}
|
||||||
return decompiler;
|
return decompiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<CodePosition, Object> getCodeAnnotations() {
|
public ICodeAnnotation getAnnotationAt(int pos) {
|
||||||
ICodeInfo code = getCodeInfo();
|
return getCodeInfo().getCodeMetadata().getAt(pos);
|
||||||
if (code == null) {
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
return code.getAnnotations();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getAnnotationAt(CodePosition pos) {
|
public Map<Integer, JavaNode> getUsageMap() {
|
||||||
return getCodeAnnotations().get(pos);
|
Map<Integer, ICodeAnnotation> map = getCodeInfo().getCodeMetadata().getAsMap();
|
||||||
}
|
|
||||||
|
|
||||||
public Map<CodePosition, JavaNode> getUsageMap() {
|
|
||||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
|
||||||
if (map.isEmpty() || decompiler == null) {
|
if (map.isEmpty() || decompiler == null) {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
Map<CodePosition, JavaNode> resultMap = new HashMap<>(map.size());
|
Map<Integer, JavaNode> resultMap = new HashMap<>(map.size());
|
||||||
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
|
||||||
CodePosition codePosition = entry.getKey();
|
int codePosition = entry.getKey();
|
||||||
Object obj = entry.getValue();
|
ICodeAnnotation obj = entry.getValue();
|
||||||
JavaNode node = getRootDecompiler().convertNode(obj);
|
if (obj instanceof ICodeNodeRef) {
|
||||||
if (node != null) {
|
JavaNode node = getRootDecompiler().getJavaNodeByRef((ICodeNodeRef) obj);
|
||||||
resultMap.put(codePosition, node);
|
if (node != null) {
|
||||||
|
resultMap.put(codePosition, node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resultMap;
|
return resultMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CodePosition> getUsageFor(JavaNode javaNode) {
|
public List<Integer> getUsePlacesFor(ICodeInfo codeInfo, JavaNode javaNode) {
|
||||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
if (!codeInfo.hasMetadata()) {
|
||||||
if (map.isEmpty() || decompiler == null) {
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
Object internalNode = getRootDecompiler().getInternalNode(javaNode);
|
List<Integer> result = new ArrayList<>();
|
||||||
List<CodePosition> result = new ArrayList<>();
|
codeInfo.getCodeMetadata().searchDown(0, (pos, ann) -> {
|
||||||
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
if (javaNode.isOwnCodeAnnotation(ann)) {
|
||||||
CodePosition codePosition = entry.getKey();
|
result.add(pos);
|
||||||
Object obj = entry.getValue();
|
|
||||||
if (internalNode.equals(obj)) {
|
|
||||||
result.add(codePosition);
|
|
||||||
}
|
}
|
||||||
}
|
return null;
|
||||||
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,20 +225,8 @@ public final class JavaClass implements JavaNode {
|
|||||||
return getRootDecompiler().convertNodes(cls.getUseIn());
|
return getRootDecompiler().convertNodes(cls.getUseIn());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Deprecated
|
|
||||||
public JavaNode getJavaNodeAtPosition(int line, int offset) {
|
|
||||||
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Deprecated
|
|
||||||
public CodePosition getDefinitionPosition() {
|
|
||||||
return getRootDecompiler().getDefinitionPosition(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getSourceLine(int decompiledLine) {
|
public Integer getSourceLine(int decompiledLine) {
|
||||||
return getCodeInfo().getLineMapping().get(decompiledLine);
|
return getCodeInfo().getCodeMetadata().getLineMapping().get(decompiledLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -241,19 +252,37 @@ public final class JavaClass implements JavaNode {
|
|||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public JavaClass getOriginalTopParentClass() {
|
||||||
public JavaClass getTopParentClass() {
|
return parent == null ? this : parent.getOriginalTopParentClass();
|
||||||
if (cls.contains(AType.ANONYMOUS_CLASS)) {
|
|
||||||
// moved to usage class
|
|
||||||
return getParentForAnonymousClass();
|
|
||||||
}
|
|
||||||
return parent == null ? this : parent.getTopParentClass();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private JavaClass getParentForAnonymousClass() {
|
/**
|
||||||
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
|
* Return top parent class which contains code of this class.
|
||||||
ClassNode topParentClass = attr.getOuterCls().getTopParentClass();
|
* Code parent can be different from original parent after move or inline
|
||||||
return getRootDecompiler().convertClassNode(topParentClass);
|
*
|
||||||
|
* @return this if already a top class
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public JavaClass getTopParentClass() {
|
||||||
|
JavaClass codeParent = getCodeParent();
|
||||||
|
return codeParent == null ? this : codeParent.getTopParentClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return parent class which contains code of this class.
|
||||||
|
* Code parent can be different for original parent after move or inline
|
||||||
|
*/
|
||||||
|
public @Nullable JavaClass getCodeParent() {
|
||||||
|
AnonymousClassAttr anonymousClsAttr = cls.get(AType.ANONYMOUS_CLASS);
|
||||||
|
if (anonymousClsAttr != null) {
|
||||||
|
// moved to usage class
|
||||||
|
return getRootDecompiler().convertClassNode(anonymousClsAttr.getOuterCls());
|
||||||
|
}
|
||||||
|
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
|
||||||
|
if (inlinedAttr != null) {
|
||||||
|
return getRootDecompiler().convertClassNode(inlinedAttr.getInlineCls());
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccessInfo getAccessInfo() {
|
public AccessInfo getAccessInfo() {
|
||||||
@@ -261,22 +290,22 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaClass> getInnerClasses() {
|
public List<JavaClass> getInnerClasses() {
|
||||||
loadLists();
|
load();
|
||||||
return innerClasses;
|
return innerClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaClass> getInlinedClasses() {
|
public List<JavaClass> getInlinedClasses() {
|
||||||
loadLists();
|
load();
|
||||||
return inlinedClasses;
|
return inlinedClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaField> getFields() {
|
public List<JavaField> getFields() {
|
||||||
loadLists();
|
load();
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaMethod> getMethods() {
|
public List<JavaMethod> getMethods() {
|
||||||
loadLists();
|
load();
|
||||||
return methods;
|
return methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,7 +315,16 @@ public final class JavaClass implements JavaNode {
|
|||||||
if (methodNode == null) {
|
if (methodNode == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new JavaMethod(this, methodNode);
|
return getRootDecompiler().convertMethodNode(methodNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JavaClass> getDependencies() {
|
||||||
|
JadxDecompiler d = getRootDecompiler();
|
||||||
|
return ListUtils.map(cls.getDependencies(), d::convertClassNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalDepsCount() {
|
||||||
|
return cls.getTotalDepsCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -294,11 +332,6 @@ public final class JavaClass implements JavaNode {
|
|||||||
this.cls.getClassInfo().removeAlias();
|
this.cls.getClassInfo().removeAlias();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDecompiledLine() {
|
|
||||||
return cls.getDecompiledLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefPos() {
|
public int getDefPos() {
|
||||||
return cls.getDefPosition();
|
return cls.getDefPosition();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
@@ -50,11 +51,6 @@ public final class JavaField implements JavaNode {
|
|||||||
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
|
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDecompiledLine() {
|
|
||||||
return field.getDecompiledLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefPos() {
|
public int getDefPos() {
|
||||||
return field.getDefPosition();
|
return field.getDefPosition();
|
||||||
@@ -70,6 +66,14 @@ public final class JavaField implements JavaNode {
|
|||||||
this.field.getFieldInfo().removeAlias();
|
this.field.getFieldInfo().removeAlias();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||||
|
if (ann.getAnnType() == ICodeAnnotation.AnnType.FIELD) {
|
||||||
|
return ann.equals(field);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal API. Not Stable!
|
* Internal API. Not Stable!
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.ApiStatus;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
@@ -18,6 +19,7 @@ import jadx.core.utils.Utils;
|
|||||||
|
|
||||||
public final class JavaMethod implements JavaNode {
|
public final class JavaMethod implements JavaNode {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class);
|
||||||
|
|
||||||
private final MethodNode mth;
|
private final MethodNode mth;
|
||||||
private final JavaClass parent;
|
private final JavaClass parent;
|
||||||
|
|
||||||
@@ -78,7 +80,7 @@ public final class JavaMethod implements JavaNode {
|
|||||||
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
||||||
return ovrdAttr.getRelatedMthNodes().stream()
|
return ovrdAttr.getRelatedMthNodes().stream()
|
||||||
.map(m -> {
|
.map(m -> {
|
||||||
JavaMethod javaMth = (JavaMethod) decompiler.convertNode(m);
|
JavaMethod javaMth = decompiler.convertMethodNode(m);
|
||||||
if (javaMth == null) {
|
if (javaMth == null) {
|
||||||
LOG.warn("Failed convert to java method: {}", m);
|
LOG.warn("Failed convert to java method: {}", m);
|
||||||
}
|
}
|
||||||
@@ -96,11 +98,6 @@ public final class JavaMethod implements JavaNode {
|
|||||||
return mth.getMethodInfo().isClassInit();
|
return mth.getMethodInfo().isClassInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDecompiledLine() {
|
|
||||||
return mth.getDecompiledLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefPos() {
|
public int getDefPos() {
|
||||||
return mth.getDefPosition();
|
return mth.getDefPosition();
|
||||||
@@ -111,6 +108,14 @@ public final class JavaMethod implements JavaNode {
|
|||||||
this.mth.getMethodInfo().removeAlias();
|
this.mth.getMethodInfo().removeAlias();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||||
|
if (ann.getAnnType() == ICodeAnnotation.AnnType.METHOD) {
|
||||||
|
return ann.equals(mth);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal API. Not Stable!
|
* Internal API. Not Stable!
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package jadx.api;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
|
||||||
public interface JavaNode {
|
public interface JavaNode {
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
@@ -12,12 +14,12 @@ public interface JavaNode {
|
|||||||
|
|
||||||
JavaClass getTopParentClass();
|
JavaClass getTopParentClass();
|
||||||
|
|
||||||
int getDecompiledLine();
|
|
||||||
|
|
||||||
int getDefPos();
|
int getDefPos();
|
||||||
|
|
||||||
List<JavaNode> getUseIn();
|
List<JavaNode> getUseIn();
|
||||||
|
|
||||||
default void removeAlias() {
|
default void removeAlias() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isOwnCodeAnnotation(ICodeAnnotation ann);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
|
||||||
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||||
private final String name;
|
private final String name;
|
||||||
private final List<JavaClass> classes;
|
private final List<JavaClass> classes;
|
||||||
@@ -39,11 +41,6 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDecompiledLine() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefPos() {
|
public int getDefPos() {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -54,6 +51,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(@NotNull JavaPackage o) {
|
public int compareTo(@NotNull JavaPackage o) {
|
||||||
return name.compareTo(o.name);
|
return name.compareTo(o.name);
|
||||||
|
|||||||
@@ -4,17 +4,20 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.data.annotations.VarDeclareRef;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.api.data.annotations.VarRef;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
|
import jadx.api.metadata.annotations.VarRef;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
|
||||||
public class JavaVariable implements JavaNode {
|
public class JavaVariable implements JavaNode {
|
||||||
private final JavaMethod mth;
|
private final JavaMethod mth;
|
||||||
private final VarRef varRef;
|
private final VarNode varNode;
|
||||||
|
|
||||||
public JavaVariable(JavaMethod mth, VarRef varRef) {
|
public JavaVariable(JavaMethod mth, VarNode varNode) {
|
||||||
this.mth = mth;
|
this.mth = mth;
|
||||||
this.varRef = varRef;
|
this.varNode = varNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JavaMethod getMth() {
|
public JavaMethod getMth() {
|
||||||
@@ -22,26 +25,30 @@ public class JavaVariable implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getReg() {
|
public int getReg() {
|
||||||
return varRef.getReg();
|
return varNode.getReg();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSsa() {
|
public int getSsa() {
|
||||||
return varRef.getSsa();
|
return varNode.getSsa();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public @Nullable String getName() {
|
||||||
return varRef.getName();
|
return varNode.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
public VarRef getVarRef() {
|
public VarNode getVarNode() {
|
||||||
return varRef;
|
return varNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFullName() {
|
public String getFullName() {
|
||||||
return varRef.getType() + " " + varRef.getName() + " (r" + varRef.getReg() + "v" + varRef.getSsa() + ")";
|
return varNode.getType() + " " + varNode.getName() + " (r" + varNode.getReg() + "v" + varNode.getSsa() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getType() {
|
||||||
|
return ArgType.tryToResolveClassAlias(mth.getMethodNode().root(), varNode.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -54,20 +61,9 @@ public class JavaVariable implements JavaNode {
|
|||||||
return mth.getTopParentClass();
|
return mth.getTopParentClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDecompiledLine() {
|
|
||||||
if (varRef instanceof VarDeclareRef) {
|
|
||||||
return ((VarDeclareRef) varRef).getDecompiledLine();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefPos() {
|
public int getDefPos() {
|
||||||
if (varRef instanceof VarDeclareRef) {
|
return varNode.getDefPosition();
|
||||||
return ((VarDeclareRef) varRef).getDefPosition();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -75,9 +71,18 @@ public class JavaVariable implements JavaNode {
|
|||||||
return Collections.singletonList(mth);
|
return Collections.singletonList(mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||||
|
if (ann.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
|
||||||
|
VarRef varRef = (VarRef) ann;
|
||||||
|
return varRef.getRefPos() == getDefPos();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return varRef.hashCode();
|
return varNode.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -88,6 +93,6 @@ public class JavaVariable implements JavaNode {
|
|||||||
if (!(o instanceof JavaVariable)) {
|
if (!(o instanceof JavaVariable)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return varRef.equals(((JavaVariable) o).varRef);
|
return varNode.equals(((JavaVariable) o).varNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package jadx.api.args;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resources original name source (for deobfuscation)
|
||||||
|
*/
|
||||||
|
public enum ResourceNameSource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically select best name (default)
|
||||||
|
*/
|
||||||
|
AUTO,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force use resources provided names
|
||||||
|
*/
|
||||||
|
RESOURCES,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force use resources names from R class
|
||||||
|
*/
|
||||||
|
CODE,
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package jadx.api.data.annotations;
|
|
||||||
|
|
||||||
public interface ICodeRawOffset {
|
|
||||||
int getOffset();
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package jadx.api.data.annotations;
|
|
||||||
|
|
||||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
|
||||||
import jadx.core.dex.instructions.args.CodeVar;
|
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
|
||||||
|
|
||||||
public class VarDeclareRef extends VarRef implements ILineAttributeNode {
|
|
||||||
|
|
||||||
public static VarDeclareRef get(MethodNode mth, CodeVar codeVar) {
|
|
||||||
VarDeclareRef ref = new VarDeclareRef(mth, codeVar);
|
|
||||||
codeVar.setCachedVarRef(ref);
|
|
||||||
return ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int sourceLine;
|
|
||||||
private int decompiledLine;
|
|
||||||
private int defPosition;
|
|
||||||
|
|
||||||
private VarDeclareRef(MethodNode mth, CodeVar codeVar) {
|
|
||||||
super(mth, codeVar.getAnySsaVar());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSourceLine() {
|
|
||||||
return sourceLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSourceLine(int sourceLine) {
|
|
||||||
this.sourceLine = sourceLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDecompiledLine() {
|
|
||||||
return decompiledLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setDecompiledLine(int decompiledLine) {
|
|
||||||
this.decompiledLine = decompiledLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDefPosition() {
|
|
||||||
return defPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setDefPosition(int pos) {
|
|
||||||
this.defPosition = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "VarDeclareRef{r" + getReg() + 'v' + getSsa() + '}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
package jadx.api.data.annotations;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
|
||||||
import jadx.core.dex.instructions.args.CodeVar;
|
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
|
||||||
import jadx.core.dex.instructions.args.SSAVar;
|
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
|
||||||
|
|
||||||
public class VarRef {
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static VarRef get(MethodNode mth, RegisterArg reg) {
|
|
||||||
SSAVar ssaVar = reg.getSVar();
|
|
||||||
if (ssaVar == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
CodeVar codeVar = ssaVar.getCodeVar();
|
|
||||||
VarRef cachedVarRef = codeVar.getCachedVarRef();
|
|
||||||
if (cachedVarRef != null) {
|
|
||||||
if (cachedVarRef.getName() == null) {
|
|
||||||
cachedVarRef.setName(codeVar.getName());
|
|
||||||
}
|
|
||||||
return cachedVarRef;
|
|
||||||
}
|
|
||||||
VarRef newVarRef = new VarRef(mth, ssaVar);
|
|
||||||
codeVar.setCachedVarRef(newVarRef);
|
|
||||||
return newVarRef;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final MethodNode mth;
|
|
||||||
private final int reg;
|
|
||||||
private final int ssa;
|
|
||||||
private final ArgType type;
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
protected VarRef(MethodNode mth, SSAVar ssaVar) {
|
|
||||||
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
|
|
||||||
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private VarRef(MethodNode mth, int reg, int ssa, ArgType type, String name) {
|
|
||||||
this.mth = mth;
|
|
||||||
this.reg = reg;
|
|
||||||
this.ssa = ssa;
|
|
||||||
this.type = type;
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MethodNode getMth() {
|
|
||||||
return mth;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getReg() {
|
|
||||||
return reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSsa() {
|
|
||||||
return ssa;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArgType getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!(o instanceof VarRef)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
VarRef other = (VarRef) o;
|
|
||||||
return getReg() == other.getReg()
|
|
||||||
&& getSsa() == other.getSsa()
|
|
||||||
&& getMth().equals(other.getMth());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return 31 * getReg() + getSsa();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "VarUseRef{r" + reg + 'v' + ssa + '}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ package jadx.api.data.impl;
|
|||||||
import jadx.api.JavaVariable;
|
import jadx.api.JavaVariable;
|
||||||
import jadx.api.data.CodeRefType;
|
import jadx.api.data.CodeRefType;
|
||||||
import jadx.api.data.IJavaCodeRef;
|
import jadx.api.data.IJavaCodeRef;
|
||||||
import jadx.api.data.annotations.VarRef;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
|
|
||||||
public class JadxCodeRef implements IJavaCodeRef {
|
public class JadxCodeRef implements IJavaCodeRef {
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@ public class JadxCodeRef implements IJavaCodeRef {
|
|||||||
return forVar(javaVariable.getReg(), javaVariable.getSsa());
|
return forVar(javaVariable.getReg(), javaVariable.getSsa());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JadxCodeRef forVar(VarRef varRef) {
|
public static JadxCodeRef forVar(VarNode varNode) {
|
||||||
return forVar(varRef.getReg(), varRef.getSsa());
|
return forVar(varNode.getReg(), varNode.getSsa());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JadxCodeRef forCatch(int handlerOffset) {
|
public static JadxCodeRef forCatch(int handlerOffset) {
|
||||||
|
|||||||
@@ -2,23 +2,19 @@ package jadx.api.impl;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
|
import jadx.api.metadata.impl.CodeMetadataStorage;
|
||||||
|
|
||||||
public class AnnotatedCodeInfo implements ICodeInfo {
|
public class AnnotatedCodeInfo implements ICodeInfo {
|
||||||
|
|
||||||
private final String code;
|
private final String code;
|
||||||
private final Map<Integer, Integer> lineMapping;
|
private final ICodeMetadata metadata;
|
||||||
private final Map<CodePosition, Object> annotations;
|
|
||||||
|
|
||||||
public AnnotatedCodeInfo(ICodeInfo codeInfo) {
|
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<Integer, ICodeAnnotation> annotations) {
|
||||||
this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations());
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<CodePosition, Object> annotations) {
|
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.lineMapping = lineMapping;
|
this.metadata = CodeMetadataStorage.build(lineMapping, annotations);
|
||||||
this.annotations = annotations;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -27,13 +23,13 @@ public class AnnotatedCodeInfo implements ICodeInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Integer, Integer> getLineMapping() {
|
public ICodeMetadata getCodeMetadata() {
|
||||||
return lineMapping;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<CodePosition, Object> getAnnotations() {
|
public boolean hasMetadata() {
|
||||||
return annotations;
|
return metadata != ICodeMetadata.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -5,18 +5,20 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||||
|
import jadx.api.metadata.annotations.VarRef;
|
||||||
import jadx.core.utils.StringUtils;
|
import jadx.core.utils.StringUtils;
|
||||||
|
|
||||||
public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter {
|
public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter {
|
||||||
|
|
||||||
private int line = 1;
|
private int line = 1;
|
||||||
private int offset;
|
private int offset;
|
||||||
private Map<CodePosition, Object> annotations = Collections.emptyMap();
|
private Map<Integer, ICodeAnnotation> annotations = Collections.emptyMap();
|
||||||
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
||||||
|
|
||||||
public AnnotatedCodeWriter() {
|
public AnnotatedCodeWriter() {
|
||||||
@@ -59,19 +61,17 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ICodeWriter add(ICodeWriter cw) {
|
public ICodeWriter add(ICodeWriter cw) {
|
||||||
if ((!(cw instanceof AnnotatedCodeWriter))) {
|
if (!cw.isMetadataSupported()) {
|
||||||
buf.append(cw.getCodeStr());
|
buf.append(cw.getCodeStr());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
|
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
|
||||||
line--;
|
line--;
|
||||||
int startLine = line;
|
|
||||||
int startPos = getLength();
|
int startPos = getLength();
|
||||||
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
|
for (Map.Entry<Integer, ICodeAnnotation> entry : code.annotations.entrySet()) {
|
||||||
CodePosition codePos = entry.getKey();
|
int pos = entry.getKey();
|
||||||
int newLine = startLine + codePos.getLine();
|
int newPos = startPos + pos;
|
||||||
int newPos = startPos + codePos.getPos();
|
attachAnnotation(entry.getValue(), newPos);
|
||||||
attachAnnotation(entry.getValue(), new CodePosition(newLine, codePos.getOffset(), newPos));
|
|
||||||
}
|
}
|
||||||
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
|
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
|
||||||
attachSourceLine(line + entry.getKey(), entry.getValue());
|
attachSourceLine(line + entry.getKey(), entry.getValue());
|
||||||
@@ -101,44 +101,36 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
|||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class DefinitionWrapper {
|
@Override
|
||||||
private final ILineAttributeNode node;
|
public int getLineStartPos() {
|
||||||
|
return getLength() - offset;
|
||||||
private DefinitionWrapper(ILineAttributeNode node) {
|
|
||||||
this.node = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ILineAttributeNode getNode() {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachDefinition(ILineAttributeNode obj) {
|
public void attachDefinition(ICodeNodeRef obj) {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
attachAnnotation(obj);
|
attachAnnotation(new NodeDeclareRef(obj));
|
||||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset, getLength()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachAnnotation(Object obj) {
|
public void attachAnnotation(ICodeAnnotation obj) {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
attachAnnotation(obj, new CodePosition(line, offset + 1, getLength()));
|
attachAnnotation(obj, getLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachLineAnnotation(Object obj) {
|
public void attachLineAnnotation(ICodeAnnotation obj) {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
attachAnnotation(obj, new CodePosition(line, 0, getLength() - offset));
|
attachAnnotation(obj, getLineStartPos());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void attachAnnotation(Object obj, CodePosition pos) {
|
private void attachAnnotation(ICodeAnnotation obj, int pos) {
|
||||||
if (annotations.isEmpty()) {
|
if (annotations.isEmpty()) {
|
||||||
annotations = new HashMap<>();
|
annotations = new HashMap<>();
|
||||||
}
|
}
|
||||||
@@ -164,29 +156,39 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
|||||||
public ICodeInfo finish() {
|
public ICodeInfo finish() {
|
||||||
removeFirstEmptyLine();
|
removeFirstEmptyLine();
|
||||||
processDefinitionAnnotations();
|
processDefinitionAnnotations();
|
||||||
|
validateAnnotations();
|
||||||
String code = buf.toString();
|
String code = buf.toString();
|
||||||
buf = null;
|
buf = null;
|
||||||
return new AnnotatedCodeInfo(code, lineMap, annotations);
|
return new AnnotatedCodeInfo(code, lineMap, annotations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<CodePosition, Object> getRawAnnotations() {
|
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
|
||||||
return annotations;
|
return annotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processDefinitionAnnotations() {
|
private void processDefinitionAnnotations() {
|
||||||
if (!annotations.isEmpty()) {
|
if (!annotations.isEmpty()) {
|
||||||
annotations.entrySet().removeIf(entry -> {
|
annotations.forEach((k, v) -> {
|
||||||
Object v = entry.getValue();
|
if (v instanceof NodeDeclareRef) {
|
||||||
if (v instanceof DefinitionWrapper) {
|
NodeDeclareRef declareRef = (NodeDeclareRef) v;
|
||||||
ILineAttributeNode l = ((DefinitionWrapper) v).getNode();
|
declareRef.setDefPos(k);
|
||||||
CodePosition codePos = entry.getKey();
|
declareRef.getNode().setDefPosition(k);
|
||||||
l.setDecompiledLine(codePos.getLine());
|
|
||||||
l.setDefPosition(codePos.getPos());
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateAnnotations() {
|
||||||
|
if (annotations.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
annotations.values().removeIf(v -> {
|
||||||
|
if (v.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
|
||||||
|
VarRef varRef = (VarRef) v;
|
||||||
|
return varRef.getRefPos() == 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.ICodeCache;
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
|
||||||
|
public abstract class DelegateCodeCache implements ICodeCache {
|
||||||
|
|
||||||
|
protected final ICodeCache backCache;
|
||||||
|
|
||||||
|
public DelegateCodeCache(ICodeCache backCache) {
|
||||||
|
this.backCache = backCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||||
|
backCache.add(clsFullName, codeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String clsFullName) {
|
||||||
|
backCache.remove(clsFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull ICodeInfo get(String clsFullName) {
|
||||||
|
return backCache.get(clsFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getCode(String clsFullName) {
|
||||||
|
return backCache.getCode(clsFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(String clsFullName) {
|
||||||
|
return backCache.contains(clsFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
backCache.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
package jadx.api.impl;
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.ICodeCache;
|
import jadx.api.ICodeCache;
|
||||||
@@ -22,9 +24,33 @@ public class InMemoryCodeCache implements ICodeCache {
|
|||||||
storage.remove(clsFullName);
|
storage.remove(clsFullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public @Nullable ICodeInfo get(String clsFullName) {
|
public ICodeInfo get(String clsFullName) {
|
||||||
return storage.get(clsFullName);
|
ICodeInfo codeInfo = storage.get(clsFullName);
|
||||||
|
if (codeInfo == null) {
|
||||||
|
return ICodeInfo.EMPTY;
|
||||||
|
}
|
||||||
|
return codeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String getCode(String clsFullName) {
|
||||||
|
ICodeInfo codeInfo = storage.get(clsFullName);
|
||||||
|
if (codeInfo == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return codeInfo.getCodeStr();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(String clsFullName) {
|
||||||
|
return storage.containsKey(clsFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
storage.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jadx.api.impl;
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.ICodeCache;
|
import jadx.api.ICodeCache;
|
||||||
@@ -7,6 +8,8 @@ import jadx.api.ICodeInfo;
|
|||||||
|
|
||||||
public class NoOpCodeCache implements ICodeCache {
|
public class NoOpCodeCache implements ICodeCache {
|
||||||
|
|
||||||
|
public static final NoOpCodeCache INSTANCE = new NoOpCodeCache();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void add(String clsFullName, ICodeInfo codeInfo) {
|
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||||
// do nothing
|
// do nothing
|
||||||
@@ -18,10 +21,26 @@ public class NoOpCodeCache implements ICodeCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable ICodeInfo get(String clsFullName) {
|
@NotNull
|
||||||
|
public ICodeInfo get(String clsFullName) {
|
||||||
|
return ICodeInfo.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String getCode(String clsFullName) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(String clsFullName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "NoOpCodeCache";
|
return "NoOpCodeCache";
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package jadx.api.impl;
|
package jadx.api.impl;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
|
|
||||||
public class SimpleCodeInfo implements ICodeInfo {
|
public class SimpleCodeInfo implements ICodeInfo {
|
||||||
|
|
||||||
@@ -20,13 +17,13 @@ public class SimpleCodeInfo implements ICodeInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Integer, Integer> getLineMapping() {
|
public ICodeMetadata getCodeMetadata() {
|
||||||
return Collections.emptyMap();
|
return ICodeMetadata.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<CodePosition, Object> getAnnotations() {
|
public boolean hasMetadata() {
|
||||||
return Collections.emptyMap();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import java.util.Map;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,17 +193,22 @@ public class SimpleCodeWriter implements ICodeWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachDefinition(ILineAttributeNode obj) {
|
public int getLineStartPos() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachDefinition(ICodeNodeRef obj) {
|
||||||
// no op
|
// no op
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachAnnotation(Object obj) {
|
public void attachAnnotation(ICodeAnnotation obj) {
|
||||||
// no op
|
// no op
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachLineAnnotation(Object obj) {
|
public void attachLineAnnotation(ICodeAnnotation obj) {
|
||||||
// no op
|
// no op
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +243,7 @@ public class SimpleCodeWriter implements ICodeWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<CodePosition, Object> getRawAnnotations() {
|
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package jadx.api.metadata;
|
||||||
|
|
||||||
|
public interface ICodeAnnotation {
|
||||||
|
|
||||||
|
enum AnnType {
|
||||||
|
CLASS,
|
||||||
|
FIELD,
|
||||||
|
METHOD,
|
||||||
|
VAR,
|
||||||
|
VAR_REF,
|
||||||
|
DECLARATION,
|
||||||
|
OFFSET,
|
||||||
|
END // class or method body end
|
||||||
|
}
|
||||||
|
|
||||||
|
AnnType getAnnType();
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package jadx.api.metadata;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.metadata.impl.CodeMetadataStorage;
|
||||||
|
|
||||||
|
public interface ICodeMetadata {
|
||||||
|
|
||||||
|
ICodeMetadata EMPTY = CodeMetadataStorage.empty();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
ICodeAnnotation getAt(int position);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
ICodeAnnotation getClosestUp(int position);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
ICodeAnnotation searchUp(int position, ICodeAnnotation.AnnType annType);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
ICodeAnnotation searchUp(int position, int limitPos, ICodeAnnotation.AnnType annType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate code annotations from {@code startPos} to smaller positions.
|
||||||
|
*
|
||||||
|
* @param visitor
|
||||||
|
* return not null value to stop iterations
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
<T> T searchUp(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate code annotations from {@code startPos} to higher positions.
|
||||||
|
*
|
||||||
|
* @param visitor
|
||||||
|
* return not null value to stop iterations
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
<T> T searchDown(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current node at position (can be enclosing class or method)
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
ICodeNodeRef getNodeAt(int position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any definition of class or method below position
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
ICodeNodeRef getNodeBelow(int position);
|
||||||
|
|
||||||
|
Map<Integer, ICodeAnnotation> getAsMap();
|
||||||
|
|
||||||
|
Map<Integer, Integer> getLineMapping();
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package jadx.api.metadata;
|
||||||
|
|
||||||
|
public interface ICodeNodeRef extends ICodeAnnotation {
|
||||||
|
int getDefPosition();
|
||||||
|
|
||||||
|
void setDefPosition(int pos);
|
||||||
|
}
|
||||||
+8
-3
@@ -1,11 +1,12 @@
|
|||||||
package jadx.api.data.annotations;
|
package jadx.api.metadata.annotations;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
|
||||||
public class InsnCodeOffset implements ICodeRawOffset {
|
public class InsnCodeOffset implements ICodeAnnotation {
|
||||||
|
|
||||||
public static void attach(ICodeWriter code, InsnNode insn) {
|
public static void attach(ICodeWriter code, InsnNode insn) {
|
||||||
if (insn == null) {
|
if (insn == null) {
|
||||||
@@ -40,11 +41,15 @@ public class InsnCodeOffset implements ICodeRawOffset {
|
|||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOffset() {
|
public int getOffset() {
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.OFFSET;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "offset=" + offset;
|
return "offset=" + offset;
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package jadx.api.metadata.annotations;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
|
||||||
|
public class NodeDeclareRef implements ICodeAnnotation {
|
||||||
|
|
||||||
|
private final ICodeNodeRef node;
|
||||||
|
|
||||||
|
private int defPos;
|
||||||
|
|
||||||
|
public NodeDeclareRef(ICodeNodeRef node) {
|
||||||
|
this.node = Objects.requireNonNull(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICodeNodeRef getNode() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDefPos() {
|
||||||
|
return defPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefPos(int defPos) {
|
||||||
|
this.defPos = defPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.DECLARATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof NodeDeclareRef)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return node.equals(((NodeDeclareRef) o).node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return node.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "NodeDeclareRef{" + node + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package jadx.api.metadata.annotations;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
|
||||||
|
public class NodeEnd implements ICodeAnnotation {
|
||||||
|
|
||||||
|
public static final NodeEnd VALUE = new NodeEnd();
|
||||||
|
|
||||||
|
private NodeEnd() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.END;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "END";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package jadx.api.metadata.annotations;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.CodeVar;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable info
|
||||||
|
*/
|
||||||
|
public class VarNode implements ICodeNodeRef {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static VarNode get(MethodNode mth, RegisterArg reg) {
|
||||||
|
SSAVar ssaVar = reg.getSVar();
|
||||||
|
if (ssaVar == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return get(mth, ssaVar);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static VarNode get(MethodNode mth, CodeVar codeVar) {
|
||||||
|
return get(mth, codeVar.getAnySsaVar());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static VarNode get(MethodNode mth, SSAVar ssaVar) {
|
||||||
|
CodeVar codeVar = ssaVar.getCodeVar();
|
||||||
|
if (codeVar.isThis()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
VarNode cachedVarNode = codeVar.getCachedVarNode();
|
||||||
|
if (cachedVarNode != null) {
|
||||||
|
return cachedVarNode;
|
||||||
|
}
|
||||||
|
VarNode newVarNode = new VarNode(mth, ssaVar);
|
||||||
|
codeVar.setCachedVarNode(newVarNode);
|
||||||
|
return newVarNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static ICodeAnnotation getRef(MethodNode mth, RegisterArg reg) {
|
||||||
|
VarNode varNode = get(mth, reg);
|
||||||
|
if (varNode == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return varNode.getVarRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final MethodNode mth;
|
||||||
|
private final int reg;
|
||||||
|
private final int ssa;
|
||||||
|
private final ArgType type;
|
||||||
|
private @Nullable String name;
|
||||||
|
private int defPos;
|
||||||
|
|
||||||
|
private final VarRef varRef;
|
||||||
|
|
||||||
|
protected VarNode(MethodNode mth, SSAVar ssaVar) {
|
||||||
|
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
|
||||||
|
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public VarNode(MethodNode mth, int reg, int ssa, ArgType type, String name) {
|
||||||
|
this.mth = mth;
|
||||||
|
this.reg = reg;
|
||||||
|
this.ssa = ssa;
|
||||||
|
this.type = type;
|
||||||
|
this.name = name;
|
||||||
|
this.varRef = VarRef.fromVarNode(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodNode getMth() {
|
||||||
|
return mth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReg() {
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSsa() {
|
||||||
|
return ssa;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VarRef getVarRef() {
|
||||||
|
return varRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefPosition() {
|
||||||
|
return defPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDefPosition(int pos) {
|
||||||
|
this.defPos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.VAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int h = 31 * getReg() + getSsa();
|
||||||
|
return 31 * h + mth.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof VarNode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
VarNode other = (VarNode) o;
|
||||||
|
return getReg() == other.getReg()
|
||||||
|
&& getSsa() == other.getSsa()
|
||||||
|
&& getMth().equals(other.getMth());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "VarNode{r" + reg + 'v' + ssa + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package jadx.api.metadata.annotations;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable reference by position of VarNode in code metadata.
|
||||||
|
* <br>
|
||||||
|
* Because on creation position not yet known,
|
||||||
|
* VarRef created using VarNode as a source of ref pos during serialization.
|
||||||
|
* <br>
|
||||||
|
* On metadata deserialization created with ref pos directly.
|
||||||
|
*/
|
||||||
|
public abstract class VarRef implements ICodeAnnotation {
|
||||||
|
|
||||||
|
public static VarRef fromPos(int refPos) {
|
||||||
|
if (refPos == 0) {
|
||||||
|
throw new IllegalArgumentException("Zero refPos");
|
||||||
|
}
|
||||||
|
return new FixedVarRef(refPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VarRef fromVarNode(VarNode varNode) {
|
||||||
|
return new RelatedVarRef(varNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract int getRefPos();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.VAR_REF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class FixedVarRef extends VarRef {
|
||||||
|
private final int refPos;
|
||||||
|
|
||||||
|
public FixedVarRef(int refPos) {
|
||||||
|
this.refPos = refPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRefPos() {
|
||||||
|
return refPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class RelatedVarRef extends VarRef {
|
||||||
|
private final VarNode varNode;
|
||||||
|
|
||||||
|
public RelatedVarRef(VarNode varNode) {
|
||||||
|
this.varNode = varNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRefPos() {
|
||||||
|
return varNode.getDefPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "VarRef{" + varNode + ", name=" + varNode.getName() + ", mth=" + varNode.getMth() + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "VarRef{" + getRefPos() + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package jadx.api.metadata.impl;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.NavigableMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeAnnotation.AnnType;
|
||||||
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
public class CodeMetadataStorage implements ICodeMetadata {
|
||||||
|
|
||||||
|
public static ICodeMetadata build(Map<Integer, Integer> lines, Map<Integer, ICodeAnnotation> map) {
|
||||||
|
if (map.isEmpty() && lines.isEmpty()) {
|
||||||
|
return ICodeMetadata.EMPTY;
|
||||||
|
}
|
||||||
|
Comparator<Integer> reverseCmp = Comparator.comparingInt(Integer::intValue).reversed();
|
||||||
|
NavigableMap<Integer, ICodeAnnotation> navMap = new TreeMap<>(reverseCmp);
|
||||||
|
navMap.putAll(map);
|
||||||
|
return new CodeMetadataStorage(lines, navMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ICodeMetadata empty() {
|
||||||
|
return new CodeMetadataStorage(Collections.emptyMap(), Collections.emptyNavigableMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<Integer, Integer> lines;
|
||||||
|
|
||||||
|
private final NavigableMap<Integer, ICodeAnnotation> navMap;
|
||||||
|
|
||||||
|
private CodeMetadataStorage(Map<Integer, Integer> lines, NavigableMap<Integer, ICodeAnnotation> navMap) {
|
||||||
|
this.lines = lines;
|
||||||
|
this.navMap = navMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICodeAnnotation getAt(int position) {
|
||||||
|
return navMap.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ICodeAnnotation getClosestUp(int position) {
|
||||||
|
Map.Entry<Integer, ICodeAnnotation> entryBefore = navMap.higherEntry(position);
|
||||||
|
return entryBefore != null ? entryBefore.getValue() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ICodeAnnotation searchUp(int position, AnnType annType) {
|
||||||
|
for (ICodeAnnotation v : navMap.tailMap(position, true).values()) {
|
||||||
|
if (v.getAnnType() == annType) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ICodeAnnotation searchUp(int position, int limitPos, AnnType annType) {
|
||||||
|
for (ICodeAnnotation v : navMap.subMap(position, true, limitPos, true).values()) {
|
||||||
|
if (v.getAnnType() == annType) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> @Nullable T searchUp(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor) {
|
||||||
|
for (Map.Entry<Integer, ICodeAnnotation> entry : navMap.tailMap(startPos, true).entrySet()) {
|
||||||
|
T value = visitor.apply(entry.getKey(), entry.getValue());
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> @Nullable T searchDown(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor) {
|
||||||
|
NavigableMap<Integer, ICodeAnnotation> map = navMap.headMap(startPos, true).descendingMap();
|
||||||
|
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
|
||||||
|
T value = visitor.apply(entry.getKey(), entry.getValue());
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICodeNodeRef getNodeAt(int position) {
|
||||||
|
int nesting = 0;
|
||||||
|
for (ICodeAnnotation ann : navMap.tailMap(position, true).values()) {
|
||||||
|
switch (ann.getAnnType()) {
|
||||||
|
case END:
|
||||||
|
nesting++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DECLARATION:
|
||||||
|
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
|
||||||
|
AnnType nodeType = node.getAnnType();
|
||||||
|
if (nodeType == AnnType.CLASS || nodeType == AnnType.METHOD) {
|
||||||
|
if (nesting == 0) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
nesting--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICodeNodeRef getNodeBelow(int position) {
|
||||||
|
for (ICodeAnnotation ann : navMap.headMap(position, true).descendingMap().values()) {
|
||||||
|
if (ann.getAnnType() == AnnType.DECLARATION) {
|
||||||
|
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
|
||||||
|
AnnType nodeType = node.getAnnType();
|
||||||
|
if (nodeType == AnnType.CLASS || nodeType == AnnType.METHOD) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NavigableMap<Integer, ICodeAnnotation> getAsMap() {
|
||||||
|
return navMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Integer, Integer> getLineMapping() {
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CodeMetadata{\nlines=" + lines
|
||||||
|
+ "\nannotations=\n " + Utils.listToString(navMap.descendingMap().entrySet(), "\n ") + "\n}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package jadx.api.utils;
|
||||||
|
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
|
|
||||||
|
public class CodeUtils {
|
||||||
|
|
||||||
|
public static String getLineForPos(String code, int pos) {
|
||||||
|
int start = getLineStartForPos(code, pos);
|
||||||
|
int end = getLineEndForPos(code, pos);
|
||||||
|
return code.substring(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getLineStartForPos(String code, int pos) {
|
||||||
|
String newLine = ICodeWriter.NL;
|
||||||
|
int start = code.lastIndexOf(newLine, pos);
|
||||||
|
return start == -1 ? 0 : start + newLine.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getLineEndForPos(String code, int pos) {
|
||||||
|
int end = code.indexOf(ICodeWriter.NL, pos);
|
||||||
|
return end == -1 ? code.length() : end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getLineNumForPos(String code, int pos) {
|
||||||
|
String newLine = ICodeWriter.NL;
|
||||||
|
int newLineLen = newLine.length();
|
||||||
|
int line = 1;
|
||||||
|
int prev = 0;
|
||||||
|
while (true) {
|
||||||
|
int next = code.indexOf(newLine, prev);
|
||||||
|
if (next >= pos) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
prev = next + newLineLen;
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ public class Consts {
|
|||||||
public static final boolean DEBUG_TYPE_INFERENCE = false;
|
public static final boolean DEBUG_TYPE_INFERENCE = false;
|
||||||
public static final boolean DEBUG_OVERLOADED_CASTS = false;
|
public static final boolean DEBUG_OVERLOADED_CASTS = false;
|
||||||
public static final boolean DEBUG_EXC_HANDLERS = false;
|
public static final boolean DEBUG_EXC_HANDLERS = false;
|
||||||
|
public static final boolean DEBUG_FINALLY = false;
|
||||||
|
|
||||||
public static final String CLASS_OBJECT = "java.lang.Object";
|
public static final String CLASS_OBJECT = "java.lang.Object";
|
||||||
public static final String CLASS_STRING = "java.lang.String";
|
public static final String CLASS_STRING = "java.lang.String";
|
||||||
|
|||||||
@@ -191,7 +191,6 @@ public class Jadx {
|
|||||||
passes.add(new ProcessInstructionsVisitor());
|
passes.add(new ProcessInstructionsVisitor());
|
||||||
|
|
||||||
passes.add(new BlockSplitter());
|
passes.add(new BlockSplitter());
|
||||||
passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK)));
|
|
||||||
if (args.isRawCFGOutput()) {
|
if (args.isRawCFGOutput()) {
|
||||||
passes.add(DotGraphVisitor.dumpRaw());
|
passes.add(DotGraphVisitor.dumpRaw());
|
||||||
}
|
}
|
||||||
@@ -215,9 +214,6 @@ public class Jadx {
|
|||||||
passes.add(new CodeShrinkVisitor());
|
passes.add(new CodeShrinkVisitor());
|
||||||
passes.add(new SimplifyVisitor());
|
passes.add(new SimplifyVisitor());
|
||||||
passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE)));
|
passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE)));
|
||||||
if (args.isRawCFGOutput()) {
|
|
||||||
passes.add(DotGraphVisitor.dumpRaw());
|
|
||||||
}
|
|
||||||
if (args.isCfgOutput()) {
|
if (args.isCfgOutput()) {
|
||||||
passes.add(DotGraphVisitor.dump());
|
passes.add(DotGraphVisitor.dump());
|
||||||
}
|
}
|
||||||
@@ -238,9 +234,17 @@ public class Jadx {
|
|||||||
private static String version;
|
private static String version;
|
||||||
|
|
||||||
public static String getVersion() {
|
public static String getVersion() {
|
||||||
if (version != null) {
|
if (version == null) {
|
||||||
return version;
|
version = searchJadxVersion();
|
||||||
}
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDevVersion() {
|
||||||
|
return getVersion().equals(VERSION_DEV);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String searchJadxVersion() {
|
||||||
try {
|
try {
|
||||||
ClassLoader classLoader = Jadx.class.getClassLoader();
|
ClassLoader classLoader = Jadx.class.getClassLoader();
|
||||||
if (classLoader != null) {
|
if (classLoader != null) {
|
||||||
@@ -250,7 +254,6 @@ public class Jadx {
|
|||||||
Manifest manifest = new Manifest(is);
|
Manifest manifest = new Manifest(is);
|
||||||
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
||||||
if (ver != null) {
|
if (ver != null) {
|
||||||
version = ver;
|
|
||||||
return ver;
|
return ver;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ public class ProcessClass {
|
|||||||
return generateCode(topParentClass);
|
return generateCode(topParentClass);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
process(cls, false);
|
||||||
|
return ICodeInfo.EMPTY;
|
||||||
|
}
|
||||||
for (ClassNode depCls : cls.getDependencies()) {
|
for (ClassNode depCls : cls.getDependencies()) {
|
||||||
process(depCls, false);
|
process(depCls, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,6 +203,9 @@ public class ClspGraph {
|
|||||||
if (isNew) {
|
if (isNew) {
|
||||||
addSuperTypes(parentCls, result);
|
addSuperTypes(parentCls, result);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// parent type is unknown
|
||||||
|
result.add(parentType.getObject());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import jadx.api.CommentsLevel;
|
|||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.metadata.annotations.NodeEnd;
|
||||||
import jadx.api.plugins.input.data.AccessFlags;
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedType;
|
import jadx.api.plugins.input.data.annotations.EncodedType;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
@@ -170,7 +171,7 @@ public class ClassGen {
|
|||||||
ArgType sup = cls.getSuperClass();
|
ArgType sup = cls.getSuperClass();
|
||||||
if (sup != null
|
if (sup != null
|
||||||
&& !sup.equals(ArgType.OBJECT)
|
&& !sup.equals(ArgType.OBJECT)
|
||||||
&& !cls.isEnum()) {
|
&& !cls.contains(AFlag.REMOVE_SUPER_CLASS)) {
|
||||||
clsCode.add("extends ");
|
clsCode.add("extends ");
|
||||||
useClass(clsCode, sup);
|
useClass(clsCode, sup);
|
||||||
clsCode.add(' ');
|
clsCode.add(' ');
|
||||||
@@ -256,6 +257,7 @@ public class ClassGen {
|
|||||||
addInnerClsAndMethods(clsCode);
|
addInnerClsAndMethods(clsCode);
|
||||||
clsCode.decIndent();
|
clsCode.decIndent();
|
||||||
clsCode.startLine('}');
|
clsCode.startLine('}');
|
||||||
|
clsCode.attachAnnotation(NodeEnd.VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addInnerClsAndMethods(ICodeWriter clsCode) {
|
private void addInnerClsAndMethods(ICodeWriter clsCode) {
|
||||||
@@ -320,19 +322,25 @@ public class ClassGen {
|
|||||||
if (inlineAttr == null || inlineAttr.notNeeded()) {
|
if (inlineAttr == null || inlineAttr.notNeeded()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (mth.getUseIn().isEmpty()) {
|
try {
|
||||||
mth.add(AFlag.DONT_GENERATE);
|
if (mth.getUseIn().isEmpty()) {
|
||||||
return true;
|
mth.add(AFlag.DONT_GENERATE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
List<MethodNode> useInCompleted = mth.getUseIn().stream()
|
||||||
|
.filter(m -> m.getTopParentClass().getState().isProcessComplete())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (useInCompleted.isEmpty()) {
|
||||||
|
mth.add(AFlag.DONT_GENERATE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
mth.addDebugComment("Method not inlined, still used in: " + useInCompleted);
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// check failed => keep method
|
||||||
|
mth.addWarnComment("Failed to check method usage", e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
List<MethodNode> useInCompleted = mth.getUseIn().stream()
|
|
||||||
.filter(m -> m.getTopParentClass().getState().isProcessComplete())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
if (useInCompleted.isEmpty()) {
|
|
||||||
mth.add(AFlag.DONT_GENERATE);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
mth.addDebugComment("Method not inlined, still used in: " + useInCompleted);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isMethodsPresents() {
|
private boolean isMethodsPresents() {
|
||||||
@@ -369,6 +377,7 @@ public class ClassGen {
|
|||||||
mthGen.addInstructions(code);
|
mthGen.addInstructions(code);
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
|
code.attachAnnotation(NodeEnd.VALUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,21 +623,23 @@ public class ClassGen {
|
|||||||
if (useCls.equals(extClsInfo)) {
|
if (useCls.equals(extClsInfo)) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
|
||||||
return shortName;
|
|
||||||
}
|
|
||||||
if (isClassInnerFor(useCls, extClsInfo)) {
|
if (isClassInnerFor(useCls, extClsInfo)) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
if (extClsInfo.isInner()) {
|
if (extClsInfo.isInner()) {
|
||||||
return expandInnerClassName(useCls, extClsInfo);
|
return expandInnerClassName(useCls, extClsInfo);
|
||||||
}
|
}
|
||||||
if (searchCollision(cls.root(), useCls, extClsInfo)) {
|
if (checkInnerCollision(cls.root(), useCls, extClsInfo)
|
||||||
|
|| checkInPackageCollision(cls.root(), useCls, extClsInfo)) {
|
||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
|
// don't add import for top classes from 'java.lang' package (subpackages excluded)
|
||||||
|
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
// don't add import if this class from same package
|
// don't add import if this class from same package
|
||||||
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
||||||
return shortName;
|
return shortName;
|
||||||
@@ -709,7 +720,7 @@ public class ClassGen {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean searchCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
|
private static boolean checkInnerCollision(RootNode root, @Nullable ClassInfo useCls, ClassInfo searchCls) {
|
||||||
if (useCls == null) {
|
if (useCls == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -726,7 +737,20 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return searchCollision(root, useCls.getParentClass(), searchCls);
|
return checkInnerCollision(root, useCls.getParentClass(), searchCls);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if class with same name exists in current package
|
||||||
|
*/
|
||||||
|
private static boolean checkInPackageCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
|
||||||
|
String currentPkg = useCls.getAliasPkg();
|
||||||
|
if (currentPkg.equals(searchCls.getAliasPkg())) {
|
||||||
|
// search class already from current package
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String shortName = searchCls.getAliasShortName();
|
||||||
|
return root.getClsp().isClsKnown(currentPkg + '.' + shortName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertRenameInfo(ICodeWriter code, ClassNode cls) {
|
private void insertRenameInfo(ICodeWriter code, ClassNode cls) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package jadx.core.codegen;
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
@@ -23,7 +23,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
|||||||
public class ConditionGen extends InsnGen {
|
public class ConditionGen extends InsnGen {
|
||||||
|
|
||||||
private static class CondStack {
|
private static class CondStack {
|
||||||
private final Queue<IfCondition> stack = new LinkedList<>();
|
private final Queue<IfCondition> stack = new ArrayDeque<>();
|
||||||
|
|
||||||
public Queue<IfCondition> getStack() {
|
public Queue<IfCondition> getStack() {
|
||||||
return stack;
|
return stack;
|
||||||
|
|||||||
@@ -11,12 +11,13 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.data.annotations.InsnCodeOffset;
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
import jadx.api.data.annotations.VarDeclareRef;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
import jadx.api.data.annotations.VarRef;
|
|
||||||
import jadx.api.plugins.input.data.MethodHandleType;
|
import jadx.api.plugins.input.data.MethodHandleType;
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.FieldInitInsnAttr;
|
||||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
@@ -37,6 +38,7 @@ import jadx.core.dex.instructions.IfNode;
|
|||||||
import jadx.core.dex.instructions.IndexInsnNode;
|
import jadx.core.dex.instructions.IndexInsnNode;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
import jadx.core.dex.instructions.InvokeCustomNode;
|
import jadx.core.dex.instructions.InvokeCustomNode;
|
||||||
|
import jadx.core.dex.instructions.InvokeCustomRawNode;
|
||||||
import jadx.core.dex.instructions.InvokeNode;
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
import jadx.core.dex.instructions.InvokeType;
|
import jadx.core.dex.instructions.InvokeType;
|
||||||
import jadx.core.dex.instructions.NewArrayNode;
|
import jadx.core.dex.instructions.NewArrayNode;
|
||||||
@@ -109,7 +111,7 @@ public class InsnGen {
|
|||||||
if (arg.isRegister()) {
|
if (arg.isRegister()) {
|
||||||
RegisterArg reg = (RegisterArg) arg;
|
RegisterArg reg = (RegisterArg) arg;
|
||||||
if (code.isMetadataSupported()) {
|
if (code.isMetadataSupported()) {
|
||||||
code.attachAnnotation(VarRef.get(mth, reg));
|
code.attachAnnotation(VarNode.getRef(mth, reg));
|
||||||
}
|
}
|
||||||
code.add(mgen.getNameGen().useArg(reg));
|
code.add(mgen.getNameGen().useArg(reg));
|
||||||
} else if (arg.isLiteral()) {
|
} else if (arg.isLiteral()) {
|
||||||
@@ -162,10 +164,18 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
useType(code, codeVar.getType());
|
useType(code, codeVar.getType());
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
|
defVar(code, codeVar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable definition without type, only var name
|
||||||
|
*/
|
||||||
|
private void defVar(ICodeWriter code, CodeVar codeVar) {
|
||||||
|
String varName = mgen.getNameGen().assignArg(codeVar);
|
||||||
if (code.isMetadataSupported()) {
|
if (code.isMetadataSupported()) {
|
||||||
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
|
code.attachDefinition(VarNode.get(mth, codeVar));
|
||||||
}
|
}
|
||||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
code.add(varName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String lit(LiteralArg arg) {
|
private String lit(LiteralArg arg) {
|
||||||
@@ -201,7 +211,31 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void staticField(ICodeWriter code, FieldInfo field) throws CodegenException {
|
||||||
|
FieldNode fieldNode = root.resolveField(field);
|
||||||
|
if (fieldNode != null
|
||||||
|
&& fieldNode.contains(AFlag.INLINE_INSTANCE_FIELD)
|
||||||
|
&& fieldNode.getParentClass().contains(AType.ANONYMOUS_CLASS)) {
|
||||||
|
FieldInitInsnAttr initInsnAttr = fieldNode.get(AType.FIELD_INIT_INSN);
|
||||||
|
if (initInsnAttr != null) {
|
||||||
|
InsnNode insn = initInsnAttr.getInsn();
|
||||||
|
if (insn instanceof ConstructorInsn) {
|
||||||
|
fieldNode.add(AFlag.DONT_GENERATE);
|
||||||
|
inlineAnonymousConstructor(code, fieldNode.getParentClass(), (ConstructorInsn) insn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
makeStaticFieldAccess(code, field, fieldNode, mgen.getClassGen());
|
||||||
|
}
|
||||||
|
|
||||||
public static void makeStaticFieldAccess(ICodeWriter code, FieldInfo field, ClassGen clsGen) {
|
public static void makeStaticFieldAccess(ICodeWriter code, FieldInfo field, ClassGen clsGen) {
|
||||||
|
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
|
||||||
|
makeStaticFieldAccess(code, field, fieldNode, clsGen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void makeStaticFieldAccess(ICodeWriter code,
|
||||||
|
FieldInfo field, @Nullable FieldNode fieldNode, ClassGen clsGen) {
|
||||||
ClassInfo declClass = field.getDeclClass();
|
ClassInfo declClass = field.getDeclClass();
|
||||||
// TODO
|
// TODO
|
||||||
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
||||||
@@ -212,7 +246,6 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
code.add('.');
|
code.add('.');
|
||||||
}
|
}
|
||||||
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
|
|
||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
code.attachAnnotation(fieldNode);
|
code.attachAnnotation(fieldNode);
|
||||||
}
|
}
|
||||||
@@ -223,10 +256,6 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void staticField(ICodeWriter code, FieldInfo field) {
|
|
||||||
makeStaticFieldAccess(code, field, mgen.getClassGen());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void useClass(ICodeWriter code, ArgType type) {
|
public void useClass(ICodeWriter code, ArgType type) {
|
||||||
mgen.getClassGen().useClass(code, type);
|
mgen.getClassGen().useClass(code, type);
|
||||||
}
|
}
|
||||||
@@ -686,9 +715,7 @@ public class InsnGen {
|
|||||||
private void makeConstructor(ConstructorInsn insn, ICodeWriter code) throws CodegenException {
|
private void makeConstructor(ConstructorInsn insn, ICodeWriter code) throws CodegenException {
|
||||||
ClassNode cls = mth.root().resolveClass(insn.getClassType());
|
ClassNode cls = mth.root().resolveClass(insn.getClassType());
|
||||||
if (cls != null && cls.isAnonymous() && !fallback) {
|
if (cls != null && cls.isAnonymous() && !fallback) {
|
||||||
cls.ensureProcessed();
|
|
||||||
inlineAnonymousConstructor(code, cls, insn);
|
inlineAnonymousConstructor(code, cls, insn);
|
||||||
mth.getParentClass().addInlinedClass(cls);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (insn.isSelf()) {
|
if (insn.isSelf()) {
|
||||||
@@ -739,6 +766,7 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||||
|
cls.ensureProcessed();
|
||||||
if (this.mth.getParentClass() == cls) {
|
if (this.mth.getParentClass() == cls) {
|
||||||
cls.remove(AType.ANONYMOUS_CLASS);
|
cls.remove(AType.ANONYMOUS_CLASS);
|
||||||
cls.remove(AFlag.DONT_GENERATE);
|
cls.remove(AFlag.DONT_GENERATE);
|
||||||
@@ -754,6 +782,7 @@ public class InsnGen {
|
|||||||
ctor.add(AFlag.DONT_GENERATE);
|
ctor.add(AFlag.DONT_GENERATE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
code.attachDefinition(cls);
|
||||||
code.add("new ");
|
code.add("new ");
|
||||||
useClass(code, parent);
|
useClass(code, parent);
|
||||||
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
||||||
@@ -776,6 +805,8 @@ public class InsnGen {
|
|||||||
ClassGen classGen = new ClassGen(cls, mgen.getClassGen().getParentGen());
|
ClassGen classGen = new ClassGen(cls, mgen.getClassGen().getParentGen());
|
||||||
classGen.setOuterNameGen(mgen.getNameGen());
|
classGen.setOuterNameGen(mgen.getNameGen());
|
||||||
classGen.addClassBody(code, true);
|
classGen.addClassBody(code, true);
|
||||||
|
|
||||||
|
mth.getParentClass().addInlinedClass(cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeInvoke(InvokeNode insn, ICodeWriter code) throws CodegenException {
|
private void makeInvoke(InvokeNode insn, ICodeWriter code) throws CodegenException {
|
||||||
@@ -787,11 +818,23 @@ public class InsnGen {
|
|||||||
MethodInfo callMth = insn.getCallMth();
|
MethodInfo callMth = insn.getCallMth();
|
||||||
MethodNode callMthNode = mth.root().resolveMethod(callMth);
|
MethodNode callMthNode = mth.root().resolveMethod(callMth);
|
||||||
|
|
||||||
|
if (type == InvokeType.CUSTOM_RAW) {
|
||||||
|
makeInvokeCustomRaw((InvokeCustomRawNode) insn, callMthNode, code);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (insn.isPolymorphicCall()) {
|
||||||
|
// add missing cast
|
||||||
|
code.add('(');
|
||||||
|
useType(code, callMth.getReturnType());
|
||||||
|
code.add(") ");
|
||||||
|
}
|
||||||
|
|
||||||
int k = 0;
|
int k = 0;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case DIRECT:
|
case DIRECT:
|
||||||
case VIRTUAL:
|
case VIRTUAL:
|
||||||
case INTERFACE:
|
case INTERFACE:
|
||||||
|
case POLYMORPHIC:
|
||||||
InsnArg arg = insn.getArg(0);
|
InsnArg arg = insn.getArg(0);
|
||||||
if (needInvokeArg(arg)) {
|
if (needInvokeArg(arg)) {
|
||||||
addArgDot(code, arg);
|
addArgDot(code, arg);
|
||||||
@@ -800,14 +843,9 @@ public class InsnGen {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SUPER:
|
case SUPER:
|
||||||
ClassInfo superCallCls = getClassForSuperCall(code, callMth);
|
callSuper(code, callMth);
|
||||||
if (superCallCls != null) {
|
k++; // use 'super' instead 'this' in 0 arg
|
||||||
useClass(code, superCallCls);
|
code.add('.');
|
||||||
code.add('.');
|
|
||||||
}
|
|
||||||
// use 'super' instead 'this' in 0 arg
|
|
||||||
code.add("super").add('.');
|
|
||||||
k++;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STATIC:
|
case STATIC:
|
||||||
@@ -821,13 +859,44 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
if (callMthNode != null) {
|
if (callMthNode != null) {
|
||||||
code.attachAnnotation(callMthNode);
|
code.attachAnnotation(callMthNode);
|
||||||
code.add(callMthNode.getAlias());
|
}
|
||||||
|
if (insn.contains(AFlag.FORCE_RAW_NAME)) {
|
||||||
|
code.add(callMth.getName());
|
||||||
} else {
|
} else {
|
||||||
code.add(callMth.getAlias());
|
if (callMthNode != null) {
|
||||||
|
code.add(callMthNode.getAlias());
|
||||||
|
} else {
|
||||||
|
code.add(callMth.getAlias());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
generateMethodArguments(code, insn, k, callMthNode);
|
generateMethodArguments(code, insn, k, callMthNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void makeInvokeCustomRaw(InvokeCustomRawNode insn,
|
||||||
|
@Nullable MethodNode callMthNode, ICodeWriter code) throws CodegenException {
|
||||||
|
if (isFallback()) {
|
||||||
|
code.add("call_site(");
|
||||||
|
code.incIndent();
|
||||||
|
for (EncodedValue value : insn.getCallSiteValues()) {
|
||||||
|
code.startLine(value.toString());
|
||||||
|
}
|
||||||
|
code.decIndent();
|
||||||
|
code.startLine(").invoke");
|
||||||
|
generateMethodArguments(code, insn, 0, callMthNode);
|
||||||
|
} else {
|
||||||
|
ArgType returnType = insn.getCallMth().getReturnType();
|
||||||
|
if (!returnType.isVoid()) {
|
||||||
|
code.add('(');
|
||||||
|
useType(code, returnType);
|
||||||
|
code.add(") ");
|
||||||
|
}
|
||||||
|
makeInvoke(insn.getResolveInvoke(), code);
|
||||||
|
code.add(".dynamicInvoker().invoke");
|
||||||
|
generateMethodArguments(code, insn, 0, callMthNode);
|
||||||
|
code.add(" /* invoke-custom */");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: add 'this' for equals methods in scope
|
// FIXME: add 'this' for equals methods in scope
|
||||||
private boolean needInvokeArg(InsnArg arg) {
|
private boolean needInvokeArg(InsnArg arg) {
|
||||||
if (arg.isAnyThis()) {
|
if (arg.isAnyThis()) {
|
||||||
@@ -939,15 +1008,17 @@ public class InsnGen {
|
|||||||
code.add(", ");
|
code.add(", ");
|
||||||
}
|
}
|
||||||
CodeVar argCodeVar = callArgs.get(i).getSVar().getCodeVar();
|
CodeVar argCodeVar = callArgs.get(i).getSVar().getCodeVar();
|
||||||
code.add(nameGen.assignArg(argCodeVar));
|
defVar(code, argCodeVar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// force set external arg names into call method args
|
// force set external arg names into call method args
|
||||||
int extArgsCount = customNode.getArgsCount();
|
int extArgsCount = customNode.getArgsCount();
|
||||||
int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg
|
int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg
|
||||||
|
int callArg = 0;
|
||||||
for (int i = startArg; i < extArgsCount; i++) {
|
for (int i = startArg; i < extArgsCount; i++) {
|
||||||
RegisterArg extArg = (RegisterArg) customNode.getArg(i);
|
RegisterArg extArg = (RegisterArg) customNode.getArg(i);
|
||||||
callArgs.get(i).setName(extArg.getName());
|
RegisterArg callRegArg = callArgs.get(callArg++);
|
||||||
|
callRegArg.getSVar().setCodeVar(extArg.getSVar().getCodeVar());
|
||||||
}
|
}
|
||||||
code.add(" -> {");
|
code.add(" -> {");
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
@@ -957,34 +1028,43 @@ public class InsnGen {
|
|||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
private void callSuper(ICodeWriter code, MethodInfo callMth) {
|
||||||
private ClassInfo getClassForSuperCall(ICodeWriter code, MethodInfo callMth) {
|
ClassInfo superCallCls = getClassForSuperCall(callMth);
|
||||||
ClassNode useCls = mth.getParentClass();
|
if (superCallCls == null) {
|
||||||
ClassInfo insnCls = useCls.getClassInfo();
|
// unknown class, add comment to keep that info
|
||||||
ClassInfo declClass = callMth.getDeclClass();
|
code.add("super/*").add(callMth.getDeclClass().getFullName()).add("*/");
|
||||||
if (insnCls.equals(declClass)) {
|
return;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
ClassNode topClass = useCls.getTopParentClass();
|
ClassInfo curClass = mth.getParentClass().getClassInfo();
|
||||||
if (topClass.getClassInfo().equals(declClass)) {
|
if (superCallCls.equals(curClass)) {
|
||||||
return declClass;
|
code.add("super");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// search call class
|
// use custom class
|
||||||
ClassNode nextParent = useCls;
|
useClass(code, superCallCls);
|
||||||
do {
|
code.add(".super");
|
||||||
ClassInfo nextClsInfo = nextParent.getClassInfo();
|
}
|
||||||
if (nextClsInfo.equals(declClass)
|
|
||||||
|| ArgType.isInstanceOf(mth.root(), nextClsInfo.getType(), declClass.getType())) {
|
|
||||||
if (nextParent == useCls) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return nextClsInfo;
|
|
||||||
}
|
|
||||||
nextParent = nextParent.getParentClass();
|
|
||||||
} while (nextParent != null && nextParent != topClass);
|
|
||||||
|
|
||||||
// search failed, just return parent class
|
/**
|
||||||
return useCls.getParentClass().getClassInfo();
|
* Search call class in super types of this
|
||||||
|
* and all parent classes (needed for inlined synthetic calls)
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private ClassInfo getClassForSuperCall(MethodInfo callMth) {
|
||||||
|
ArgType declClsType = callMth.getDeclClass().getType();
|
||||||
|
ClassNode parentNode = mth.getParentClass();
|
||||||
|
while (true) {
|
||||||
|
ClassInfo parentCls = parentNode.getClassInfo();
|
||||||
|
if (ArgType.isInstanceOf(root, parentCls.getType(), declClsType)) {
|
||||||
|
return parentCls;
|
||||||
|
}
|
||||||
|
ClassNode nextParent = parentNode.getParentClass();
|
||||||
|
if (nextParent == parentNode) {
|
||||||
|
// no parent, class not found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
parentNode = nextParent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void generateMethodArguments(ICodeWriter code, BaseInvokeNode insn, int startArgNum,
|
void generateMethodArguments(ICodeWriter code, BaseInvokeNode insn, int startArgNum,
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.data.annotations.InsnCodeOffset;
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
import jadx.api.data.annotations.VarDeclareRef;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
import jadx.api.plugins.input.data.AccessFlags;
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
@@ -26,6 +26,7 @@ import jadx.core.dex.attributes.AType;
|
|||||||
import jadx.core.dex.attributes.nodes.JadxError;
|
import jadx.core.dex.attributes.nodes.JadxError;
|
||||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.instructions.ConstStringNode;
|
import jadx.core.dex.instructions.ConstStringNode;
|
||||||
import jadx.core.dex.instructions.IfNode;
|
import jadx.core.dex.instructions.IfNode;
|
||||||
@@ -144,8 +145,9 @@ public class MethodGen {
|
|||||||
} else {
|
} else {
|
||||||
classGen.useType(code, mth.getReturnType());
|
classGen.useType(code, mth.getReturnType());
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
code.attachDefinition(mth);
|
MethodNode defMth = getMethodForDefinition();
|
||||||
code.add(mth.getAlias());
|
code.attachDefinition(defMth);
|
||||||
|
code.add(defMth.getAlias());
|
||||||
}
|
}
|
||||||
code.add('(');
|
code.add('(');
|
||||||
|
|
||||||
@@ -178,6 +180,14 @@ public class MethodGen {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MethodNode getMethodForDefinition() {
|
||||||
|
MethodReplaceAttr replaceAttr = mth.get(AType.METHOD_REPLACE);
|
||||||
|
if (replaceAttr != null) {
|
||||||
|
return replaceAttr.getReplaceMth();
|
||||||
|
}
|
||||||
|
return mth;
|
||||||
|
}
|
||||||
|
|
||||||
private void addOverrideAnnotation(ICodeWriter code, MethodNode mth) {
|
private void addOverrideAnnotation(ICodeWriter code, MethodNode mth) {
|
||||||
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||||
if (overrideAttr == null) {
|
if (overrideAttr == null) {
|
||||||
@@ -243,7 +253,7 @@ public class MethodGen {
|
|||||||
code.add(' ');
|
code.add(' ');
|
||||||
String varName = nameGen.assignArg(var);
|
String varName = nameGen.assignArg(var);
|
||||||
if (code.isMetadataSupported() && ssaVar != null /* for fallback mode */) {
|
if (code.isMetadataSupported() && ssaVar != null /* for fallback mode */) {
|
||||||
code.attachDefinition(VarDeclareRef.get(mth, var));
|
code.attachDefinition(VarNode.get(mth, var));
|
||||||
}
|
}
|
||||||
code.add(varName);
|
code.add(varName);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.util.Set;
|
|||||||
|
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.deobf.NameMapper;
|
import jadx.core.deobf.NameMapper;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
@@ -173,7 +174,7 @@ public class NameGen {
|
|||||||
|
|
||||||
private String makeNameForType(ArgType type) {
|
private String makeNameForType(ArgType type) {
|
||||||
if (type.isPrimitive()) {
|
if (type.isPrimitive()) {
|
||||||
return makeNameForPrimitive(type);
|
return type.getPrimitiveType().getShortName().toLowerCase();
|
||||||
}
|
}
|
||||||
if (type.isArray()) {
|
if (type.isArray()) {
|
||||||
return makeNameForType(type.getArrayRootElement()) + "Arr";
|
return makeNameForType(type.getArrayRootElement()) + "Arr";
|
||||||
@@ -181,10 +182,6 @@ public class NameGen {
|
|||||||
return makeNameForObject(type);
|
return makeNameForObject(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String makeNameForPrimitive(ArgType type) {
|
|
||||||
return type.getPrimitiveType().getShortName().toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String makeNameForObject(ArgType type) {
|
private String makeNameForObject(ArgType type) {
|
||||||
if (type.isGenericType()) {
|
if (type.isGenericType()) {
|
||||||
return StringUtils.escape(type.getObject().toLowerCase());
|
return StringUtils.escape(type.getObject().toLowerCase());
|
||||||
@@ -194,23 +191,32 @@ public class NameGen {
|
|||||||
if (alias != null) {
|
if (alias != null) {
|
||||||
return alias;
|
return alias;
|
||||||
}
|
}
|
||||||
ClassInfo extClsInfo = ClassInfo.fromType(mth.root(), type);
|
return makeNameForCheckedClass(ClassInfo.fromType(mth.root(), type));
|
||||||
String shortName = extClsInfo.getShortName();
|
|
||||||
String vName = fromName(shortName);
|
|
||||||
if (vName != null) {
|
|
||||||
return vName;
|
|
||||||
}
|
|
||||||
if (shortName != null) {
|
|
||||||
String lower = StringUtils.escape(shortName.toLowerCase());
|
|
||||||
if (shortName.equals(lower)) {
|
|
||||||
return lower + "Var";
|
|
||||||
}
|
|
||||||
return lower;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return StringUtils.escape(type.toString());
|
return StringUtils.escape(type.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String makeNameForCheckedClass(ClassInfo classInfo) {
|
||||||
|
String shortName = classInfo.getAliasShortName();
|
||||||
|
String vName = fromName(shortName);
|
||||||
|
if (vName != null) {
|
||||||
|
return vName;
|
||||||
|
}
|
||||||
|
String lower = StringUtils.escape(shortName.toLowerCase());
|
||||||
|
if (shortName.equals(lower)) {
|
||||||
|
return lower + "Var";
|
||||||
|
}
|
||||||
|
return lower;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeNameForClass(ClassInfo classInfo) {
|
||||||
|
String alias = getAliasForObject(classInfo.getFullName());
|
||||||
|
if (alias != null) {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
return makeNameForCheckedClass(classInfo);
|
||||||
|
}
|
||||||
|
|
||||||
private static String fromName(String name) {
|
private static String fromName(String name) {
|
||||||
if (name == null || name.isEmpty()) {
|
if (name == null || name.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
@@ -241,7 +247,12 @@ public class NameGen {
|
|||||||
|
|
||||||
case CONSTRUCTOR:
|
case CONSTRUCTOR:
|
||||||
ConstructorInsn co = (ConstructorInsn) insn;
|
ConstructorInsn co = (ConstructorInsn) insn;
|
||||||
return makeNameForObject(co.getClassType().getType());
|
MethodNode callMth = mth.root().getMethodUtils().resolveMethod(co);
|
||||||
|
if (callMth != null && callMth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||||
|
// don't use name of anonymous class
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return makeNameForClass(co.getClassType());
|
||||||
|
|
||||||
case ARRAY_LENGTH:
|
case ARRAY_LENGTH:
|
||||||
return "length";
|
return "length";
|
||||||
@@ -267,11 +278,11 @@ public class NameGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String makeNameFromInvoke(MethodInfo callMth) {
|
private String makeNameFromInvoke(MethodInfo callMth) {
|
||||||
String name = callMth.getName();
|
String name = callMth.getAlias();
|
||||||
ArgType declType = callMth.getDeclClass().getType();
|
ClassInfo declClass = callMth.getDeclClass();
|
||||||
if ("getInstance".equals(name)) {
|
if ("getInstance".equals(name)) {
|
||||||
// e.g. Cipher.getInstance
|
// e.g. Cipher.getInstance
|
||||||
return makeNameForType(declType);
|
return makeNameForClass(declClass);
|
||||||
}
|
}
|
||||||
if (name.startsWith("get") || name.startsWith("set")) {
|
if (name.startsWith("get") || name.startsWith("set")) {
|
||||||
return fromName(name.substring(3));
|
return fromName(name.substring(3));
|
||||||
@@ -280,9 +291,9 @@ public class NameGen {
|
|||||||
return "it";
|
return "it";
|
||||||
}
|
}
|
||||||
if ("toString".equals(name)) {
|
if ("toString".equals(name)) {
|
||||||
return makeNameForType(declType);
|
return makeNameForClass(declClass);
|
||||||
}
|
}
|
||||||
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
|
if ("forName".equals(name) && declClass.getType().equals(ArgType.CLASS)) {
|
||||||
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
||||||
}
|
}
|
||||||
if (name.startsWith("to")) {
|
if (name.startsWith("to")) {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.data.annotations.InsnCodeOffset;
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
import jadx.api.data.annotations.VarDeclareRef;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
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;
|
||||||
@@ -26,6 +26,7 @@ import jadx.core.dex.instructions.args.CodeVar;
|
|||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.NamedArg;
|
import jadx.core.dex.instructions.args.NamedArg;
|
||||||
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.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.IBlock;
|
import jadx.core.dex.nodes.IBlock;
|
||||||
@@ -269,7 +270,7 @@ public class RegionGen extends InsnGen {
|
|||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) {
|
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) throws CodegenException {
|
||||||
if (k instanceof FieldNode) {
|
if (k instanceof FieldNode) {
|
||||||
FieldNode fn = (FieldNode) k;
|
FieldNode fn = (FieldNode) k;
|
||||||
if (fn.getParentClass().isEnum()) {
|
if (fn.getParentClass().isEnum()) {
|
||||||
@@ -346,11 +347,11 @@ public class RegionGen extends InsnGen {
|
|||||||
if (arg == null) {
|
if (arg == null) {
|
||||||
code.add("unknown"); // throwing exception is too late at this point
|
code.add("unknown"); // throwing exception is too late at this point
|
||||||
} else if (arg instanceof RegisterArg) {
|
} else if (arg instanceof RegisterArg) {
|
||||||
CodeVar codeVar = ((RegisterArg) arg).getSVar().getCodeVar();
|
SSAVar ssaVar = ((RegisterArg) arg).getSVar();
|
||||||
if (code.isMetadataSupported()) {
|
if (code.isMetadataSupported()) {
|
||||||
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
|
code.attachDefinition(VarNode.get(mth, ssaVar));
|
||||||
}
|
}
|
||||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
code.add(mgen.getNameGen().assignArg(ssaVar.getCodeVar()));
|
||||||
} else if (arg instanceof NamedArg) {
|
} else if (arg instanceof NamedArg) {
|
||||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ public class SimpleModeHelper {
|
|||||||
startLabel.set(block.getId());
|
startLabel.set(block.getId());
|
||||||
} else if (predsCount == 1 && prev != null) {
|
} else if (predsCount == 1 && prev != null) {
|
||||||
if (!prev.equals(preds.get(0))) {
|
if (!prev.equals(preds.get(0))) {
|
||||||
startLabel.set(block.getId());
|
if (!block.contains(AFlag.EXC_BOTTOM_SPLITTER)) {
|
||||||
|
startLabel.set(block.getId());
|
||||||
|
}
|
||||||
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) {
|
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) {
|
||||||
endGoto.set(prev.getId());
|
endGoto.set(prev.getId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ import com.google.gson.FieldNamingPolicy;
|
|||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.data.annotations.InsnCodeOffset;
|
|
||||||
import jadx.api.impl.AnnotatedCodeWriter;
|
import jadx.api.impl.AnnotatedCodeWriter;
|
||||||
import jadx.api.impl.SimpleCodeWriter;
|
import jadx.api.impl.SimpleCodeWriter;
|
||||||
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
import jadx.core.codegen.ClassGen;
|
import jadx.core.codegen.ClassGen;
|
||||||
import jadx.core.codegen.MethodGen;
|
import jadx.core.codegen.MethodGen;
|
||||||
import jadx.core.codegen.json.cls.JsonClass;
|
import jadx.core.codegen.json.cls.JsonClass;
|
||||||
@@ -180,24 +180,27 @@ public class JsonCodeGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String[] lines = codeStr.split(ICodeWriter.NL);
|
String[] lines = codeStr.split(ICodeWriter.NL);
|
||||||
Map<Integer, Integer> lineMapping = code.getLineMapping();
|
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
|
||||||
Map<CodePosition, Object> annotations = code.getAnnotations();
|
ICodeMetadata metadata = code.getCodeMetadata();
|
||||||
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
|
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
|
||||||
|
|
||||||
int linesCount = lines.length;
|
int linesCount = lines.length;
|
||||||
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
|
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
|
||||||
|
int lineStartPos = 0;
|
||||||
|
int newLineLen = ICodeWriter.NL.length();
|
||||||
for (int i = 0; i < linesCount; i++) {
|
for (int i = 0; i < linesCount; i++) {
|
||||||
String codeLine = lines[i];
|
String codeLine = lines[i];
|
||||||
int line = i + 2;
|
int line = i + 2;
|
||||||
JsonCodeLine jsonCodeLine = new JsonCodeLine();
|
JsonCodeLine jsonCodeLine = new JsonCodeLine();
|
||||||
jsonCodeLine.setCode(codeLine);
|
jsonCodeLine.setCode(codeLine);
|
||||||
jsonCodeLine.setSourceLine(lineMapping.get(line));
|
jsonCodeLine.setSourceLine(lineMapping.get(line));
|
||||||
Object obj = annotations.get(new CodePosition(line));
|
Object obj = metadata.getAt(lineStartPos);
|
||||||
if (obj instanceof InsnCodeOffset) {
|
if (obj instanceof InsnCodeOffset) {
|
||||||
long offset = ((InsnCodeOffset) obj).getOffset();
|
long offset = ((InsnCodeOffset) obj).getOffset();
|
||||||
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
|
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
|
||||||
}
|
}
|
||||||
codeLines.add(jsonCodeLine);
|
codeLines.add(jsonCodeLine);
|
||||||
|
lineStartPos += codeLine.length() + newLineLen;
|
||||||
}
|
}
|
||||||
return codeLines;
|
return codeLines;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -428,7 +428,9 @@ public class Deobfuscator {
|
|||||||
return "Enum";
|
return "Enum";
|
||||||
}
|
}
|
||||||
String result = "";
|
String result = "";
|
||||||
if (cls.getAccessFlags().isAbstract()) {
|
if (cls.getAccessFlags().isInterface()) {
|
||||||
|
result += "Interface";
|
||||||
|
} else if (cls.getAccessFlags().isAbstract()) {
|
||||||
result += "Abstract";
|
result += "Abstract";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,13 @@ public enum AFlag {
|
|||||||
DONT_GENERATE, // process as usual, but don't output to generated code
|
DONT_GENERATE, // process as usual, but don't output to generated code
|
||||||
COMMENT_OUT, // process as usual, but comment insn in generated code
|
COMMENT_OUT, // process as usual, but comment insn in generated code
|
||||||
REMOVE, // can be completely removed
|
REMOVE, // can be completely removed
|
||||||
|
REMOVE_SUPER_CLASS, // don't add super class
|
||||||
|
|
||||||
HIDDEN, // instruction used inside other instruction but not listed in args
|
HIDDEN, // instruction used inside other instruction but not listed in args
|
||||||
|
|
||||||
DONT_RENAME, // do not rename during deobfuscation
|
DONT_RENAME, // do not rename during deobfuscation
|
||||||
|
FORCE_RAW_NAME, // force use of raw name instead alias
|
||||||
|
|
||||||
ADDED_TO_REGION,
|
ADDED_TO_REGION,
|
||||||
|
|
||||||
EXC_TOP_SPLITTER,
|
EXC_TOP_SPLITTER,
|
||||||
@@ -34,7 +37,9 @@ public enum AFlag {
|
|||||||
SKIP_FIRST_ARG,
|
SKIP_FIRST_ARG,
|
||||||
SKIP_ARG, // skip argument in invoke call
|
SKIP_ARG, // skip argument in invoke call
|
||||||
NO_SKIP_ARGS,
|
NO_SKIP_ARGS,
|
||||||
|
|
||||||
ANONYMOUS_CONSTRUCTOR,
|
ANONYMOUS_CONSTRUCTOR,
|
||||||
|
INLINE_INSTANCE_FIELD,
|
||||||
|
|
||||||
THIS,
|
THIS,
|
||||||
SUPER,
|
SUPER,
|
||||||
@@ -80,6 +85,7 @@ public enum AFlag {
|
|||||||
RERUN_SSA_TRANSFORM,
|
RERUN_SSA_TRANSFORM,
|
||||||
|
|
||||||
METHOD_CANDIDATE_FOR_INLINE,
|
METHOD_CANDIDATE_FOR_INLINE,
|
||||||
|
USE_LINES_HINTS, // source lines info in methods can be trusted
|
||||||
|
|
||||||
DISABLE_BLOCKS_LOCK,
|
DISABLE_BLOCKS_LOCK,
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,6 @@ public interface ILineAttributeNode {
|
|||||||
|
|
||||||
void setSourceLine(int sourceLine);
|
void setSourceLine(int sourceLine);
|
||||||
|
|
||||||
int getDecompiledLine();
|
|
||||||
|
|
||||||
void setDecompiledLine(int line);
|
|
||||||
|
|
||||||
int getDefPosition();
|
int getDefPosition();
|
||||||
|
|
||||||
void setDefPosition(int pos);
|
void setDefPosition(int pos);
|
||||||
|
|||||||
@@ -7,12 +7,19 @@ import jadx.core.dex.nodes.ClassNode;
|
|||||||
|
|
||||||
public class AnonymousClassAttr extends PinnedAttribute {
|
public class AnonymousClassAttr extends PinnedAttribute {
|
||||||
|
|
||||||
|
public enum InlineType {
|
||||||
|
CONSTRUCTOR,
|
||||||
|
INSTANCE_FIELD,
|
||||||
|
}
|
||||||
|
|
||||||
private final ClassNode outerCls;
|
private final ClassNode outerCls;
|
||||||
private final ArgType baseType;
|
private final ArgType baseType;
|
||||||
|
private final InlineType inlineType;
|
||||||
|
|
||||||
public AnonymousClassAttr(ClassNode outerCls, ArgType baseType) {
|
public AnonymousClassAttr(ClassNode outerCls, ArgType baseType, InlineType inlineType) {
|
||||||
this.outerCls = outerCls;
|
this.outerCls = outerCls;
|
||||||
this.baseType = baseType;
|
this.baseType = baseType;
|
||||||
|
this.inlineType = inlineType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClassNode getOuterCls() {
|
public ClassNode getOuterCls() {
|
||||||
@@ -23,6 +30,10 @@ public class AnonymousClassAttr extends PinnedAttribute {
|
|||||||
return baseType;
|
return baseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InlineType getInlineType() {
|
||||||
|
return inlineType;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AType<AnonymousClassAttr> getAttrType() {
|
public AType<AnonymousClassAttr> getAttrType() {
|
||||||
return AType.ANONYMOUS_CLASS;
|
return AType.ANONYMOUS_CLASS;
|
||||||
@@ -30,6 +41,6 @@ public class AnonymousClassAttr extends PinnedAttribute {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "AnonymousClass{" + outerCls + ", base: " + baseType + '}';
|
return "AnonymousClass{" + outerCls + ", base: " + baseType + ", inline type: " + inlineType + '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,21 +7,11 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
|
|||||||
|
|
||||||
private int sourceLine;
|
private int sourceLine;
|
||||||
|
|
||||||
private int decompiledLine;
|
/**
|
||||||
|
* Position where a node declared at in decompiled code
|
||||||
// the position exactly where a node declared at in decompiled java code.
|
*/
|
||||||
private int defPosition;
|
private int defPosition;
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDefPosition() {
|
|
||||||
return this.defPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setDefPosition(int defPosition) {
|
|
||||||
this.defPosition = defPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getSourceLine() {
|
public int getSourceLine() {
|
||||||
return sourceLine;
|
return sourceLine;
|
||||||
@@ -33,13 +23,13 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDecompiledLine() {
|
public int getDefPosition() {
|
||||||
return decompiledLine;
|
return this.defPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDecompiledLine(int decompiledLine) {
|
public void setDefPosition(int defPosition) {
|
||||||
this.decompiledLine = decompiledLine;
|
this.defPosition = defPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSourceLineFrom(LineAttrNode lineAttrNode) {
|
public void addSourceLineFrom(LineAttrNode lineAttrNode) {
|
||||||
@@ -50,6 +40,6 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
|
|||||||
|
|
||||||
public void copyLines(LineAttrNode lineAttrNode) {
|
public void copyLines(LineAttrNode lineAttrNode) {
|
||||||
setSourceLine(lineAttrNode.getSourceLine());
|
setSourceLine(lineAttrNode.getSourceLine());
|
||||||
setDecompiledLine(lineAttrNode.getDecompiledLine());
|
setDefPosition(lineAttrNode.getDefPosition());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,19 @@ public class LoopInfo {
|
|||||||
this.parentLoop = parentLoop;
|
this.parentLoop = parentLoop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasParent(LoopInfo searchLoop) {
|
||||||
|
LoopInfo parent = parentLoop;
|
||||||
|
while (true) {
|
||||||
|
if (parent == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (parent == searchLoop) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
parent = parent.getParentLoop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "LOOP:" + id + ": " + start + "->" + end;
|
return "LOOP:" + id + ": " + start + "->" + end;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package jadx.core.dex.attributes.nodes;
|
package jadx.core.dex.attributes.nodes;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
@@ -10,7 +10,7 @@ import jadx.core.dex.instructions.PhiInsn;
|
|||||||
|
|
||||||
public class PhiListAttr implements IJadxAttribute {
|
public class PhiListAttr implements IJadxAttribute {
|
||||||
|
|
||||||
private final List<PhiInsn> list = new LinkedList<>();
|
private final List<PhiInsn> list = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AType<PhiListAttr> getAttrType() {
|
public AType<PhiListAttr> getAttrType() {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package jadx.core.dex.info;
|
package jadx.core.dex.info;
|
||||||
|
|
||||||
|
import org.intellij.lang.annotations.MagicConstant;
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.AccessFlags;
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
@@ -20,10 +22,21 @@ public class AccessInfo {
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MagicConstant(valuesFromClass = AccessFlags.class)
|
||||||
public boolean containsFlag(int flag) {
|
public boolean containsFlag(int flag) {
|
||||||
return (accFlags & flag) != 0;
|
return (accFlags & flag) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MagicConstant(valuesFromClass = AccessFlags.class)
|
||||||
|
public boolean containsFlags(int... flags) {
|
||||||
|
for (int flag : flags) {
|
||||||
|
if ((accFlags & flag) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public AccessInfo remove(int flag) {
|
public AccessInfo remove(int flag) {
|
||||||
if (containsFlag(flag)) {
|
if (containsFlag(flag)) {
|
||||||
return new AccessInfo(accFlags & ~flag, type);
|
return new AccessInfo(accFlags & ~flag, type);
|
||||||
|
|||||||
@@ -180,12 +180,12 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
|||||||
return makeFullClsName(pkg, name, parentClass, false, true);
|
return makeFullClsName(pkg, name, parentClass, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String makeAliasFullName() {
|
public String makeAliasFullName() {
|
||||||
return makeFullClsName(getAliasPkg(), getAliasShortName(), parentClass, true, false);
|
return makeFullClsName(getAliasPkg(), getAliasShortName(), parentClass, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String makeAliasRawFullName() {
|
public String makeAliasRawFullName() {
|
||||||
return makeFullClsName(pkg, name, parentClass, true, true);
|
return makeFullClsName(getAliasPkg(), getAliasShortName(), parentClass, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAliasFullPath() {
|
public String getAliasFullPath() {
|
||||||
|
|||||||
@@ -82,16 +82,24 @@ public class ConstStorage {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (FieldNode f : staticFields) {
|
for (FieldNode f : staticFields) {
|
||||||
AccessInfo accFlags = f.getAccessFlags();
|
Object value = getFieldConstValue(f);
|
||||||
if (accFlags.isStatic() && accFlags.isFinal()) {
|
if (value != null) {
|
||||||
EncodedValue constVal = f.get(JadxAttrType.CONSTANT_VALUE);
|
addConstField(cls, f, value, f.getAccessFlags().isPublic());
|
||||||
if (constVal != null && constVal.getValue() != null) {
|
|
||||||
addConstField(cls, f, constVal.getValue(), accFlags.isPublic());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @Nullable Object getFieldConstValue(FieldNode fld) {
|
||||||
|
AccessInfo accFlags = fld.getAccessFlags();
|
||||||
|
if (accFlags.isStatic() && accFlags.isFinal()) {
|
||||||
|
EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE);
|
||||||
|
if (constVal != null) {
|
||||||
|
return constVal.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeForClass(ClassNode cls) {
|
public void removeForClass(ClassNode cls) {
|
||||||
classes.remove(cls);
|
classes.remove(cls);
|
||||||
globalValues.removeForCls(cls);
|
globalValues.removeForCls(cls);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jadx.core.dex.instructions;
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -7,6 +8,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.ICodeReader;
|
import jadx.api.plugins.input.data.ICodeReader;
|
||||||
|
import jadx.api.plugins.input.data.IMethodProto;
|
||||||
import jadx.api.plugins.input.data.IMethodRef;
|
import jadx.api.plugins.input.data.IMethodRef;
|
||||||
import jadx.api.plugins.input.insns.InsnData;
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
import jadx.api.plugins.input.insns.custom.IArrayPayload;
|
import jadx.api.plugins.input.insns.custom.IArrayPayload;
|
||||||
@@ -25,6 +27,7 @@ import jadx.core.dex.nodes.FieldNode;
|
|||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.DecodeException;
|
import jadx.core.utils.exceptions.DecodeException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.utils.input.InsnDataUtils;
|
import jadx.core.utils.input.InsnDataUtils;
|
||||||
@@ -440,6 +443,8 @@ public class InsnDecoder {
|
|||||||
return invokeCustom(insn, false);
|
return invokeCustom(insn, false);
|
||||||
case INVOKE_SPECIAL:
|
case INVOKE_SPECIAL:
|
||||||
return invokeSpecial(insn);
|
return invokeSpecial(insn);
|
||||||
|
case INVOKE_POLYMORPHIC:
|
||||||
|
return invokePolymorphic(insn, false);
|
||||||
|
|
||||||
case INVOKE_DIRECT_RANGE:
|
case INVOKE_DIRECT_RANGE:
|
||||||
return invoke(insn, InvokeType.DIRECT, true);
|
return invoke(insn, InvokeType.DIRECT, true);
|
||||||
@@ -451,6 +456,8 @@ public class InsnDecoder {
|
|||||||
return invoke(insn, InvokeType.VIRTUAL, true);
|
return invoke(insn, InvokeType.VIRTUAL, true);
|
||||||
case INVOKE_CUSTOM_RANGE:
|
case INVOKE_CUSTOM_RANGE:
|
||||||
return invokeCustom(insn, true);
|
return invokeCustom(insn, true);
|
||||||
|
case INVOKE_POLYMORPHIC_RANGE:
|
||||||
|
return invokePolymorphic(insn, true);
|
||||||
|
|
||||||
case NEW_INSTANCE:
|
case NEW_INSTANCE:
|
||||||
ArgType clsType = ArgType.parse(insn.getIndexAsType());
|
ArgType clsType = ArgType.parse(insn.getIndexAsType());
|
||||||
@@ -581,6 +588,22 @@ public class InsnDecoder {
|
|||||||
return InvokeCustomBuilder.build(method, insn, isRange);
|
return InvokeCustomBuilder.build(method, insn, isRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private InsnNode invokePolymorphic(InsnData insn, boolean isRange) {
|
||||||
|
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
|
||||||
|
if (mthRef == null) {
|
||||||
|
throw new JadxRuntimeException("Failed to load method reference for insn: " + insn);
|
||||||
|
}
|
||||||
|
MethodInfo callMth = MethodInfo.fromRef(root, mthRef);
|
||||||
|
IMethodProto proto = insn.getIndexAsProto(insn.getTarget());
|
||||||
|
|
||||||
|
// expand call args
|
||||||
|
List<ArgType> args = Utils.collectionMap(proto.getArgTypes(), ArgType::parse);
|
||||||
|
ArgType returnType = ArgType.parse(proto.getReturnType());
|
||||||
|
MethodInfo effectiveCallMth = MethodInfo.fromDetails(root, callMth.getDeclClass(),
|
||||||
|
callMth.getName(), args, returnType);
|
||||||
|
return new InvokePolymorphicNode(effectiveCallMth, insn, proto, callMth, isRange);
|
||||||
|
}
|
||||||
|
|
||||||
private InsnNode invokeSpecial(InsnData insn) {
|
private InsnNode invokeSpecial(InsnData insn) {
|
||||||
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
|
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
|
||||||
if (mthRef == null) {
|
if (mthRef == null) {
|
||||||
|
|||||||
@@ -5,10 +5,15 @@ import java.util.List;
|
|||||||
import jadx.api.plugins.input.data.ICallSite;
|
import jadx.api.plugins.input.data.ICallSite;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
import jadx.api.plugins.input.insns.InsnData;
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.JadxError;
|
||||||
import jadx.core.dex.instructions.invokedynamic.CustomLambdaCall;
|
import jadx.core.dex.instructions.invokedynamic.CustomLambdaCall;
|
||||||
|
import jadx.core.dex.instructions.invokedynamic.CustomRawCall;
|
||||||
import jadx.core.dex.instructions.invokedynamic.CustomStringConcat;
|
import jadx.core.dex.instructions.invokedynamic.CustomStringConcat;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.utils.input.InsnDataUtils;
|
import jadx.core.utils.input.InsnDataUtils;
|
||||||
|
|
||||||
@@ -28,8 +33,16 @@ public class InvokeCustomBuilder {
|
|||||||
if (CustomStringConcat.isStringConcat(values)) {
|
if (CustomStringConcat.isStringConcat(values)) {
|
||||||
return CustomStringConcat.buildStringConcat(insn, isRange, values);
|
return CustomStringConcat.buildStringConcat(insn, isRange, values);
|
||||||
}
|
}
|
||||||
// TODO: output raw dynamic call
|
try {
|
||||||
throw new JadxRuntimeException("Failed to process invoke-custom instruction: " + callSite);
|
return CustomRawCall.build(mth, insn, isRange, values);
|
||||||
|
} catch (Exception e) {
|
||||||
|
mth.addWarn("Failed to decode invoke-custom: \n" + Utils.listToString(values, "\n")
|
||||||
|
+ ",\n exception: " + Utils.getStackTrace(e));
|
||||||
|
InsnNode nop = new InsnNode(InsnType.NOP, 0);
|
||||||
|
nop.add(AFlag.SYNTHETIC);
|
||||||
|
nop.addAttr(AType.JADX_ERROR, new JadxError("Failed to decode invoke-custom: " + values, e));
|
||||||
|
return nop;
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new JadxRuntimeException("'invoke-custom' instruction processing error: " + e.getMessage(), e);
|
throw new JadxRuntimeException("'invoke-custom' instruction processing error: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.instructions.invokedynamic.CustomRawCall;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information for raw invoke-custom instruction.<br>
|
||||||
|
* Output will be formatted as polymorphic call with equivalent semantic
|
||||||
|
* Contains two parts:
|
||||||
|
* - resolve: treated as additional invoke insn (uses only constant args)
|
||||||
|
* - invoke: call of resolved method (base for this invoke)
|
||||||
|
* <br>
|
||||||
|
* See {@link CustomRawCall} class for build details
|
||||||
|
*/
|
||||||
|
public class InvokeCustomRawNode extends InvokeNode {
|
||||||
|
private final InvokeNode resolve;
|
||||||
|
private List<EncodedValue> callSiteValues;
|
||||||
|
|
||||||
|
public InvokeCustomRawNode(InvokeNode resolve, MethodInfo mthInfo, InsnData insn, boolean isRange) {
|
||||||
|
super(mthInfo, insn, InvokeType.CUSTOM_RAW, false, isRange);
|
||||||
|
this.resolve = resolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvokeCustomRawNode(InvokeNode resolve, MethodInfo mthInfo, InvokeType invokeType, int argsCount) {
|
||||||
|
super(mthInfo, invokeType, argsCount);
|
||||||
|
this.resolve = resolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvokeNode getResolveInvoke() {
|
||||||
|
return resolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCallSiteValues(List<EncodedValue> callSiteValues) {
|
||||||
|
this.callSiteValues = callSiteValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EncodedValue> getCallSiteValues() {
|
||||||
|
return callSiteValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InsnNode copy() {
|
||||||
|
InvokeCustomRawNode copy = new InvokeCustomRawNode(resolve, getCallMth(), getInvokeType(), getArgsCount());
|
||||||
|
copyCommonParams(copy);
|
||||||
|
copy.setCallSiteValues(callSiteValues);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStaticCall() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFirstArgOffset() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable InsnArg getInstanceArg() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(InsnNode obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj instanceof InvokeCustomRawNode) {
|
||||||
|
return super.isSame(obj) && resolve.isSame(((InvokeCustomRawNode) obj).resolve);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(InsnUtils.formatOffset(offset)).append(": INVOKE_CUSTOM ");
|
||||||
|
if (getResult() != null) {
|
||||||
|
sb.append(getResult()).append(" = ");
|
||||||
|
}
|
||||||
|
if (!appendArgs(sb)) {
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
sb.append(" call-site: \n ").append(Utils.listToString(callSiteValues, "\n ")).append('\n');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -67,6 +67,19 @@ public class InvokeNode extends BaseInvokeNode {
|
|||||||
return type == InvokeType.STATIC;
|
return type == InvokeType.STATIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPolymorphicCall() {
|
||||||
|
if (type == InvokeType.POLYMORPHIC) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// java bytecode uses virtual call with modified method info
|
||||||
|
if (type == InvokeType.VIRTUAL
|
||||||
|
&& mth.getDeclClass().getFullName().equals("java.lang.invoke.MethodHandle")
|
||||||
|
&& (mth.getName().equals("invoke") || mth.getName().equals("invokeExact"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public int getFirstArgOffset() {
|
public int getFirstArgOffset() {
|
||||||
return type == InvokeType.STATIC ? 0 : 1;
|
return type == InvokeType.STATIC ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package jadx.core.dex.instructions;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.IMethodProto;
|
||||||
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.utils.InsnUtils;
|
||||||
|
|
||||||
|
public class InvokePolymorphicNode extends InvokeNode {
|
||||||
|
private final IMethodProto proto;
|
||||||
|
private final MethodInfo baseCallRef;
|
||||||
|
|
||||||
|
public InvokePolymorphicNode(MethodInfo callMth, InsnData insn, IMethodProto proto, MethodInfo baseRef, boolean isRange) {
|
||||||
|
super(callMth, insn, InvokeType.POLYMORPHIC, true, isRange);
|
||||||
|
this.proto = proto;
|
||||||
|
this.baseCallRef = baseRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvokePolymorphicNode(MethodInfo callMth, int argsCount, IMethodProto proto, MethodInfo baseRef) {
|
||||||
|
super(callMth, InvokeType.POLYMORPHIC, argsCount);
|
||||||
|
this.proto = proto;
|
||||||
|
this.baseCallRef = baseRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMethodProto getProto() {
|
||||||
|
return proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodInfo getBaseCallRef() {
|
||||||
|
return baseCallRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InsnNode copy() {
|
||||||
|
InvokePolymorphicNode copy = new InvokePolymorphicNode(getCallMth(), getArgsCount(), proto, baseCallRef);
|
||||||
|
copyCommonParams(copy);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(InsnNode obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof InvokePolymorphicNode) || !super.isSame(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InvokePolymorphicNode other = (InvokePolymorphicNode) obj;
|
||||||
|
return proto.equals(other.proto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(InsnUtils.formatOffset(offset)).append(": INVOKE_POLYMORPHIC ");
|
||||||
|
if (getResult() != null) {
|
||||||
|
sb.append(getResult()).append(" = ");
|
||||||
|
}
|
||||||
|
if (!appendArgs(sb)) {
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
sb.append(" base: ").append(baseCallRef).append('\n');
|
||||||
|
sb.append(" proto: ").append(proto).append('\n');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,4 +8,5 @@ public enum InvokeType {
|
|||||||
SUPER,
|
SUPER,
|
||||||
POLYMORPHIC,
|
POLYMORPHIC,
|
||||||
CUSTOM,
|
CUSTOM,
|
||||||
|
CUSTOM_RAW,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import org.jetbrains.annotations.TestOnly;
|
|||||||
|
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
@@ -874,28 +874,37 @@ public abstract class ArgType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ArgType tryToResolveClassAlias(RootNode root, ArgType type) {
|
public static ArgType tryToResolveClassAlias(RootNode root, ArgType type) {
|
||||||
if (!type.isObject() || type.isGenericType()) {
|
if (type.isGenericType()) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
if (type.isArray()) {
|
||||||
|
ArgType rootType = type.getArrayRootElement();
|
||||||
|
ArgType aliasType = tryToResolveClassAlias(root, rootType);
|
||||||
|
if (aliasType == rootType) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
return ArgType.array(aliasType, type.getArrayDimension());
|
||||||
|
}
|
||||||
|
if (type.isObject()) {
|
||||||
|
ArgType wildcardType = type.getWildcardType();
|
||||||
|
if (wildcardType != null) {
|
||||||
|
return new WildcardType(tryToResolveClassAlias(root, wildcardType), type.getWildcardBound());
|
||||||
|
}
|
||||||
|
ClassInfo clsInfo = ClassInfo.fromName(root, type.getObject());
|
||||||
|
ArgType baseType = clsInfo.hasAlias() ? ArgType.object(clsInfo.getAliasFullName()) : type;
|
||||||
|
if (!type.isGeneric()) {
|
||||||
|
return baseType;
|
||||||
|
}
|
||||||
|
List<ArgType> genericTypes = type.getGenericTypes();
|
||||||
|
if (genericTypes != null) {
|
||||||
|
return new GenericObject(baseType.getObject(), tryToResolveClassAlias(root, genericTypes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
ClassNode cls = root.resolveClass(type);
|
public static List<ArgType> tryToResolveClassAlias(RootNode root, List<ArgType> types) {
|
||||||
if (cls == null) {
|
return ListUtils.map(types, t -> tryToResolveClassAlias(root, t));
|
||||||
return type;
|
|
||||||
}
|
|
||||||
ClassInfo clsInfo = cls.getClassInfo();
|
|
||||||
if (!clsInfo.hasAlias()) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
String aliasFullName = clsInfo.getAliasFullName();
|
|
||||||
if (type.isGeneric()) {
|
|
||||||
if (type instanceof GenericObject) {
|
|
||||||
return new GenericObject(aliasFullName, type.getGenericTypes());
|
|
||||||
}
|
|
||||||
if (type instanceof WildcardType) {
|
|
||||||
return new WildcardType(ArgType.object(aliasFullName), type.getWildcardBound());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ArgType.object(aliasFullName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.api.data.annotations.VarRef;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
|
|
||||||
public class CodeVar {
|
public class CodeVar {
|
||||||
private String name;
|
private String name;
|
||||||
@@ -15,7 +15,7 @@ public class CodeVar {
|
|||||||
private boolean isThis;
|
private boolean isThis;
|
||||||
private boolean isDeclared;
|
private boolean isDeclared;
|
||||||
|
|
||||||
private VarRef cachedVarRef; // set and used at codegen stage
|
private VarNode cachedVarNode; // set and used at codegen stage
|
||||||
|
|
||||||
public static CodeVar fromMthArg(RegisterArg mthArg, boolean linkRegister) {
|
public static CodeVar fromMthArg(RegisterArg mthArg, boolean linkRegister) {
|
||||||
CodeVar var = new CodeVar();
|
CodeVar var = new CodeVar();
|
||||||
@@ -94,12 +94,12 @@ public class CodeVar {
|
|||||||
isDeclared = declared;
|
isDeclared = declared;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VarRef getCachedVarRef() {
|
public VarNode getCachedVarNode() {
|
||||||
return cachedVarRef;
|
return cachedVarNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCachedVarRef(VarRef cachedVarRef) {
|
public void setCachedVarNode(VarNode varNode) {
|
||||||
this.cachedVarRef = cachedVarRef;
|
this.cachedVarNode = varNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -264,6 +264,13 @@ public abstract class InsnArg extends Typed {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSameVar(RegisterArg arg) {
|
||||||
|
if (isRegister()) {
|
||||||
|
return ((RegisterArg) this).sameRegAndSVar(arg);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected final <T extends InsnArg> T copyCommonParams(T copy) {
|
protected final <T extends InsnArg> T copyCommonParams(T copy) {
|
||||||
copy.copyAttributesFrom(this);
|
copy.copyAttributesFrom(this);
|
||||||
copy.setParentInsn(parentInsn);
|
copy.setParentInsn(parentInsn);
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
package jadx.core.dex.instructions.args;
|
package jadx.core.dex.instructions.args;
|
||||||
|
|
||||||
public enum PrimitiveType {
|
public enum PrimitiveType {
|
||||||
BOOLEAN("Z", "boolean"),
|
BOOLEAN("Z", "boolean", ArgType.object("java.lang.Boolean")),
|
||||||
CHAR("C", "char"),
|
CHAR("C", "char", ArgType.object("java.lang.Character")),
|
||||||
BYTE("B", "byte"),
|
BYTE("B", "byte", ArgType.object("java.lang.Byte")),
|
||||||
SHORT("S", "short"),
|
SHORT("S", "short", ArgType.object("java.lang.Short")),
|
||||||
INT("I", "int"),
|
INT("I", "int", ArgType.object("java.lang.Integer")),
|
||||||
FLOAT("F", "float"),
|
FLOAT("F", "float", ArgType.object("java.lang.Float")),
|
||||||
LONG("J", "long"),
|
LONG("J", "long", ArgType.object("java.lang.Long")),
|
||||||
DOUBLE("D", "double"),
|
DOUBLE("D", "double", ArgType.object("java.lang.Double")),
|
||||||
OBJECT("L", "OBJECT"),
|
OBJECT("L", "OBJECT", ArgType.OBJECT),
|
||||||
ARRAY("[", "ARRAY"),
|
ARRAY("[", "ARRAY", ArgType.OBJECT_ARRAY),
|
||||||
VOID("V", "void");
|
VOID("V", "void", ArgType.object("java.lang.Void"));
|
||||||
|
|
||||||
private final String shortName;
|
private final String shortName;
|
||||||
private final String longName;
|
private final String longName;
|
||||||
|
private final ArgType boxType;
|
||||||
|
|
||||||
PrimitiveType(String shortName, String longName) {
|
PrimitiveType(String shortName, String longName, ArgType boxType) {
|
||||||
this.shortName = shortName;
|
this.shortName = shortName;
|
||||||
this.longName = longName;
|
this.longName = longName;
|
||||||
|
this.boxType = boxType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getShortName() {
|
public String getShortName() {
|
||||||
@@ -29,6 +31,10 @@ public enum PrimitiveType {
|
|||||||
return longName;
|
return longName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ArgType getBoxType() {
|
||||||
|
return boxType;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return longName;
|
return longName;
|
||||||
|
|||||||
+9
-23
@@ -8,6 +8,7 @@ import jadx.api.plugins.input.data.IMethodHandle;
|
|||||||
import jadx.api.plugins.input.data.IMethodProto;
|
import jadx.api.plugins.input.data.IMethodProto;
|
||||||
import jadx.api.plugins.input.data.IMethodRef;
|
import jadx.api.plugins.input.data.IMethodRef;
|
||||||
import jadx.api.plugins.input.data.MethodHandleType;
|
import jadx.api.plugins.input.data.MethodHandleType;
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedType;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
import jadx.api.plugins.input.insns.InsnData;
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
@@ -34,18 +35,20 @@ public class CustomLambdaCall {
|
|||||||
if (values.size() < 6) {
|
if (values.size() < 6) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
IMethodHandle methodHandle = (IMethodHandle) values.get(0).getValue();
|
EncodedValue mthRef = values.get(0);
|
||||||
|
if (mthRef.getType() != EncodedType.ENCODED_METHOD_HANDLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
IMethodHandle methodHandle = (IMethodHandle) mthRef.getValue();
|
||||||
if (methodHandle.getType() != MethodHandleType.INVOKE_STATIC) {
|
if (methodHandle.getType() != MethodHandleType.INVOKE_STATIC) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
IMethodRef methodRef = methodHandle.getMethodRef();
|
IMethodRef methodRef = methodHandle.getMethodRef();
|
||||||
if (!methodRef.getName().equals("metafactory")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!methodRef.getParentClassType().equals("Ljava/lang/invoke/LambdaMetafactory;")) {
|
if (!methodRef.getParentClassType().equals("Ljava/lang/invoke/LambdaMetafactory;")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
String mthName = methodRef.getName();
|
||||||
|
return mthName.equals("metafactory") || mthName.equals("altMetafactory");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InvokeCustomNode buildLambdaMethodCall(MethodNode mth, InsnData insn, boolean isRange, List<EncodedValue> values) {
|
public static InvokeCustomNode buildLambdaMethodCall(MethodNode mth, InsnData insn, boolean isRange, List<EncodedValue> values) {
|
||||||
@@ -115,7 +118,7 @@ public class CustomLambdaCall {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private static InvokeNode buildInvokeNode(MethodHandleType methodHandleType, InvokeCustomNode invokeCustomNode,
|
private static InvokeNode buildInvokeNode(MethodHandleType methodHandleType, InvokeCustomNode invokeCustomNode,
|
||||||
MethodInfo callMthInfo) {
|
MethodInfo callMthInfo) {
|
||||||
InvokeType invokeType = convertInvokeType(methodHandleType);
|
InvokeType invokeType = InvokeCustomUtils.convertInvokeType(methodHandleType);
|
||||||
int callArgsCount = callMthInfo.getArgsCount();
|
int callArgsCount = callMthInfo.getArgsCount();
|
||||||
boolean instanceCall = invokeType != InvokeType.STATIC;
|
boolean instanceCall = invokeType != InvokeType.STATIC;
|
||||||
if (instanceCall) {
|
if (instanceCall) {
|
||||||
@@ -149,21 +152,4 @@ public class CustomLambdaCall {
|
|||||||
}
|
}
|
||||||
return invokeNode;
|
return invokeNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InvokeType convertInvokeType(MethodHandleType type) {
|
|
||||||
switch (type) {
|
|
||||||
case INVOKE_STATIC:
|
|
||||||
return InvokeType.STATIC;
|
|
||||||
case INVOKE_INSTANCE:
|
|
||||||
return InvokeType.VIRTUAL;
|
|
||||||
case INVOKE_DIRECT:
|
|
||||||
case INVOKE_CONSTRUCTOR:
|
|
||||||
return InvokeType.DIRECT;
|
|
||||||
case INVOKE_INTERFACE:
|
|
||||||
return InvokeType.INTERFACE;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new JadxRuntimeException("Unsupported method handle type: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package jadx.core.dex.instructions.invokedynamic;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.IMethodHandle;
|
||||||
|
import jadx.api.plugins.input.data.IMethodProto;
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
|
import jadx.api.plugins.input.insns.InsnData;
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.instructions.ConstStringNode;
|
||||||
|
import jadx.core.dex.instructions.InvokeCustomRawNode;
|
||||||
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
|
import jadx.core.dex.instructions.InvokeType;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import static jadx.core.utils.EncodedValueUtils.buildLookupArg;
|
||||||
|
import static jadx.core.utils.EncodedValueUtils.convertToInsnArg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show `invoke-custom` similar to polymorphic call
|
||||||
|
*/
|
||||||
|
public class CustomRawCall {
|
||||||
|
|
||||||
|
public static InsnNode build(MethodNode mth, InsnData insn, boolean isRange, List<EncodedValue> values) {
|
||||||
|
IMethodHandle resolveHandle = (IMethodHandle) values.get(0).getValue();
|
||||||
|
String invokeName = (String) values.get(1).getValue();
|
||||||
|
IMethodProto invokeProto = (IMethodProto) values.get(2).getValue();
|
||||||
|
List<InsnArg> resolveArgs = buildArgs(mth, values);
|
||||||
|
|
||||||
|
if (resolveHandle.getType().isField()) {
|
||||||
|
throw new JadxRuntimeException("Field handle not yet supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
RootNode root = mth.root();
|
||||||
|
MethodInfo resolveMth = MethodInfo.fromRef(root, resolveHandle.getMethodRef());
|
||||||
|
InvokeType resolveInvokeType = InvokeCustomUtils.convertInvokeType(resolveHandle.getType());
|
||||||
|
InvokeNode resolve = new InvokeNode(resolveMth, resolveInvokeType, resolveArgs.size());
|
||||||
|
resolveArgs.forEach(resolve::addArg);
|
||||||
|
|
||||||
|
ClassInfo invokeCls = ClassInfo.fromType(root, ArgType.OBJECT); // type will be known at runtime
|
||||||
|
MethodInfo invokeMth = MethodInfo.fromMethodProto(root, invokeCls, invokeName, invokeProto);
|
||||||
|
InvokeCustomRawNode customRawNode = new InvokeCustomRawNode(resolve, invokeMth, insn, isRange);
|
||||||
|
customRawNode.setCallSiteValues(values);
|
||||||
|
return customRawNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<InsnArg> buildArgs(MethodNode mth, List<EncodedValue> values) {
|
||||||
|
int valuesCount = values.size();
|
||||||
|
List<InsnArg> list = new ArrayList<>(valuesCount);
|
||||||
|
RootNode root = mth.root();
|
||||||
|
list.add(buildLookupArg(root)); // use `java.lang.invoke.MethodHandles.lookup()` as first arg
|
||||||
|
for (int i = 1; i < valuesCount; i++) {
|
||||||
|
EncodedValue value = values.get(i);
|
||||||
|
try {
|
||||||
|
list.add(convertToInsnArg(root, value));
|
||||||
|
} catch (Exception e) {
|
||||||
|
mth.addWarnComment("Failed to build arg in invoke-custom insn: " + value, e);
|
||||||
|
list.add(InsnArg.wrapArg(new ConstStringNode(value.toString())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package jadx.core.dex.instructions.invokedynamic;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.MethodHandleType;
|
||||||
|
import jadx.core.dex.instructions.InvokeType;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
public class InvokeCustomUtils {
|
||||||
|
|
||||||
|
public static InvokeType convertInvokeType(MethodHandleType type) {
|
||||||
|
switch (type) {
|
||||||
|
case INVOKE_STATIC:
|
||||||
|
return InvokeType.STATIC;
|
||||||
|
case INVOKE_INSTANCE:
|
||||||
|
return InvokeType.VIRTUAL;
|
||||||
|
case INVOKE_DIRECT:
|
||||||
|
case INVOKE_CONSTRUCTOR:
|
||||||
|
return InvokeType.DIRECT;
|
||||||
|
case INVOKE_INTERFACE:
|
||||||
|
return InvokeType.INTERFACE;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unsupported method handle type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,29 +19,59 @@ import static jadx.core.utils.Utils.lockList;
|
|||||||
|
|
||||||
public final class BlockNode extends AttrNode implements IBlock, Comparable<BlockNode> {
|
public final class BlockNode extends AttrNode implements IBlock, Comparable<BlockNode> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Const ID
|
||||||
|
*/
|
||||||
|
private final int cid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID linked to position in blocks list (easier to use BitSet)
|
||||||
|
* TODO: rename to avoid confusion
|
||||||
|
*/
|
||||||
private int id;
|
private int id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offset in methods bytecode
|
||||||
|
*/
|
||||||
private final int startOffset;
|
private final int startOffset;
|
||||||
|
|
||||||
private final List<InsnNode> instructions = new ArrayList<>(2);
|
private final List<InsnNode> instructions = new ArrayList<>(2);
|
||||||
|
|
||||||
private List<BlockNode> predecessors = new ArrayList<>(1);
|
private List<BlockNode> predecessors = new ArrayList<>(1);
|
||||||
private List<BlockNode> successors = new ArrayList<>(1);
|
private List<BlockNode> successors = new ArrayList<>(1);
|
||||||
private List<BlockNode> cleanSuccessors;
|
private List<BlockNode> cleanSuccessors;
|
||||||
|
|
||||||
// all dominators
|
/**
|
||||||
|
* All dominators, excluding self
|
||||||
|
*/
|
||||||
private BitSet doms = EmptyBitSet.EMPTY;
|
private BitSet doms = EmptyBitSet.EMPTY;
|
||||||
// dominance frontier
|
|
||||||
|
/**
|
||||||
|
* Dominance frontier
|
||||||
|
*/
|
||||||
private BitSet domFrontier;
|
private BitSet domFrontier;
|
||||||
// immediate dominator
|
|
||||||
|
/**
|
||||||
|
* Immediate dominator
|
||||||
|
*/
|
||||||
private BlockNode idom;
|
private BlockNode idom;
|
||||||
// blocks on which dominates this block
|
|
||||||
|
/**
|
||||||
|
* Blocks on which dominates this block
|
||||||
|
*/
|
||||||
private List<BlockNode> dominatesOn = new ArrayList<>(3);
|
private List<BlockNode> dominatesOn = new ArrayList<>(3);
|
||||||
|
|
||||||
public BlockNode(int id, int offset) {
|
public BlockNode(int cid, int id, int offset) {
|
||||||
|
this.cid = cid;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.startOffset = offset;
|
this.startOffset = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(int id) {
|
public int getCId() {
|
||||||
|
return cid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setId(int id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +200,10 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
|||||||
return contains(AFlag.RETURN);
|
return contains(AFlag.RETURN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return instructions.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return startOffset;
|
return startOffset;
|
||||||
@@ -184,12 +218,12 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BlockNode other = (BlockNode) obj;
|
BlockNode other = (BlockNode) obj;
|
||||||
return id == other.id && startOffset == other.startOffset;
|
return cid == other.cid && startOffset == other.startOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(@NotNull BlockNode o) {
|
public int compareTo(@NotNull BlockNode o) {
|
||||||
return Integer.compare(id, o.id);
|
return Integer.compare(cid, o.cid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -199,6 +233,6 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "B:" + id + ':' + InsnUtils.formatOffset(startOffset);
|
return "B:" + cid + ':' + InsnUtils.formatOffset(startOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.api.DecompilationMode;
|
import jadx.api.DecompilationMode;
|
||||||
import jadx.api.ICodeCache;
|
import jadx.api.ICodeCache;
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.JavaClass;
|
||||||
|
import jadx.api.impl.SimpleCodeInfo;
|
||||||
import jadx.api.plugins.input.data.IClassData;
|
import jadx.api.plugins.input.data.IClassData;
|
||||||
import jadx.api.plugins.input.data.IFieldData;
|
import jadx.api.plugins.input.data.IFieldData;
|
||||||
import jadx.api.plugins.input.data.IMethodData;
|
import jadx.api.plugins.input.data.IMethodData;
|
||||||
@@ -54,8 +54,6 @@ import static jadx.core.dex.nodes.ProcessState.LOADED;
|
|||||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||||
|
|
||||||
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
|
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
|
|
||||||
|
|
||||||
private final RootNode root;
|
private final RootNode root;
|
||||||
private final IClassData clsData;
|
private final IClassData clsData;
|
||||||
|
|
||||||
@@ -99,6 +97,8 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
// cache maps
|
// cache maps
|
||||||
private Map<MethodInfo, MethodNode> mthInfoMap = Collections.emptyMap();
|
private Map<MethodInfo, MethodNode> mthInfoMap = Collections.emptyMap();
|
||||||
|
|
||||||
|
private JavaClass javaNode;
|
||||||
|
|
||||||
public ClassNode(RootNode root, IClassData cls) {
|
public ClassNode(RootNode root, IClassData cls) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType()));
|
this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType()));
|
||||||
@@ -170,10 +170,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return ArgType.object(superType);
|
return ArgType.object(superType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateGenericClsData(ArgType superClass, List<ArgType> interfaces, List<ArgType> generics) {
|
public void updateGenericClsData(List<ArgType> generics, ArgType superClass, List<ArgType> interfaces) {
|
||||||
|
this.generics = generics;
|
||||||
this.superClass = superClass;
|
this.superClass = superClass;
|
||||||
this.interfaces = interfaces;
|
this.interfaces = interfaces;
|
||||||
this.generics = generics;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void processAttributes(ClassNode cls) {
|
private static void processAttributes(ClassNode cls) {
|
||||||
@@ -374,12 +374,20 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
String clsRawName = getRawName();
|
String clsRawName = getRawName();
|
||||||
if (searchInCache) {
|
if (searchInCache) {
|
||||||
ICodeInfo code = codeCache.get(clsRawName);
|
ICodeInfo code = codeCache.get(clsRawName);
|
||||||
if (code != null && code != ICodeInfo.EMPTY) {
|
if (code != ICodeInfo.EMPTY) {
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
|
ICodeInfo codeInfo;
|
||||||
codeCache.add(clsRawName, codeInfo);
|
try {
|
||||||
|
codeInfo = root.getProcessClasses().generateCode(this);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
addError("Code generation failed", e);
|
||||||
|
codeInfo = new SimpleCodeInfo(Utils.getStackTrace(e));
|
||||||
|
}
|
||||||
|
if (codeInfo != ICodeInfo.EMPTY) {
|
||||||
|
codeCache.add(clsRawName, codeInfo);
|
||||||
|
}
|
||||||
return codeInfo;
|
return codeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,6 +468,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addField(FieldNode fld) {
|
public void addField(FieldNode fld) {
|
||||||
|
if (fields == null || fields.isEmpty()) {
|
||||||
|
fields = new ArrayList<>(1);
|
||||||
|
}
|
||||||
fields.add(fld);
|
fields.add(fld);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -648,6 +659,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return contains(AType.ANONYMOUS_CLASS);
|
return contains(AType.ANONYMOUS_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSynthetic() {
|
||||||
|
return contains(AFlag.SYNTHETIC);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isInner() {
|
public boolean isInner() {
|
||||||
return parentClass != this;
|
return parentClass != this;
|
||||||
}
|
}
|
||||||
@@ -789,7 +804,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addCodegenDep(ClassNode dep) {
|
public void addCodegenDep(ClassNode dep) {
|
||||||
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
|
if (!codegenDeps.contains(dep)) {
|
||||||
|
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTotalDepsCount() {
|
public int getTotalDepsCount() {
|
||||||
@@ -817,6 +834,19 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return clsData == null ? "synthetic" : clsData.getInputFileName();
|
return clsData == null ? "synthetic" : clsData.getInputFileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JavaClass getJavaNode() {
|
||||||
|
return javaNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJavaNode(JavaClass javaNode) {
|
||||||
|
this.javaNode = javaNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return clsInfo.hashCode();
|
return clsInfo.hashCode();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package jadx.core.dex.nodes;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.api.JavaField;
|
||||||
import jadx.api.plugins.input.data.IFieldData;
|
import jadx.api.plugins.input.data.IFieldData;
|
||||||
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
@@ -21,6 +22,8 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
|||||||
|
|
||||||
private List<MethodNode> useIn = Collections.emptyList();
|
private List<MethodNode> useIn = Collections.emptyList();
|
||||||
|
|
||||||
|
private JavaField javaNode;
|
||||||
|
|
||||||
public static FieldNode build(ClassNode cls, IFieldData fieldData) {
|
public static FieldNode build(ClassNode cls, IFieldData fieldData) {
|
||||||
FieldInfo fieldInfo = FieldInfo.fromRef(cls.root(), fieldData);
|
FieldInfo fieldInfo = FieldInfo.fromRef(cls.root(), fieldData);
|
||||||
FieldNode fieldNode = new FieldNode(cls, fieldInfo, fieldData.getAccessFlags());
|
FieldNode fieldNode = new FieldNode(cls, fieldInfo, fieldData.getAccessFlags());
|
||||||
@@ -57,6 +60,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
|||||||
return accFlags.isStatic();
|
return accFlags.isStatic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInstance() {
|
||||||
|
return !accFlags.isStatic();
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return fieldInfo.getName();
|
return fieldInfo.getName();
|
||||||
}
|
}
|
||||||
@@ -65,6 +72,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
|||||||
return fieldInfo.getAlias();
|
return fieldInfo.getAlias();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void rename(String alias) {
|
||||||
|
fieldInfo.setAlias(alias);
|
||||||
|
}
|
||||||
|
|
||||||
public ArgType getType() {
|
public ArgType getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
@@ -73,6 +84,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
|||||||
return parentClass;
|
return parentClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ClassNode getTopParentClass() {
|
||||||
|
return parentClass.getTopParentClass();
|
||||||
|
}
|
||||||
|
|
||||||
public List<MethodNode> getUseIn() {
|
public List<MethodNode> getUseIn() {
|
||||||
return useIn;
|
return useIn;
|
||||||
}
|
}
|
||||||
@@ -100,6 +115,19 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
|||||||
return parentClass.root();
|
return parentClass.root();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JavaField getJavaNode() {
|
||||||
|
return javaNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJavaNode(JavaField javaNode) {
|
||||||
|
this.javaNode = javaNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.FIELD;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return fieldInfo.hashCode();
|
return fieldInfo.hashCode();
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package jadx.core.dex.nodes;
|
package jadx.core.dex.nodes;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
import jadx.core.dex.attributes.IAttributeNode;
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
|
|
||||||
public interface ICodeNode extends IDexNode, IAttributeNode, IUsageInfoNode {
|
public interface ICodeNode extends IDexNode, IAttributeNode, IUsageInfoNode, ICodeNodeRef {
|
||||||
AccessInfo getAccessFlags();
|
AccessInfo getAccessFlags();
|
||||||
|
|
||||||
void setAccessFlags(AccessInfo newAccessFlags);
|
void setAccessFlags(AccessInfo newAccessFlags);
|
||||||
|
|||||||
@@ -264,21 +264,6 @@ public class InsnNode extends LineAttrNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canReorderRecursive() {
|
|
||||||
if (!canReorder()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (InsnArg arg : this.getArguments()) {
|
|
||||||
if (arg.isInsnWrap()) {
|
|
||||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
|
||||||
if (!wrapInsn.canReorderRecursive()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean containsWrappedInsn() {
|
public boolean containsWrappedInsn() {
|
||||||
for (InsnArg arg : this.getArguments()) {
|
for (InsnArg arg : this.getArguments()) {
|
||||||
if (arg.isInsnWrap()) {
|
if (arg.isInsnWrap()) {
|
||||||
@@ -554,19 +539,25 @@ public class InsnNode extends LineAttrNode {
|
|||||||
return super.equals(obj);
|
return super.equals(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void appendArgs(StringBuilder sb) {
|
/**
|
||||||
|
* Append arguments type, wrap line if too long
|
||||||
|
*
|
||||||
|
* @return true if args wrapped
|
||||||
|
*/
|
||||||
|
protected boolean appendArgs(StringBuilder sb) {
|
||||||
if (arguments.isEmpty()) {
|
if (arguments.isEmpty()) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
String argsStr = Utils.listToString(arguments);
|
String argsStr = Utils.listToString(arguments);
|
||||||
if (argsStr.length() < 120) {
|
if (argsStr.length() < 120) {
|
||||||
sb.append(argsStr);
|
sb.append(argsStr);
|
||||||
} else {
|
return false;
|
||||||
// wrap args
|
|
||||||
String separator = ICodeWriter.NL + " ";
|
|
||||||
sb.append(separator).append(Utils.listToString(arguments, separator));
|
|
||||||
sb.append(ICodeWriter.NL);
|
|
||||||
}
|
}
|
||||||
|
// wrap args
|
||||||
|
String separator = ICodeWriter.NL + " ";
|
||||||
|
sb.append(separator).append(Utils.listToString(arguments, separator));
|
||||||
|
sb.append(ICodeWriter.NL);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.JavaMethod;
|
||||||
import jadx.api.plugins.input.data.ICodeReader;
|
import jadx.api.plugins.input.data.ICodeReader;
|
||||||
import jadx.api.plugins.input.data.IDebugInfo;
|
import jadx.api.plugins.input.data.IDebugInfo;
|
||||||
import jadx.api.plugins.input.data.IMethodData;
|
import jadx.api.plugins.input.data.IMethodData;
|
||||||
@@ -61,6 +62,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
private List<RegisterArg> argsList;
|
private List<RegisterArg> argsList;
|
||||||
private InsnNode[] instructions;
|
private InsnNode[] instructions;
|
||||||
private List<BlockNode> blocks;
|
private List<BlockNode> blocks;
|
||||||
|
private int blocksMaxCId;
|
||||||
private BlockNode enterBlock;
|
private BlockNode enterBlock;
|
||||||
private BlockNode exitBlock;
|
private BlockNode exitBlock;
|
||||||
private List<SSAVar> sVars;
|
private List<SSAVar> sVars;
|
||||||
@@ -70,6 +72,8 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
|
|
||||||
private List<MethodNode> useIn = Collections.emptyList();
|
private List<MethodNode> useIn = Collections.emptyList();
|
||||||
|
|
||||||
|
private JavaMethod javaNode;
|
||||||
|
|
||||||
public static MethodNode build(ClassNode classNode, IMethodData methodData) {
|
public static MethodNode build(ClassNode classNode, IMethodData methodData) {
|
||||||
MethodNode methodNode = new MethodNode(classNode, methodData);
|
MethodNode methodNode = new MethodNode(classNode, methodData);
|
||||||
methodNode.addAttrs(methodData.getAttributes());
|
methodNode.addAttrs(methodData.getAttributes());
|
||||||
@@ -316,6 +320,19 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBasicBlocks(List<BlockNode> blocks) {
|
||||||
|
this.blocks = blocks;
|
||||||
|
int i = 0;
|
||||||
|
for (BlockNode block : blocks) {
|
||||||
|
block.setId(i);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNextBlockCId() {
|
||||||
|
return blocksMaxCId++;
|
||||||
|
}
|
||||||
|
|
||||||
public BlockNode getEnterBlock() {
|
public BlockNode getEnterBlock() {
|
||||||
return enterBlock;
|
return enterBlock;
|
||||||
}
|
}
|
||||||
@@ -461,6 +478,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
return regsCount;
|
return regsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getArgsStartReg() {
|
||||||
|
return argsStartReg;
|
||||||
|
}
|
||||||
|
|
||||||
public SSAVar makeNewSVar(@NotNull RegisterArg assignArg) {
|
public SSAVar makeNewSVar(@NotNull RegisterArg assignArg) {
|
||||||
int regNum = assignArg.getRegNum();
|
int regNum = assignArg.getRegNum();
|
||||||
return makeNewSVar(regNum, getNextSVarVersion(regNum), assignArg);
|
return makeNewSVar(regNum, getNextSVarVersion(regNum), assignArg);
|
||||||
@@ -592,6 +613,19 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
this.useIn = useIn;
|
this.useIn = useIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JavaMethod getJavaNode() {
|
||||||
|
return javaNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJavaNode(JavaMethod javaNode) {
|
||||||
|
this.javaNode = javaNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.METHOD;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return mthInfo.hashCode();
|
return mthInfo.hashCode();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user