Compare commits
413 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 1ec127c3cb | |||
| 7a3b7c55c9 | |||
| b66293a2f7 | |||
| abcaafa89a | |||
| cf25cc4faa | |||
| b57001d4a7 | |||
| 83decc2473 | |||
| 92faa569be | |||
| c5b731169d | |||
| f0a8ef81d3 | |||
| 994973ac01 | |||
| c9622c0771 | |||
| 8551c6c903 | |||
| 9a9ac4308e | |||
| e784cbdd09 | |||
| 2744c4bfb6 | |||
| e4f4c1b84a | |||
| e5fa818b5c | |||
| b22b554a69 | |||
| e9b8060889 | |||
| 1c2b2c072c | |||
| 3d451912ee | |||
| fe91d774fa | |||
| d8306cb1c0 | |||
| 909cf0a576 | |||
| 8fe1ee11e4 | |||
| d2bef108f5 | |||
| ba8ba504b1 | |||
| 481b5abf85 | |||
| c4e1d9445a | |||
| cb03532b76 | |||
| c93e9eea14 | |||
| 9a67b19973 | |||
| 95c75bed1e | |||
| b008568a5c | |||
| 94fb91cec6 | |||
| c54dd77f35 | |||
| 17fbc99f29 | |||
| 21dd17290b | |||
| dc73fc92be | |||
| 592215db66 | |||
| fb318e3bd9 | |||
| 5f3c8816a3 | |||
| 6016b902c7 | |||
| 5852da1e3d | |||
| 502fd069be | |||
| fad9e7b827 | |||
| 35116d0b1a | |||
| 3b781e41ad | |||
| a3e9744364 | |||
| 7030daeccd | |||
| e7151ad7b2 | |||
| ed2a3c8458 | |||
| 779f75cd52 | |||
| 54683e3198 | |||
| 09335395f5 | |||
| 57e3dd8f15 | |||
| a9bbadd602 | |||
| 2c570681f7 | |||
| 25166970cc | |||
| d3a0a56b8b | |||
| 3c2c198a0e | |||
| 4d4d67f0b4 | |||
| 97e8a34906 | |||
| 82f3b57e83 | |||
| af2f14f807 | |||
| fe248d7098 | |||
| 1a2e702b25 | |||
| 1da20b8e7d | |||
| 01f74ff706 | |||
| 89e95eb9ee | |||
| a61ebaaa00 | |||
| 7a5a2fcd84 | |||
| 8d5554f1b5 | |||
| 873aabb471 | |||
| 4bed9dc358 | |||
| e229874195 | |||
| 473b6e31e9 | |||
| b5ce460618 | |||
| 3c05b05196 | |||
| bdb2efdb6b | |||
| a27ba3ff4b | |||
| 4684207b54 | |||
| dd1be3039b | |||
| 8b30b770cd | |||
| 47caa91e85 | |||
| d71f3e09df | |||
| 06c7415827 | |||
| bd3e62617e | |||
| 00b48473a0 | |||
| 84facb13d0 | |||
| 96f90e18e8 | |||
| 8ff18e63ee | |||
| 381405ea99 | |||
| ae5c00397a | |||
| bd4509f1a7 | |||
| b8c84886a8 | |||
| 45021389bc | |||
| f674a29a64 | |||
| 0c9e3227d0 | |||
| be7e1479a1 | |||
| 19827fca20 | |||
| 5eb7cc40ed | |||
| d22db30166 | |||
| 6db61e7a59 | |||
| 86582de521 | |||
| a7c63c2eb3 | |||
| 081a0e21ee | |||
| 9ac9c05265 | |||
| b7daf79b26 | |||
| b67a3561a4 | |||
| 52ac6dbbaf | |||
| 72381ad8f3 | |||
| 6a065c46f4 | |||
| 092d0d7e67 | |||
| 5ca7285558 | |||
| 7576f9cd5e | |||
| 46b5725d98 | |||
| 72542fa6f9 | |||
| a250d0461b | |||
| c7795bfc48 | |||
| 5de46b7e40 | |||
| 99c70872c1 | |||
| 3566669303 | |||
| 4557d05256 | |||
| fa421d165e | |||
| ecf20020d7 | |||
| ae85af61c7 | |||
| 659bbbf4fb | |||
| 427e2dddc4 | |||
| d47483f957 | |||
| 4bd8e26ae7 | |||
| 01f47282ed | |||
| afdd37cd97 | |||
| addaffcd1d | |||
| 63f7ce20a4 | |||
| f37c23db7a | |||
| d2bde0be21 | |||
| 9c446ebbd6 | |||
| 0f00fb9a27 | |||
| 2d6f819c86 | |||
| 56683ac409 | |||
| a72523c7df | |||
| 46eeb0bc22 | |||
| 6e8baef9b2 | |||
| 947b621733 | |||
| 4cc00bdaf2 | |||
| 59ef569a63 | |||
| abae225915 | |||
| 05bdf9daae | |||
| 0a8192168a | |||
| 88fd5a517e | |||
| 74c5b616a4 | |||
| 22a61d715b | |||
| a90ec7c64a | |||
| b22812b43a | |||
| 4c0da8c3d5 | |||
| 9aa30f77b7 | |||
| 2dbef83fa6 | |||
| 6ec7f789ef | |||
| 31c0afe29e | |||
| 46b07863c1 | |||
| 445e91e6b5 | |||
| 9daf386d66 | |||
| 49b4079cd8 | |||
| 0ffa1838a2 | |||
| 0efca29e95 | |||
| 0ab933efff | |||
| 4ee4a34323 | |||
| 985ccd6bba | |||
| 570e7528a7 | |||
| 918585968d | |||
| cf918a897f | |||
| 5fc27c1136 | |||
| 6bcc48c462 | |||
| 4dc368c7d0 | |||
| 17f99ed928 | |||
| 954d239b52 | |||
| ea167cbefc | |||
| 90a436236d | |||
| 4479a3fbd5 | |||
| f5216b77f8 | |||
| 39dc288978 | |||
| f37005958f | |||
| dfdc14ea86 | |||
| 82712776cc | |||
| 439446816c | |||
| 940108661c | |||
| 0423f33e93 | |||
| c2a4a7a6c2 | |||
| ff4e3dd976 | |||
| 94b00b4e7a | |||
| 48252c3c3d | |||
| 37adce2efb | |||
| 358cddd9a7 | |||
| 418df2fd93 | |||
| cd153c76f2 | |||
| f30c14b277 | |||
| 3a29812241 | |||
| 67e6b647a2 | |||
| ea0795c8a9 | |||
| 099acfca1d | |||
| 8969d11a22 | |||
| eedf32d197 | |||
| 9c8642593c | |||
| 8e89a2ef1b | |||
| 316c2fdd4d | |||
| 6bbf1d0996 | |||
| e854ba3c44 | |||
| f681c8963d | |||
| 8d5f22e43d | |||
| a62de839be | |||
| 5af60b2ff4 | |||
| c8d7fce938 | |||
| 90fbc790d9 | |||
| 1ce3fc972a | |||
| 9ea3f0f240 | |||
| 868fa90097 | |||
| 55bb20cf29 | |||
| 7c0671c81b | |||
| 12a66bd83e | |||
| 1efdcd7b10 | |||
| 2d9bcdb87a | |||
| ac9cace8f6 | |||
| f9bf27579e | |||
| 667cae2e62 | |||
| e8e0491cb5 | |||
| ee12f0bd18 | |||
| 5f24193c49 | |||
| dd29d37154 | |||
| b63e3aca00 | |||
| 859674ce7e | |||
| ea8b9ce462 | |||
| b5720bd14e | |||
| cc99409a7e | |||
| fef3e21c70 | |||
| f3d76c433a | |||
| 31d5715723 | |||
| 592eef3cda | |||
| 0541748e5f | |||
| cf1d9e8372 | |||
| b096d8869e | |||
| 2acc14b04a | |||
| 1f1efb0e17 | |||
| 1c08d854fb | |||
| 9c252fb226 | |||
| 4bda3b9e9b | |||
| 21da3c8602 | |||
| 7ec43776ae | |||
| 07d7e68dc2 | |||
| 8785c33d06 | |||
| 661ebe439d | |||
| 4732fa36a6 | |||
| 8dad158ae6 | |||
| bfc343d1ee | |||
| ca723c3b47 | |||
| b6657351fc | |||
| f26032ed7d | |||
| 012f7665aa | |||
| c28e8142f4 | |||
| 1462acbb92 | |||
| c52c659b94 | |||
| 6bf358fc66 | |||
| e8f57d3ace | |||
| 766e7193b9 | |||
| 6fe762aa7b | |||
| 7065b1b3ba | |||
| 72b812acad | |||
| d7ffa21fbe | |||
| c95d64909a | |||
| a5b2b04317 | |||
| 4705194a1d | |||
| 19572a674e | |||
| a1247f4d96 | |||
| 52412dfe31 | |||
| ab02e6e7c3 | |||
| 9ef99a2b92 | |||
| 4e5fac4b88 | |||
| 7a14aaa17e | |||
| 650863836c | |||
| 3a69ac23c0 | |||
| b873c6ae4d | |||
| 4835b1b897 | |||
| 67def6319e | |||
| c56d9ac7ce | |||
| e6588c4307 | |||
| 712389ab24 | |||
| 5f1be38490 | |||
| 7982592c6e | |||
| 69574918b5 | |||
| f6783e8f5e | |||
| 913b00a4d4 | |||
| 22fa132110 | |||
| 5a30fc0300 | |||
| c774ffc979 | |||
| c93e7fb9cd | |||
| 3437888964 | |||
| b314e0bdda | |||
| 2bdde6a528 | |||
| c61cb80a8b | |||
| 4217aab933 | |||
| ffb2956d90 | |||
| 9744547fab | |||
| b580d1cf5b | |||
| 855c7b608e | |||
| 707ed9a828 | |||
| a3ea514521 | |||
| 3dfaec5033 | |||
| 778106c41b | |||
| c47e9cdde4 | |||
| 8dd76420c8 | |||
| dfe026ac2d | |||
| f0849d0ed1 | |||
| b7ca898b77 | |||
| 1b8b377f90 | |||
| 7227f1ac78 | |||
| 23f088105e | |||
| 3bbb6b1058 | |||
| 3a4895b21c | |||
| 4e6afe9b64 | |||
| dd4c20249f | |||
| b54b2d47e9 | |||
| 64fb587d0f | |||
| 2ca3c65300 | |||
| 549f346d5e | |||
| 80a879bddf | |||
| 13c17a000a | |||
| 96dea75bc8 | |||
| 035fce6191 | |||
| 2f5dd171d0 | |||
| e7598d4340 | |||
| 76d0a39a0f | |||
| 3f25f072c6 | |||
| 5c75f249c7 | |||
| 02bfe63245 | |||
| faa205a486 | |||
| 3a6d645ea3 | |||
| e65468b97a | |||
| edbe6015f6 | |||
| f642e11a7a | |||
| fdc87fe296 | |||
| 7396c7595f | |||
| d39849ad00 | |||
| d65ee902f7 | |||
| eada4b0fc3 | |||
| 6f9619126a | |||
| 4bc6007a4d | |||
| d3f5154c19 | |||
| 71aa29cc71 | |||
| 98d8015015 | |||
| 42a44f210d | |||
| 29ff86b74f |
@@ -1,14 +0,0 @@
|
|||||||
coverage:
|
|
||||||
precision: 2
|
|
||||||
round: down
|
|
||||||
range: "50...100"
|
|
||||||
|
|
||||||
status:
|
|
||||||
project:
|
|
||||||
default: on
|
|
||||||
patch:
|
|
||||||
default: on
|
|
||||||
changes:
|
|
||||||
default: off
|
|
||||||
|
|
||||||
comment: false
|
|
||||||
@@ -8,7 +8,6 @@ assignees: ''
|
|||||||
---
|
---
|
||||||
|
|
||||||
**Checks before report**
|
**Checks before report**
|
||||||
- check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
|
|
||||||
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
|
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
|
||||||
- search existing issues by exception message
|
- search existing issues by exception message
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
# Set update schedule for GitHub Actions
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
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 # set latest java version by default
|
||||||
|
|
||||||
|
- 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
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [master]
|
||||||
|
schedule:
|
||||||
|
- cron: '0 9 * * 5'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: ['java']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
queries: +security-extended
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
|
# Don't build tests in jadx-core also skip tests execution and checkstyle tasks
|
||||||
|
- run: |
|
||||||
|
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
||||||
@@ -33,3 +33,7 @@ jadx-output/
|
|||||||
*.log
|
*.log
|
||||||
*.cfg
|
*.cfg
|
||||||
*.orig
|
*.orig
|
||||||
|
quark.json
|
||||||
|
|
||||||
|
cliff.toml
|
||||||
|
jadx-gui/src/main/resources/logback.xml
|
||||||
|
|||||||
+3
-3
@@ -11,14 +11,14 @@ stages:
|
|||||||
java-8:
|
java-8:
|
||||||
stage: test
|
stage: test
|
||||||
image: openjdk:8
|
image: openjdk:8
|
||||||
script: ./gradlew clean build dist
|
script: ./gradlew clean build dist copyExe --warning-mode=all
|
||||||
|
|
||||||
java-11:
|
java-11:
|
||||||
stage: test
|
stage: test
|
||||||
image: openjdk:11
|
image: openjdk:11
|
||||||
script: ./gradlew clean build dist
|
script: ./gradlew clean build dist copyExe --warning-mode=all
|
||||||
|
|
||||||
java-latest:
|
java-latest:
|
||||||
stage: test
|
stage: test
|
||||||
image: openjdk:latest
|
image: openjdk:latest
|
||||||
script: java -version && ./gradlew clean build dist
|
script: java -version && ./gradlew clean build dist --warning-mode=all
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
jdk:
|
||||||
|
- openjdk11
|
||||||
|
install:
|
||||||
|
- echo "Jitpack is not supported. Use artifacts from Maven Central (https://search.maven.org/search?q=jadx), check usage help at https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library"
|
||||||
|
- ./gradlew intentional-fail
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
branches:
|
|
||||||
- release
|
|
||||||
|
|
||||||
verifyConditions:
|
|
||||||
- '@semantic-release/github'
|
|
||||||
|
|
||||||
prepare:
|
|
||||||
- path: '@semantic-release/exec'
|
|
||||||
cmd: "JADX_VERSION=${nextRelease.version} ./gradlew clean dist"
|
|
||||||
|
|
||||||
preset: "angular"
|
|
||||||
|
|
||||||
generateNotes:
|
|
||||||
- path: '@semantic-release/release-notes-generator'
|
|
||||||
writerOpts:
|
|
||||||
groupBy: "type"
|
|
||||||
commitGroupsSort:
|
|
||||||
- "feat"
|
|
||||||
- "perf"
|
|
||||||
- "fix"
|
|
||||||
commitsSort: "header"
|
|
||||||
|
|
||||||
publish:
|
|
||||||
- path: '@semantic-release/exec'
|
|
||||||
cmd: "JADX_VERSION=${nextRelease.version} BINTRAY_PACKAGE=releases bash scripts/bintray-upload.sh"
|
|
||||||
- path: '@semantic-release/github'
|
|
||||||
assets:
|
|
||||||
- path: 'build/*.zip'
|
|
||||||
- path: 'build/*.exe'
|
|
||||||
|
|
||||||
success:
|
|
||||||
- path: '@semantic-release/github'
|
|
||||||
successComment: false
|
|
||||||
|
|
||||||
fail:
|
|
||||||
- path: '@semantic-release/github'
|
|
||||||
failComment: false
|
|
||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
language: java
|
|
||||||
os: linux
|
|
||||||
dist: trusty
|
|
||||||
|
|
||||||
# don't build on tag push
|
|
||||||
if: tag IS blank
|
|
||||||
|
|
||||||
git:
|
|
||||||
depth: false
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- chmod +x gradlew
|
|
||||||
|
|
||||||
# override install to skip 'gradle assemble'
|
|
||||||
install: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- TERM=dumb
|
|
||||||
- JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
|
||||||
- JADX_VERSION="${JADX_LAST_TAG:1}-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
|
|
||||||
|
|
||||||
jdk:
|
|
||||||
- openjdk8
|
|
||||||
- openjdk11
|
|
||||||
|
|
||||||
script: ./gradlew clean build
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- stage: checks
|
|
||||||
jdk: openjdk11
|
|
||||||
if: branch = master AND repo = env(MAIN_REPO) AND type = push
|
|
||||||
script: bash scripts/travis-checks.sh
|
|
||||||
|
|
||||||
- stage: deploy-unstable
|
|
||||||
jdk: openjdk8
|
|
||||||
if: branch = master AND repo = env(MAIN_REPO) AND type = push
|
|
||||||
script: bash scripts/travis-master.sh
|
|
||||||
|
|
||||||
- stage: deploy-release
|
|
||||||
language: node_js
|
|
||||||
jdk: openjdk8
|
|
||||||
node_js: 11
|
|
||||||
if: branch = release AND repo = env(MAIN_REPO) AND type = push
|
|
||||||
script: bash scripts/travis-release.sh
|
|
||||||
@@ -5,7 +5,6 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
|
|||||||
## Open Issue
|
## Open Issue
|
||||||
|
|
||||||
1. Before proceed please do:
|
1. Before proceed please do:
|
||||||
- check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
|
|
||||||
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
|
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
|
||||||
- search existing issues by exception message
|
- search existing issues by exception message
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,22 @@
|
|||||||
|
|
||||||
## JADX
|
## JADX
|
||||||
|
|
||||||
[](https://travis-ci.com/skylot/jadx)
|
[](https://github.com/skylot/jadx/actions?query=workflow%3ABuild)
|
||||||
[](https://codecov.io/gh/skylot/jadx)
|

|
||||||
[](https://lgtm.com/projects/g/skylot/jadx/alerts/)
|

|
||||||
[](https://sonarcloud.io/dashboard?id=jadx)
|

|
||||||
|

|
||||||
|
[](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)
|
||||||
[](https://github.com/semantic-release/semantic-release)
|
|
||||||
|
|
||||||
**jadx** - Dex to Java decompiler
|
**jadx** - Dex to Java decompiler
|
||||||
|
|
||||||
Command line and GUI tools for producing Java source code from Android Dex and Apk files
|
Command line and GUI tools for producing Java source code from Android Dex and Apk files
|
||||||
|
|
||||||
|
:exclamation::exclamation::exclamation: Please note that in most cases **jadx** can't decompile all 100% of the code, so errors will occur. Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds
|
||||||
|
|
||||||
**Main features:**
|
**Main features:**
|
||||||
- decompile Dalvik bytecode to java classes from APK, dex, aar and zip files
|
- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
|
||||||
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
|
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
|
||||||
- deobfuscator included
|
- deobfuscator included
|
||||||
|
|
||||||
@@ -23,35 +26,43 @@ Command line and GUI tools for producing Java source code from Android Dex and A
|
|||||||
- jump to declaration
|
- jump to declaration
|
||||||
- find usage
|
- find usage
|
||||||
- full text search
|
- full text search
|
||||||
|
- smali debugger, check [wiki page](https://github.com/skylot/jadx/wiki/Smali-debugger) for setup and usage
|
||||||
|
|
||||||
|
Jadx-gui key bindings can be found [here](https://github.com/skylot/jadx/wiki/JADX-GUI-Key-bindings)
|
||||||
|
|
||||||
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
|
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
|
||||||
|
|
||||||
|
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
- latest [unstable build:  ](https://bintray.com/skylot/jadx/unstable/_latestVersion#files)
|
- release
|
||||||
- release from [github: ](https://github.com/skylot/jadx/releases/latest)
|
from [github: ](https://github.com/skylot/jadx/releases/latest)
|
||||||
- release from [bintray:  ](https://bintray.com/skylot/jadx/releases/_latestVersion#files)
|
- 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
|
||||||
- `jadx-gui` - UI version
|
- `jadx-gui` - UI version
|
||||||
|
|
||||||
On Windows run `.bat` files with double-click\
|
On Windows run `.bat` files with double-click\
|
||||||
**Note:** ensure you have installed Java 8 or later 64-bit version.
|
**Note:** ensure you have installed Java 11 or later 64-bit version.
|
||||||
For windows you can download it from [adoptopenjdk.net](https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot#x64_win) (select "Install JRE").
|
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
|
||||||
|
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
|
||||||
|
|
||||||
### Build from source
|
### Build from source
|
||||||
JDK 8 or higher must be installed:
|
JDK 8 or higher must be installed:
|
||||||
@@ -68,44 +79,75 @@ and also packed to `build/jadx-<version>.zip`
|
|||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
```
|
```
|
||||||
jadx[-gui] [options] <input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)
|
jadx[-gui] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab)
|
||||||
options:
|
options:
|
||||||
-d, --output-dir - output directory
|
-d, --output-dir - output directory
|
||||||
-ds, --output-dir-src - output directory for sources
|
-ds, --output-dir-src - output directory for sources
|
||||||
-dr, --output-dir-res - output directory for resources
|
-dr, --output-dir-res - output directory for resources
|
||||||
-r, --no-res - do not decode resources
|
-r, --no-res - do not decode resources
|
||||||
-s, --no-src - do not decompile source code
|
-s, --no-src - do not decompile source code
|
||||||
--single-class - decompile a single class
|
--single-class - decompile a single class, full name, raw or alias
|
||||||
|
--single-class-output - file or dir for write if decompile a single class
|
||||||
--output-format - can be 'java' or 'json', default: java
|
--output-format - can be 'java' or 'json', default: java
|
||||||
-e, --export-gradle - save as android gradle project
|
-e, --export-gradle - save as android gradle project
|
||||||
-j, --threads-count - processing threads count, default: 4
|
-j, --threads-count - processing threads count, default: 4
|
||||||
|
-m, --decompilation-mode - code output mode:
|
||||||
|
'auto' - trying best options (default)
|
||||||
|
'restructure' - restore code structure (normal java code)
|
||||||
|
'simple' - simplified instructions (linear, with goto's)
|
||||||
|
'fallback' - raw instructions without modifications
|
||||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||||
--no-imports - disable use of imports, always write entire package name
|
--no-imports - disable use of imports, always write entire package name
|
||||||
--no-debug-info - disable debug info
|
--no-debug-info - disable debug info
|
||||||
|
--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-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
|
||||||
--deobf - activate deobfuscation
|
--deobf - activate deobfuscation
|
||||||
--deobf-min - min length of name, renamed if shorter, default: 3
|
--deobf-min - min length of name, renamed if shorter, default: 3
|
||||||
--deobf-max - max length of name, renamed if longer, default: 64
|
--deobf-max - max length of name, renamed if longer, default: 64
|
||||||
--deobf-rewrite-cfg - force to save deobfuscation map
|
--deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension
|
||||||
|
--deobf-cfg-file-mode - set mode for handle deobfuscation map file:
|
||||||
|
'read' - read if found, don't save (default)
|
||||||
|
'read-or-save' - read if found, save otherwise (don't overwrite)
|
||||||
|
'overwrite' - don't read, always save
|
||||||
|
'ignore' - don't read and don't save
|
||||||
--deobf-use-sourcename - use source file name as class name alias
|
--deobf-use-sourcename - use source file name as class name alias
|
||||||
--rename-flags - what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all' (default)
|
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
||||||
|
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
||||||
|
--rename-flags - fix options (comma-separated list of):
|
||||||
|
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
||||||
|
'valid' - rename java identifiers to make them valid,
|
||||||
|
'printable' - remove non-printable chars from identifiers,
|
||||||
|
or single 'none' - to disable all renames
|
||||||
|
or single 'all' - to enable all (default)
|
||||||
--fs-case-sensitive - treat filesystem as case sensitive, false by default
|
--fs-case-sensitive - treat filesystem as case sensitive, false by default
|
||||||
--cfg - save methods control flow graph to dot file
|
--cfg - save methods control flow graph to dot file
|
||||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
-f, --fallback - set '--decompilation-mode' to 'fallback' (deprecated)
|
||||||
|
--use-dx - use dx/d8 to convert java bytecode
|
||||||
|
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
|
||||||
|
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
|
||||||
-v, --verbose - verbose output (set --log-level to DEBUG)
|
-v, --verbose - verbose output (set --log-level to DEBUG)
|
||||||
-q, --quiet - turn off output (set --log-level to QUIET)
|
-q, --quiet - turn off output (set --log-level to QUIET)
|
||||||
--log-level - set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG, default: PROGRESS
|
|
||||||
--version - print jadx version
|
--version - print jadx version
|
||||||
-h, --help - print this help
|
-h, --help - print this help
|
||||||
Example:
|
|
||||||
jadx -d out classes.dex
|
Plugin options (-P<name>=<value>):
|
||||||
jadx --rename-flags "none" classes.dex
|
1) dex-input: Load .dex and .apk files
|
||||||
jadx --rename-flags "valid,printable" classes.dex
|
- dex-input.verify-checksum - verify dex file checksum before load, values: [yes, no], default: yes
|
||||||
jadx --log-level error app.apk
|
2) java-convert: Convert .class, .jar and .aar files to dex
|
||||||
|
- java-convert.mode - convert mode, values: [dx, d8, both], default: both
|
||||||
|
- java-convert.d8-desugar - use desugar in d8, values: [yes, no], default: no
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
jadx -d out classes.dex
|
||||||
|
jadx --rename-flags "none" classes.dex
|
||||||
|
jadx --rename-flags "valid, printable" classes.dex
|
||||||
|
jadx --log-level ERROR app.apk
|
||||||
|
jadx -Pdex-input.verify-checksum=no app.apk
|
||||||
```
|
```
|
||||||
These options also worked on jadx-gui running from command line and override options from preferences dialog
|
These options also worked on jadx-gui running from command line and override options from preferences dialog
|
||||||
|
|
||||||
@@ -118,8 +160,5 @@ To support this project you can:
|
|||||||
- Submit decompilation issues, please read before proceed: [Open issue](CONTRIBUTING.md#Open-Issue)
|
- Submit decompilation issues, please read before proceed: [Open issue](CONTRIBUTING.md#Open-Issue)
|
||||||
- Open pull request, please follow these rules: [Pull Request Process](CONTRIBUTING.md#Pull-Request-Process)
|
- Open pull request, please follow these rules: [Pull Request Process](CONTRIBUTING.md#Pull-Request-Process)
|
||||||
|
|
||||||
### Related projects:
|
|
||||||
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
|
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
*Licensed under the Apache 2.0 License*
|
*Licensed under the Apache 2.0 License*
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
To report a security issue, please email `skylot@gmail.com` with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
|
||||||
|
We will check and respond within 3 working days. If the issue is confirmed as a vulnerability, we will apply required mitigations at the next release.
|
||||||
|
This project follows a 90 day disclosure timeline.
|
||||||
+58
-68
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'org.sonarqube' version '3.0'
|
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||||
id 'com.github.ben-manes.versions' version '0.33.0'
|
id 'com.diffplug.spotless' version '6.6.1'
|
||||||
id "com.diffplug.spotless" version "5.5.1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||||
@@ -10,19 +9,11 @@ println("jadx version: ${jadxVersion}")
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'jacoco'
|
|
||||||
apply plugin: 'checkstyle'
|
apply plugin: 'checkstyle'
|
||||||
|
|
||||||
version = jadxVersion
|
version = jadxVersion
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
|
|
||||||
tasks.withType(JavaCompile) {
|
|
||||||
if (!"$it".contains(':jadx-samples:')) {
|
|
||||||
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileJava {
|
compileJava {
|
||||||
options.encoding = "UTF-8"
|
options.encoding = "UTF-8"
|
||||||
@@ -35,52 +26,34 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.slf4j:slf4j-api:1.7.30'
|
implementation 'org.slf4j:slf4j-api:1.7.36'
|
||||||
compileOnly 'org.jetbrains:annotations:20.1.0'
|
compileOnly 'org.jetbrains:annotations:23.0.0'
|
||||||
|
|
||||||
testImplementation 'ch.qos.logback:logback-classic:1.2.3'
|
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||||
testImplementation 'org.mockito:mockito-core:3.5.10'
|
testImplementation 'org.mockito:mockito-core:4.6.0'
|
||||||
testImplementation 'org.assertj:assertj-core:3.17.2'
|
testImplementation 'org.assertj:assertj-core:3.22.0'
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
|
||||||
|
|
||||||
testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
|
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
||||||
testCompileOnly 'org.jetbrains:annotations:20.1.0'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
|
maxParallelForks = Runtime.runtime.availableProcessors()
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
|
||||||
google()
|
google()
|
||||||
}
|
// Commented out for now since we're using a local mapping-io fork atm.
|
||||||
|
// maven {
|
||||||
jacoco {
|
// name 'FabricMC'
|
||||||
toolVersion = "0.8.6"
|
// url 'https://maven.fabricmc.net/'
|
||||||
}
|
// }
|
||||||
jacocoTestReport {
|
|
||||||
reports {
|
|
||||||
xml.enabled = true
|
|
||||||
html.enabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkstyleMain {
|
|
||||||
// exclude all sources in samples module
|
|
||||||
exclude '**/samples/**'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sonarqube {
|
|
||||||
properties {
|
|
||||||
property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*'
|
|
||||||
property 'sonar.coverage.exclusions', '**/jadx/gui/**/*'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,36 +101,55 @@ dependencyUpdates {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
|
task copyArtifacts(type: Copy) {
|
||||||
destinationDir file("$buildDir/jadx")
|
from tasks.getByPath(":jadx-cli:installDist")
|
||||||
['jadx-cli', 'jadx-gui'].each {
|
from tasks.getByPath(":jadx-gui:installDist")
|
||||||
from tasks.getByPath(":${it}:installDist").destinationDir
|
into layout.buildDirectory.dir("jadx")
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
|
|
||||||
|
task pack(type: Zip) {
|
||||||
|
from copyArtifacts
|
||||||
|
archiveFileName = "jadx-${jadxVersion}.zip"
|
||||||
|
destinationDirectory = layout.buildDirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
task copyExe(type: Copy) {
|
||||||
|
group 'jadx'
|
||||||
|
description = 'Copy exe to build dir'
|
||||||
|
mustRunAfter pack // not needed, but gradle throws warning because of same output dir
|
||||||
|
|
||||||
|
from tasks.getByPath('jadx-gui:createExe')
|
||||||
|
include '*.exe'
|
||||||
|
into layout.buildDirectory
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
|
|
||||||
|
task distWinBundle(type: Copy, dependsOn: 'jadx-gui:distWinWithJre') {
|
||||||
|
group 'jadx'
|
||||||
|
description = 'Copy bundle to build dir'
|
||||||
|
destinationDir buildDir
|
||||||
|
from(tasks.getByPath('jadx-gui:distWinWithJre').outputs) {
|
||||||
|
include '*.zip'
|
||||||
}
|
}
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
}
|
}
|
||||||
|
|
||||||
task pack(type: Zip, dependsOn: copyArtifacts) {
|
task dist {
|
||||||
destinationDirectory = buildDir
|
|
||||||
archiveFileName = "jadx-${jadxVersion}.zip"
|
|
||||||
from copyArtifacts.destinationDir
|
|
||||||
}
|
|
||||||
|
|
||||||
task copyExe(type: Copy, dependsOn: 'jadx-gui:createExe') {
|
|
||||||
group 'jadx'
|
|
||||||
description = 'Copy exe to build dir'
|
|
||||||
destinationDir buildDir
|
|
||||||
from tasks.getByPath('jadx-gui:createExe').outputs
|
|
||||||
include '*.exe'
|
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
|
||||||
}
|
|
||||||
|
|
||||||
task dist(dependsOn: [pack, copyExe]) {
|
|
||||||
group 'jadx'
|
group 'jadx'
|
||||||
description = 'Build jadx distribution zip'
|
description = 'Build jadx distribution zip'
|
||||||
}
|
|
||||||
|
|
||||||
task samples(dependsOn: 'jadx-samples:samples') {
|
dependsOn(pack)
|
||||||
group 'jadx'
|
|
||||||
|
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
|
||||||
|
if (os.isWindows()) {
|
||||||
|
if (project.hasProperty("bundleJRE")) {
|
||||||
|
println("Build win bundle with JRE")
|
||||||
|
dependsOn('distWinBundle')
|
||||||
|
} else {
|
||||||
|
dependsOn('copyExe')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task cleanBuildDir(type: Delete) {
|
task cleanBuildDir(type: Delete) {
|
||||||
@@ -165,6 +157,4 @@ task cleanBuildDir(type: Delete) {
|
|||||||
delete buildDir
|
delete buildDir
|
||||||
}
|
}
|
||||||
|
|
||||||
test.dependsOn(samples)
|
|
||||||
|
|
||||||
clean.dependsOn(cleanBuildDir)
|
clean.dependsOn(cleanBuildDir)
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
plugins {
|
||||||
|
id 'groovy-gradle-plugin'
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
id 'maven-publish'
|
||||||
|
id 'signing'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
group = 'io.github.skylot'
|
||||||
|
version = jadxVersion
|
||||||
|
|
||||||
|
java {
|
||||||
|
withJavadocJar()
|
||||||
|
withSourcesJar()
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
mavenJava(MavenPublication) {
|
||||||
|
artifactId = project.name
|
||||||
|
from components.java
|
||||||
|
versionMapping {
|
||||||
|
usage('java-api') {
|
||||||
|
fromResolutionOf('runtimeClasspath')
|
||||||
|
}
|
||||||
|
usage('java-runtime') {
|
||||||
|
fromResolutionResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pom {
|
||||||
|
name = project.name
|
||||||
|
description = 'Dex to Java decompiler'
|
||||||
|
url = 'https://github.com/skylot/jadx'
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name = 'The Apache License, Version 2.0'
|
||||||
|
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id = 'skylot'
|
||||||
|
name = 'Skylot'
|
||||||
|
email = 'skylot@gmail.com'
|
||||||
|
url = 'https://github.com/skylot'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scm {
|
||||||
|
connection = 'scm:git:git://github.com/skylot/jadx.git'
|
||||||
|
developerConnection = 'scm:git:ssh://github.com:skylot/jadx.git'
|
||||||
|
url = 'https://github.com/skylot/jadx'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
def releasesRepoUrl = uri('https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/')
|
||||||
|
def snapshotsRepoUrl = uri('https://s01.oss.sonatype.org/content/repositories/snapshots/')
|
||||||
|
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
|
||||||
|
credentials {
|
||||||
|
username = project.properties['ossrhUser'].toString()
|
||||||
|
password = project.properties['ossrhPassword'].toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signing {
|
||||||
|
required { gradle.taskGraph.hasTask("publish") }
|
||||||
|
sign publishing.publications.mavenJava
|
||||||
|
}
|
||||||
|
|
||||||
|
javadoc {
|
||||||
|
if (JavaVersion.current().isJava9Compatible()) {
|
||||||
|
options.addBooleanOption('html5', true)
|
||||||
|
}
|
||||||
|
// disable 'missing' warnings
|
||||||
|
options.addStringOption('Xdoclint:all,-missing', '-quiet')
|
||||||
|
}
|
||||||
@@ -119,6 +119,17 @@
|
|||||||
<module name="OuterTypeNumber"/>
|
<module name="OuterTypeNumber"/>
|
||||||
|
|
||||||
<module name="SuppressWarningsHolder"/>
|
<module name="SuppressWarningsHolder"/>
|
||||||
|
|
||||||
|
<module name="IllegalType"/>
|
||||||
|
<module name="IllegalImport">
|
||||||
|
<property name="illegalClasses" value="jadx.core.utils.DebugUtils"/>
|
||||||
|
</module>
|
||||||
|
<module name="RegexpSinglelineJava">
|
||||||
|
<property name="id" value="printstacktrace"/>
|
||||||
|
<property name="format" value="\.printStackTrace\(\)"/>
|
||||||
|
<property name="ignoreComments" value="true"/>
|
||||||
|
<property name="message" value="Using Throwable.printStackTrace() is forbidden. Use logger to print exception"/>
|
||||||
|
</module>
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
<module name="NewlineAtEndOfFile"/>
|
<module name="NewlineAtEndOfFile"/>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
SmaliTokenMaker.java
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Refer to the following instructions to modify and use to generate SmaliTokenMarker
|
||||||
|
|
||||||
|
```shell
|
||||||
|
jflex SmaliTokenMaker.flex --skel skeleton.default
|
||||||
|
```
|
||||||
@@ -0,0 +1,681 @@
|
|||||||
|
/*
|
||||||
|
* Generated on 11/22/21, 8:58 PM
|
||||||
|
*/
|
||||||
|
package jadx.gui.ui.codeearea;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import javax.swing.text.Segment;
|
||||||
|
|
||||||
|
import org.fife.ui.rsyntaxtextarea.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于Smali代码高亮
|
||||||
|
* MartinKay@qq.com
|
||||||
|
*/
|
||||||
|
%%
|
||||||
|
|
||||||
|
%public
|
||||||
|
%class SmaliTokenMaker
|
||||||
|
%extends AbstractJFlexCTokenMaker
|
||||||
|
%unicode
|
||||||
|
/* Case sensitive */
|
||||||
|
%type org.fife.ui.rsyntaxtextarea.Token
|
||||||
|
|
||||||
|
|
||||||
|
%{
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor. This must be here because JFlex does not generate a
|
||||||
|
* no-parameter constructor.
|
||||||
|
*/
|
||||||
|
public SmaliTokenMaker() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the token specified to the current linked list of tokens.
|
||||||
|
*
|
||||||
|
* @param tokenType The token's type.
|
||||||
|
* @see #addToken(int, int, int)
|
||||||
|
*/
|
||||||
|
private void addHyperlinkToken(int start, int end, int tokenType) {
|
||||||
|
int so = start + offsetShift;
|
||||||
|
addToken(zzBuffer, start,end, tokenType, so, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the token specified to the current linked list of tokens.
|
||||||
|
*
|
||||||
|
* @param tokenType The token's type.
|
||||||
|
*/
|
||||||
|
private void addToken(int tokenType) {
|
||||||
|
addToken(zzStartRead, zzMarkedPos-1, tokenType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the token specified to the current linked list of tokens.
|
||||||
|
*
|
||||||
|
* @param tokenType The token's type.
|
||||||
|
* @see #addHyperlinkToken(int, int, int)
|
||||||
|
*/
|
||||||
|
private void addToken(int start, int end, int tokenType) {
|
||||||
|
int so = start + offsetShift;
|
||||||
|
addToken(zzBuffer, start,end, tokenType, so, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the token specified to the current linked list of tokens.
|
||||||
|
*
|
||||||
|
* @param array The character array.
|
||||||
|
* @param start The starting offset in the array.
|
||||||
|
* @param end The ending offset in the array.
|
||||||
|
* @param tokenType The token's type.
|
||||||
|
* @param startOffset The offset in the document at which this token
|
||||||
|
* occurs.
|
||||||
|
* @param hyperlink Whether this token is a hyperlink.
|
||||||
|
*/
|
||||||
|
public void addToken(char[] array, int start, int end, int tokenType,
|
||||||
|
int startOffset, boolean hyperlink) {
|
||||||
|
super.addToken(array, start,end, tokenType, startOffset, hyperlink);
|
||||||
|
zzStartRead = zzMarkedPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public String[] getLineCommentStartAndEnd(int languageIndex) {
|
||||||
|
return new String[] { "#", null };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first token in the linked list of tokens generated
|
||||||
|
* from <code>text</code>. This method must be implemented by
|
||||||
|
* subclasses so they can correctly implement syntax highlighting.
|
||||||
|
*
|
||||||
|
* @param text The text from which to get tokens.
|
||||||
|
* @param initialTokenType The token type we should start with.
|
||||||
|
* @param startOffset The offset into the document at which
|
||||||
|
* <code>text</code> starts.
|
||||||
|
* @return The first <code>Token</code> in a linked list representing
|
||||||
|
* the syntax highlighted text.
|
||||||
|
*/
|
||||||
|
public Token getTokenList(Segment text, int initialTokenType, int startOffset) {
|
||||||
|
|
||||||
|
resetTokenList();
|
||||||
|
this.offsetShift = -text.offset + startOffset;
|
||||||
|
|
||||||
|
// Start off in the proper state.
|
||||||
|
int state = Token.NULL;
|
||||||
|
switch (initialTokenType) {
|
||||||
|
/* No multi-line comments */
|
||||||
|
/* No documentation comments */
|
||||||
|
default:
|
||||||
|
state = Token.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
s = text;
|
||||||
|
try {
|
||||||
|
yyreset(zzReader);
|
||||||
|
yybegin(state);
|
||||||
|
return yylex();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
ioe.printStackTrace();
|
||||||
|
return new TokenImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refills the input buffer.
|
||||||
|
*
|
||||||
|
* @return <code>true</code> if EOF was reached, otherwise
|
||||||
|
* <code>false</code>.
|
||||||
|
*/
|
||||||
|
private boolean zzRefill() {
|
||||||
|
return zzCurrentPos>=s.offset+s.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the scanner to read from a new input stream.
|
||||||
|
* Does not close the old reader.
|
||||||
|
*
|
||||||
|
* All internal variables are reset, the old input stream
|
||||||
|
* <b>cannot</b> be reused (internal buffer is discarded and lost).
|
||||||
|
* Lexical state is set to <tt>YY_INITIAL</tt>.
|
||||||
|
*
|
||||||
|
* @param reader the new input stream
|
||||||
|
*/
|
||||||
|
public final void yyreset(Reader reader) {
|
||||||
|
// 's' has been updated.
|
||||||
|
zzBuffer = s.array;
|
||||||
|
/*
|
||||||
|
* We replaced the line below with the two below it because zzRefill
|
||||||
|
* no longer "refills" the buffer (since the way we do it, it's always
|
||||||
|
* "full" the first time through, since it points to the segment's
|
||||||
|
* array). So, we assign zzEndRead here.
|
||||||
|
*/
|
||||||
|
//zzStartRead = zzEndRead = s.offset;
|
||||||
|
zzStartRead = s.offset;
|
||||||
|
zzEndRead = zzStartRead + s.count - 1;
|
||||||
|
zzCurrentPos = zzMarkedPos = zzPushbackPos = s.offset;
|
||||||
|
zzLexicalState = YYINITIAL;
|
||||||
|
zzReader = reader;
|
||||||
|
zzAtBOL = true;
|
||||||
|
zzAtEOF = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
Letter = [A-Za-z]
|
||||||
|
LetterOrUnderscore = ({Letter}|"_")
|
||||||
|
NonzeroDigit = [1-9]
|
||||||
|
Digit = ("0"|{NonzeroDigit})
|
||||||
|
HexDigit = ({Digit}|[A-Fa-f])
|
||||||
|
OctalDigit = ([0-7])
|
||||||
|
AnyCharacterButApostropheOrBackSlash = ([^\\'])
|
||||||
|
AnyCharacterButDoubleQuoteOrBackSlash = ([^\\\"\n])
|
||||||
|
EscapedSourceCharacter = ("u"{HexDigit}{HexDigit}{HexDigit}{HexDigit})
|
||||||
|
Escape = ("\\"(([btnfr\"'\\])|([0123]{OctalDigit}?{OctalDigit}?)|({OctalDigit}{OctalDigit}?)|{EscapedSourceCharacter}))
|
||||||
|
NonSeparator = ([^\t\f\r\n\ \(\)\{\}\[\]\;\,\.\=\>\<\!\~\?\:\+\-\*\/\&\|\^\%\"\']|"#"|"\\")
|
||||||
|
IdentifierStart = ({LetterOrUnderscore}|"$")
|
||||||
|
IdentifierPart = ({IdentifierStart}|{Digit}|("\\"{EscapedSourceCharacter}))
|
||||||
|
|
||||||
|
LineTerminator = (\n)
|
||||||
|
WhiteSpace = ([ \t\f]+)
|
||||||
|
|
||||||
|
CharLiteral = ([\']({AnyCharacterButApostropheOrBackSlash}|{Escape})[\'])
|
||||||
|
UnclosedCharLiteral = ([\'][^\'\n]*)
|
||||||
|
ErrorCharLiteral = ({UnclosedCharLiteral}[\'])
|
||||||
|
StringLiteral = ([\"]({AnyCharacterButDoubleQuoteOrBackSlash}|{Escape})*[\"])
|
||||||
|
UnclosedStringLiteral = ([\"]([\\].|[^\\\"])*[^\"]?)
|
||||||
|
ErrorStringLiteral = ({UnclosedStringLiteral}[\"])
|
||||||
|
|
||||||
|
/* No multi-line comments */
|
||||||
|
/* No documentation comments */
|
||||||
|
LineCommentBegin = "#"
|
||||||
|
|
||||||
|
IntegerLiteral = ({Digit}+)
|
||||||
|
HexLiteral = (0x{HexDigit}+)
|
||||||
|
FloatLiteral = (({Digit}+)("."{Digit}+)?(e[+-]?{Digit}+)? | ({Digit}+)?("."{Digit}+)(e[+-]?{Digit}+)?)
|
||||||
|
ErrorNumberFormat = (({IntegerLiteral}|{HexLiteral}|{FloatLiteral}){NonSeparator}+)
|
||||||
|
BooleanLiteral = ("true"|"false")
|
||||||
|
|
||||||
|
Separator = ([\(\)\{\}\[\]])
|
||||||
|
Separator2 = ([\;,.])
|
||||||
|
|
||||||
|
Identifier = ({IdentifierStart}{IdentifierPart}*)
|
||||||
|
|
||||||
|
URLGenDelim = ([:\/\?#\[\]@])
|
||||||
|
URLSubDelim = ([\!\$&'\(\)\*\+,;=])
|
||||||
|
URLUnreserved = ({LetterOrUnderscore}|{Digit}|[\-\.\~])
|
||||||
|
URLCharacter = ({URLGenDelim}|{URLSubDelim}|{URLUnreserved}|[%])
|
||||||
|
URLCharacters = ({URLCharacter}*)
|
||||||
|
URLEndCharacter = ([\/\$]|{Letter}|{Digit})
|
||||||
|
URL = (((https?|f(tp|ile))"://"|"www.")({URLCharacters}{URLEndCharacter})?)
|
||||||
|
|
||||||
|
|
||||||
|
/* Custom Regex */
|
||||||
|
/* fully-qualified name Rules */
|
||||||
|
SimpleName = ([a-zA-Z0-9_$]*)
|
||||||
|
QUALIFIED_TYPE_NAME = ("L"({SimpleName}{SLASH})*{SimpleName}*";")
|
||||||
|
/* Types */
|
||||||
|
VOID_TYPE = ("V")
|
||||||
|
BOOLEAN_TYPE = ("Z")
|
||||||
|
BYTE_TYPE = ("B")
|
||||||
|
SHORT_TYPE = ("S")
|
||||||
|
CHAR_TYPE = ("C")
|
||||||
|
INT_TYPE = ("I")
|
||||||
|
LONG_TYPE = ("J")
|
||||||
|
FLOAT_TYPE = ("F")
|
||||||
|
DOUBLE_TYPE = ("D")
|
||||||
|
/* Multi Args Types Highlight */
|
||||||
|
MULTI_ARGS_TYPES = (({BOOLEAN_TYPE}|{BYTE_TYPE}|{SHORT_TYPE}|{CHAR_TYPE}|{INT_TYPE}|{LONG_TYPE}|{FLOAT_TYPE}|{DOUBLE_TYPE})+);
|
||||||
|
|
||||||
|
/* Types fully-qualified name */
|
||||||
|
COMPOUND_METHOD_ARG_LITERAL = (({BOOLEAN_TYPE}|{BYTE_TYPE}|{SHORT_TYPE}|{CHAR_TYPE}|{INT_TYPE}|{LONG_TYPE}|{FLOAT_TYPE}|{DOUBLE_TYPE})+{QUALIFIED_TYPE_NAME})
|
||||||
|
|
||||||
|
|
||||||
|
LBRACK = ("[")
|
||||||
|
RBRACK = ("]")
|
||||||
|
LPAREN = ("(")
|
||||||
|
RPAREN = (")")
|
||||||
|
LBRACE = ("{")
|
||||||
|
RBRACE = ("}")
|
||||||
|
COLON = (":")
|
||||||
|
ASSIGN = ("=")
|
||||||
|
DOT = (".")
|
||||||
|
SUB = ("-")
|
||||||
|
COMMA = (",")
|
||||||
|
SLASH = ("/")
|
||||||
|
LT = ("<")
|
||||||
|
GT = (">")
|
||||||
|
ARROW = ("->")
|
||||||
|
SEMI = (";")
|
||||||
|
ARROW_FUNCTION = ({ARROW}[a-zA-Z_$<>]*)
|
||||||
|
CustomSeparator = ({Separator}|{ARROW_FUNCTION})
|
||||||
|
|
||||||
|
/* Register */
|
||||||
|
VREGISTER = ("v"("0"|[1-9])*)
|
||||||
|
PREGISTER = ("p"("0"|[1-9])*)
|
||||||
|
|
||||||
|
/* Flags */
|
||||||
|
FLAG_PSWITCH = (":pswitch_"{SimpleName})
|
||||||
|
FLAG_PSWITCH_DATA = (":pswitch_data_"{SimpleName})
|
||||||
|
FLAG_GOTO = (":goto_"{SimpleName})
|
||||||
|
FLAG_COND = (":cond_"{SimpleName})
|
||||||
|
FLAG_TRY_START = (":try_start_"{SimpleName})
|
||||||
|
FLAG_TRY_END = (":try_end_"{SimpleName})
|
||||||
|
FLAG_CATCH = (":catch_"{SimpleName})
|
||||||
|
FLAG_CATCHALL = (":catchall_"{SimpleName})
|
||||||
|
FLAG_ARRAY = (":array_"{SimpleName})
|
||||||
|
|
||||||
|
/* No string state */
|
||||||
|
/* No char state */
|
||||||
|
/* No MLC state */
|
||||||
|
/* No documentation comment state */
|
||||||
|
%state EOL_COMMENT
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
<YYINITIAL> {
|
||||||
|
|
||||||
|
/* Keywords Instructions Highlight */
|
||||||
|
"nop" |
|
||||||
|
"move" |
|
||||||
|
"move/from16" |
|
||||||
|
"move/16" |
|
||||||
|
"move-wide" |
|
||||||
|
"move-wide/from16" |
|
||||||
|
"move-wide/16" |
|
||||||
|
"move-object" |
|
||||||
|
"move-object/from16" |
|
||||||
|
"move-object/16" |
|
||||||
|
"move-result" |
|
||||||
|
"move-result-wide" |
|
||||||
|
"move-result-object" |
|
||||||
|
"move-exception" |
|
||||||
|
"return-void" |
|
||||||
|
"return" |
|
||||||
|
"return-wide" |
|
||||||
|
"return-object" |
|
||||||
|
"const/4" |
|
||||||
|
"const/16" |
|
||||||
|
"const" |
|
||||||
|
"const/high16" |
|
||||||
|
"const-wide/16" |
|
||||||
|
"const-wide/32" |
|
||||||
|
"const-wide" |
|
||||||
|
"const-wide/high16" |
|
||||||
|
"const-string" |
|
||||||
|
"const-string/jumbo" |
|
||||||
|
"const-class" |
|
||||||
|
"monitor-enter" |
|
||||||
|
"monitor-exit" |
|
||||||
|
"check-cast" |
|
||||||
|
"instance-of" |
|
||||||
|
"array-length" |
|
||||||
|
"new-instance" |
|
||||||
|
"new-array" |
|
||||||
|
"filled-new-array" |
|
||||||
|
"filled-new-array/range" |
|
||||||
|
"fill-array-data" |
|
||||||
|
"throw" |
|
||||||
|
"goto" |
|
||||||
|
"goto/16" |
|
||||||
|
"goto/32" |
|
||||||
|
"cmpl-float" |
|
||||||
|
"cmpg-float" |
|
||||||
|
"cmpl-double" |
|
||||||
|
"cmpg-double" |
|
||||||
|
"cmp-long" |
|
||||||
|
"if-eq" |
|
||||||
|
"if-ne" |
|
||||||
|
"if-lt" |
|
||||||
|
"if-ge" |
|
||||||
|
"if-gt" |
|
||||||
|
"if-le" |
|
||||||
|
"if-eqz" |
|
||||||
|
"if-nez" |
|
||||||
|
"if-ltz" |
|
||||||
|
"if-gez" |
|
||||||
|
"if-gtz" |
|
||||||
|
"if-lez" |
|
||||||
|
"aget" |
|
||||||
|
"aget-wide" |
|
||||||
|
"aget-object" |
|
||||||
|
"aget-boolean" |
|
||||||
|
"aget-byte" |
|
||||||
|
"aget-char" |
|
||||||
|
"aget-short" |
|
||||||
|
"aput" |
|
||||||
|
"aput-wide" |
|
||||||
|
"aput-object" |
|
||||||
|
"aput-boolean" |
|
||||||
|
"aput-byte" |
|
||||||
|
"aput-char" |
|
||||||
|
"aput-short" |
|
||||||
|
"iget" |
|
||||||
|
"iget-wide" |
|
||||||
|
"iget-object" |
|
||||||
|
"iget-boolean" |
|
||||||
|
"iget-byte" |
|
||||||
|
"iget-char" |
|
||||||
|
"iget-short" |
|
||||||
|
"iput" |
|
||||||
|
"iput-wide" |
|
||||||
|
"iput-object" |
|
||||||
|
"iput-boolean" |
|
||||||
|
"iput-byte" |
|
||||||
|
"iput-char" |
|
||||||
|
"iput-short" |
|
||||||
|
"sget" |
|
||||||
|
"sget-wide" |
|
||||||
|
"sget-object" |
|
||||||
|
"sget-boolean" |
|
||||||
|
"sget-byte" |
|
||||||
|
"sget-char" |
|
||||||
|
"sget-short" |
|
||||||
|
"sput" |
|
||||||
|
"sput-wide" |
|
||||||
|
"sput-object" |
|
||||||
|
"sput-boolean" |
|
||||||
|
"sput-byte" |
|
||||||
|
"sput-char" |
|
||||||
|
"sput-short" |
|
||||||
|
"invoke-virtual" |
|
||||||
|
"invoke-super" |
|
||||||
|
"invoke-direct" |
|
||||||
|
"invoke-static" |
|
||||||
|
"invoke-interface" |
|
||||||
|
"invoke-virtual/range" |
|
||||||
|
"invoke-super/range" |
|
||||||
|
"invoke-direct/range" |
|
||||||
|
"invoke-static/range" |
|
||||||
|
"invoke-interface/range" |
|
||||||
|
"neg-int" |
|
||||||
|
"not-int" |
|
||||||
|
"neg-long" |
|
||||||
|
"not-long" |
|
||||||
|
"neg-float" |
|
||||||
|
"neg-double" |
|
||||||
|
"int-to-long" |
|
||||||
|
"int-to-float" |
|
||||||
|
"int-to-double" |
|
||||||
|
"long-to-int" |
|
||||||
|
"long-to-float" |
|
||||||
|
"long-to-double" |
|
||||||
|
"float-to-int" |
|
||||||
|
"float-to-long" |
|
||||||
|
"float-to-double" |
|
||||||
|
"double-to-int" |
|
||||||
|
"double-to-long" |
|
||||||
|
"double-to-float" |
|
||||||
|
"int-to-byte" |
|
||||||
|
"int-to-char" |
|
||||||
|
"int-to-short" |
|
||||||
|
"add-int" |
|
||||||
|
"sub-int" |
|
||||||
|
"mul-int" |
|
||||||
|
"div-int" |
|
||||||
|
"rem-int" |
|
||||||
|
"and-int" |
|
||||||
|
"or-int" |
|
||||||
|
"xor-int" |
|
||||||
|
"shl-int" |
|
||||||
|
"shr-int" |
|
||||||
|
"ushr-int" |
|
||||||
|
"add-long" |
|
||||||
|
"sub-long" |
|
||||||
|
"mul-long" |
|
||||||
|
"div-long" |
|
||||||
|
"rem-long" |
|
||||||
|
"and-long" |
|
||||||
|
"or-long" |
|
||||||
|
"xor-long" |
|
||||||
|
"shl-long" |
|
||||||
|
"shr-long" |
|
||||||
|
"ushr-long" |
|
||||||
|
"add-float" |
|
||||||
|
"sub-float" |
|
||||||
|
"mul-float" |
|
||||||
|
"div-float" |
|
||||||
|
"rem-float" |
|
||||||
|
"add-double" |
|
||||||
|
"sub-double" |
|
||||||
|
"mul-double" |
|
||||||
|
"div-double" |
|
||||||
|
"rem-double" |
|
||||||
|
"add-int/2addr" |
|
||||||
|
"sub-int/2addr" |
|
||||||
|
"mul-int/2addr" |
|
||||||
|
"div-int/2addr" |
|
||||||
|
"rem-int/2addr" |
|
||||||
|
"and-int/2addr" |
|
||||||
|
"or-int/2addr" |
|
||||||
|
"xor-int/2addr" |
|
||||||
|
"shl-int/2addr" |
|
||||||
|
"shr-int/2addr" |
|
||||||
|
"ushr-int/2addr" |
|
||||||
|
"add-long/2addr" |
|
||||||
|
"sub-long/2addr" |
|
||||||
|
"mul-long/2addr" |
|
||||||
|
"div-long/2addr" |
|
||||||
|
"rem-long/2addr" |
|
||||||
|
"and-long/2addr" |
|
||||||
|
"or-long/2addr" |
|
||||||
|
"xor-long/2addr" |
|
||||||
|
"shl-long/2addr" |
|
||||||
|
"shr-long/2addr" |
|
||||||
|
"ushr-long/2addr" |
|
||||||
|
"add-float/2addr" |
|
||||||
|
"sub-float/2addr" |
|
||||||
|
"mul-float/2addr" |
|
||||||
|
"div-float/2addr" |
|
||||||
|
"rem-float/2addr" |
|
||||||
|
"add-double/2addr" |
|
||||||
|
"sub-double/2addr" |
|
||||||
|
"mul-double/2addr" |
|
||||||
|
"div-double/2addr" |
|
||||||
|
"rem-double/2addr" |
|
||||||
|
"add-int/lit16" |
|
||||||
|
"rsub-int" |
|
||||||
|
"mul-int/lit16" |
|
||||||
|
"div-int/lit16" |
|
||||||
|
"rem-int/lit16" |
|
||||||
|
"and-int/lit16" |
|
||||||
|
"or-int/lit16" |
|
||||||
|
"xor-int/lit16" |
|
||||||
|
"add-int/lit8" |
|
||||||
|
"rsub-int/lit8" |
|
||||||
|
"mul-int/lit8" |
|
||||||
|
"div-int/lit8" |
|
||||||
|
"rem-int/lit8" |
|
||||||
|
"and-int/lit8" |
|
||||||
|
"or-int/lit8" |
|
||||||
|
"xor-int/lit8" |
|
||||||
|
"shl-int/lit8" |
|
||||||
|
"shr-int/lit8" |
|
||||||
|
"ushr-int/lit8" |
|
||||||
|
"invoke-polymorphic" |
|
||||||
|
"invoke-polymorphic/range" |
|
||||||
|
"invoke-custom" |
|
||||||
|
"invoke-custom/range" |
|
||||||
|
"const-method-handle" |
|
||||||
|
"const-method-type" |
|
||||||
|
"packed-switch" |
|
||||||
|
"sparse-switch" { addToken(Token.FUNCTION); }
|
||||||
|
|
||||||
|
/* Keywords Modifiers(IDENTIFIER标识符、修饰符) Highlight */
|
||||||
|
"public" |
|
||||||
|
"private" |
|
||||||
|
"protected" |
|
||||||
|
"final" |
|
||||||
|
"annotation" |
|
||||||
|
"static" |
|
||||||
|
"synthetic" |
|
||||||
|
"constructor" |
|
||||||
|
"abstract" |
|
||||||
|
"enum" |
|
||||||
|
"interface" |
|
||||||
|
"transient" |
|
||||||
|
"bridge" |
|
||||||
|
"declared-synchronized" |
|
||||||
|
"volatile" |
|
||||||
|
"strictfp" |
|
||||||
|
"varargs" |
|
||||||
|
"native" { addToken(Token.RESERVED_WORD); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Keywords Directives Highlight */
|
||||||
|
".method" |
|
||||||
|
".end method" |
|
||||||
|
".implements" |
|
||||||
|
".class" |
|
||||||
|
".prologue" |
|
||||||
|
".source" |
|
||||||
|
".super" |
|
||||||
|
".field" |
|
||||||
|
".end field" |
|
||||||
|
".registers" |
|
||||||
|
".locals" |
|
||||||
|
".param" |
|
||||||
|
".line" |
|
||||||
|
".catch" |
|
||||||
|
".catchall" |
|
||||||
|
".annotation" |
|
||||||
|
".end annotation" |
|
||||||
|
".local" |
|
||||||
|
".end local" |
|
||||||
|
".restart local" |
|
||||||
|
".packed-switch" |
|
||||||
|
".end packed-switch" |
|
||||||
|
".array-data" |
|
||||||
|
".end array-data" |
|
||||||
|
".sparse-switch" |
|
||||||
|
".end sparse-switch" |
|
||||||
|
".end param" { addToken(Token.RESERVED_WORD_2); }
|
||||||
|
|
||||||
|
|
||||||
|
/* VARIABLE Register Highlight */
|
||||||
|
{VREGISTER} |
|
||||||
|
{PREGISTER} { addToken(Token.VARIABLE); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Data types Highlight */
|
||||||
|
{QUALIFIED_TYPE_NAME} |
|
||||||
|
{COMPOUND_METHOD_ARG_LITERAL} |
|
||||||
|
{MULTI_ARGS_TYPES} |
|
||||||
|
{QUALIFIED_TYPE_NAME} |
|
||||||
|
{VOID_TYPE} |
|
||||||
|
{BOOLEAN_TYPE} |
|
||||||
|
{BYTE_TYPE} |
|
||||||
|
{SHORT_TYPE} |
|
||||||
|
{CHAR_TYPE} |
|
||||||
|
{INT_TYPE} |
|
||||||
|
{LONG_TYPE} |
|
||||||
|
{FLOAT_TYPE} |
|
||||||
|
{DOUBLE_TYPE} { addToken(Token.DATA_TYPE); }
|
||||||
|
|
||||||
|
/* FLAGS */
|
||||||
|
{FLAG_PSWITCH} |
|
||||||
|
{FLAG_PSWITCH_DATA} |
|
||||||
|
{FLAG_GOTO} |
|
||||||
|
{FLAG_COND} |
|
||||||
|
{FLAG_TRY_START} |
|
||||||
|
{FLAG_TRY_END} |
|
||||||
|
{FLAG_CATCHALL} |
|
||||||
|
{FLAG_ARRAY} |
|
||||||
|
{FLAG_CATCH} { addToken(Token.MARKUP_TAG_NAME); }
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
|
/* No functions */
|
||||||
|
|
||||||
|
{BooleanLiteral} { addToken(Token.LITERAL_BOOLEAN); }
|
||||||
|
|
||||||
|
{LineTerminator} { addNullToken(); return firstToken; }
|
||||||
|
|
||||||
|
{Identifier} { addToken(Token.IDENTIFIER); }
|
||||||
|
|
||||||
|
{WhiteSpace} { addToken(Token.WHITESPACE); }
|
||||||
|
|
||||||
|
/* String/Character literals. */
|
||||||
|
{CharLiteral} { addToken(Token.LITERAL_CHAR); }
|
||||||
|
{UnclosedCharLiteral} { addToken(Token.ERROR_CHAR); addNullToken(); return firstToken; }
|
||||||
|
{ErrorCharLiteral} { addToken(Token.ERROR_CHAR); }
|
||||||
|
{StringLiteral} { addToken(Token.LITERAL_STRING_DOUBLE_QUOTE); }
|
||||||
|
{UnclosedStringLiteral} { addToken(Token.ERROR_STRING_DOUBLE); addNullToken(); return firstToken; }
|
||||||
|
{ErrorStringLiteral} { addToken(Token.ERROR_STRING_DOUBLE); }
|
||||||
|
|
||||||
|
/* Comment literals. */
|
||||||
|
/* No multi-line comments */
|
||||||
|
/* No documentation comments */
|
||||||
|
{LineCommentBegin} { start = zzMarkedPos-1; yybegin(EOL_COMMENT); }
|
||||||
|
|
||||||
|
/* Separators. */
|
||||||
|
{CustomSeparator} { addToken(Token.SEPARATOR); }
|
||||||
|
{Separator2} { addToken(Token.IDENTIFIER); }
|
||||||
|
|
||||||
|
/* Operators. */
|
||||||
|
"!" |
|
||||||
|
";" |
|
||||||
|
"." |
|
||||||
|
"=" |
|
||||||
|
"/" |
|
||||||
|
"'" |
|
||||||
|
"(" |
|
||||||
|
")" |
|
||||||
|
"," |
|
||||||
|
"->" |
|
||||||
|
";->" |
|
||||||
|
"<" |
|
||||||
|
">" |
|
||||||
|
"@" |
|
||||||
|
"[" |
|
||||||
|
"]" |
|
||||||
|
"{" |
|
||||||
|
"}" { addToken(Token.OPERATOR); }
|
||||||
|
|
||||||
|
/* Numbers */
|
||||||
|
{IntegerLiteral} { addToken(Token.LITERAL_NUMBER_DECIMAL_INT); }
|
||||||
|
{HexLiteral} { addToken(Token.LITERAL_NUMBER_HEXADECIMAL); }
|
||||||
|
{FloatLiteral} { addToken(Token.LITERAL_NUMBER_FLOAT); }
|
||||||
|
{ErrorNumberFormat} { addToken(Token.ERROR_NUMBER_FORMAT); }
|
||||||
|
|
||||||
|
/* Ended with a line not in a string or comment. */
|
||||||
|
<<EOF>> { addNullToken(); return firstToken; }
|
||||||
|
|
||||||
|
/* Catch any other (unhandled) characters. */
|
||||||
|
. { addToken(Token.IDENTIFIER); }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* No char state */
|
||||||
|
|
||||||
|
/* No string state */
|
||||||
|
|
||||||
|
/* No multi-line comment state */
|
||||||
|
|
||||||
|
/* No documentation comment state */
|
||||||
|
|
||||||
|
<EOL_COMMENT> {
|
||||||
|
[^hwf\n]+ {}
|
||||||
|
{URL} { int temp=zzStartRead; addToken(start,zzStartRead-1, Token.COMMENT_EOL); addHyperlinkToken(temp,zzMarkedPos-1, Token.COMMENT_EOL); start = zzMarkedPos; }
|
||||||
|
[hwf] {}
|
||||||
|
\n { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
|
||||||
|
<<EOF>> { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
|
||||||
|
/** This character denotes the end of file */
|
||||||
|
public static final int YYEOF = -1;
|
||||||
|
|
||||||
|
/** initial size of the lookahead buffer */
|
||||||
|
--- private static final int ZZ_BUFFERSIZE = ...;
|
||||||
|
|
||||||
|
/** lexical states */
|
||||||
|
--- lexical states, charmap
|
||||||
|
|
||||||
|
/* error codes */
|
||||||
|
private static final int ZZ_UNKNOWN_ERROR = 0;
|
||||||
|
private static final int ZZ_NO_MATCH = 1;
|
||||||
|
private static final int ZZ_PUSHBACK_2BIG = 2;
|
||||||
|
|
||||||
|
/* error messages for the codes above */
|
||||||
|
private static final String ZZ_ERROR_MSG[] = {
|
||||||
|
"Unkown internal scanner error",
|
||||||
|
"Error: could not match input",
|
||||||
|
"Error: pushback value was too large"
|
||||||
|
};
|
||||||
|
|
||||||
|
--- isFinal list
|
||||||
|
/** the input device */
|
||||||
|
private java.io.Reader zzReader;
|
||||||
|
|
||||||
|
/** the current state of the DFA */
|
||||||
|
private int zzState;
|
||||||
|
|
||||||
|
/** the current lexical state */
|
||||||
|
private int zzLexicalState = YYINITIAL;
|
||||||
|
|
||||||
|
/** this buffer contains the current text to be matched and is
|
||||||
|
the source of the yytext() string */
|
||||||
|
private char zzBuffer[];
|
||||||
|
|
||||||
|
/** the textposition at the last accepting state */
|
||||||
|
private int zzMarkedPos;
|
||||||
|
|
||||||
|
/** the textposition at the last state to be included in yytext */
|
||||||
|
private int zzPushbackPos;
|
||||||
|
|
||||||
|
/** the current text position in the buffer */
|
||||||
|
private int zzCurrentPos;
|
||||||
|
|
||||||
|
/** startRead marks the beginning of the yytext() string in the buffer */
|
||||||
|
private int zzStartRead;
|
||||||
|
|
||||||
|
/** endRead marks the last character in the buffer, that has been read
|
||||||
|
from input */
|
||||||
|
private int zzEndRead;
|
||||||
|
|
||||||
|
/** number of newlines encountered up to the start of the matched text */
|
||||||
|
private int yyline;
|
||||||
|
|
||||||
|
/** the number of characters up to the start of the matched text */
|
||||||
|
private int yychar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the number of characters from the last newline up to the start of the
|
||||||
|
* matched text
|
||||||
|
*/
|
||||||
|
private int yycolumn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* zzAtBOL == true <=> the scanner is currently at the beginning of a line
|
||||||
|
*/
|
||||||
|
private boolean zzAtBOL = true;
|
||||||
|
|
||||||
|
/** zzAtEOF == true <=> the scanner is at the EOF */
|
||||||
|
private boolean zzAtEOF;
|
||||||
|
|
||||||
|
--- user class code
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new scanner
|
||||||
|
* There is also a java.io.InputStream version of this constructor.
|
||||||
|
*
|
||||||
|
* @param in the java.io.Reader to read input from.
|
||||||
|
*/
|
||||||
|
--- constructor declaration
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the input stream.
|
||||||
|
*/
|
||||||
|
public final void yyclose() throws java.io.IOException {
|
||||||
|
zzAtEOF = true; /* indicate end of file */
|
||||||
|
zzEndRead = zzStartRead; /* invalidate buffer */
|
||||||
|
|
||||||
|
if (zzReader != null)
|
||||||
|
zzReader.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enters a new lexical state
|
||||||
|
*
|
||||||
|
* @param newState the new lexical state
|
||||||
|
*/
|
||||||
|
public final void yybegin(int newState) {
|
||||||
|
zzLexicalState = newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text matched by the current regular expression.
|
||||||
|
*/
|
||||||
|
public final String yytext() {
|
||||||
|
return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the character at position <tt>pos</tt> from the
|
||||||
|
* matched text.
|
||||||
|
*
|
||||||
|
* It is equivalent to yytext().charAt(pos), but faster
|
||||||
|
*
|
||||||
|
* @param pos the position of the character to fetch.
|
||||||
|
* A value from 0 to yylength()-1.
|
||||||
|
*
|
||||||
|
* @return the character at position pos
|
||||||
|
*/
|
||||||
|
public final char yycharat(int pos) {
|
||||||
|
return zzBuffer[zzStartRead+pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the length of the matched text region.
|
||||||
|
*/
|
||||||
|
public final int yylength() {
|
||||||
|
return zzMarkedPos-zzStartRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports an error that occured while scanning.
|
||||||
|
*
|
||||||
|
* In a wellformed scanner (no or only correct usage of
|
||||||
|
* yypushback(int) and a match-all fallback rule) this method
|
||||||
|
* will only be called with things that "Can't Possibly Happen".
|
||||||
|
* If this method is called, something is seriously wrong
|
||||||
|
* (e.g. a JFlex bug producing a faulty scanner etc.).
|
||||||
|
*
|
||||||
|
* Usual syntax/scanner level error handling should be done
|
||||||
|
* in error fallback rules.
|
||||||
|
*
|
||||||
|
* @param errorCode the code of the errormessage to display
|
||||||
|
*/
|
||||||
|
--- zzScanError declaration
|
||||||
|
String message;
|
||||||
|
try {
|
||||||
|
message = ZZ_ERROR_MSG[errorCode];
|
||||||
|
}
|
||||||
|
catch (ArrayIndexOutOfBoundsException e) {
|
||||||
|
message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
|
||||||
|
}
|
||||||
|
|
||||||
|
--- throws clause
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes the specified amount of characters back into the input stream.
|
||||||
|
*
|
||||||
|
* They will be read again by then next call of the scanning method
|
||||||
|
*
|
||||||
|
* @param number the number of characters to be read again.
|
||||||
|
* This number must not be greater than yylength()!
|
||||||
|
*/
|
||||||
|
--- yypushback decl (contains zzScanError exception)
|
||||||
|
if ( number > yylength() )
|
||||||
|
zzScanError(ZZ_PUSHBACK_2BIG);
|
||||||
|
|
||||||
|
zzMarkedPos -= number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
--- zzDoEOF
|
||||||
|
/**
|
||||||
|
* Resumes scanning until the next regular expression is matched,
|
||||||
|
* the end of input is encountered or an I/O-Error occurs.
|
||||||
|
*
|
||||||
|
* @return the next token
|
||||||
|
* @exception java.io.IOException if any I/O-Error occurs
|
||||||
|
*/
|
||||||
|
--- yylex declaration
|
||||||
|
int zzInput;
|
||||||
|
int zzAction;
|
||||||
|
|
||||||
|
// cached fields:
|
||||||
|
int zzCurrentPosL;
|
||||||
|
int zzMarkedPosL;
|
||||||
|
int zzEndReadL = zzEndRead;
|
||||||
|
char [] zzBufferL = zzBuffer;
|
||||||
|
char [] zzCMapL = ZZ_CMAP;
|
||||||
|
|
||||||
|
--- local declarations
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
zzMarkedPosL = zzMarkedPos;
|
||||||
|
|
||||||
|
--- start admin (line, char, col count)
|
||||||
|
zzAction = -1;
|
||||||
|
|
||||||
|
zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
|
||||||
|
|
||||||
|
--- start admin (lexstate etc)
|
||||||
|
|
||||||
|
zzForAction: {
|
||||||
|
while (true) {
|
||||||
|
|
||||||
|
--- next input, line, col, char count, next transition, isFinal action
|
||||||
|
zzAction = zzState;
|
||||||
|
zzMarkedPosL = zzCurrentPosL;
|
||||||
|
--- line count update
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store back cached position
|
||||||
|
zzMarkedPos = zzMarkedPosL;
|
||||||
|
--- char count update
|
||||||
|
|
||||||
|
--- actions
|
||||||
|
default:
|
||||||
|
if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
|
||||||
|
zzAtEOF = true;
|
||||||
|
--- eofvalue
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
--- no match
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
--- main
|
||||||
|
|
||||||
|
}
|
||||||
+11
-1
@@ -1 +1,11 @@
|
|||||||
org.gradle.daemon=false
|
org.gradle.warning.mode=all
|
||||||
|
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
-1
@@ -1,5 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
|
distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright 2015 the original author or authors.
|
# Copyright © 2015-2021 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -17,67 +17,101 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
#
|
||||||
## Gradle start up script for UN*X
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
##
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
PRG="$0"
|
app_path=$0
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
# Need this for daisy-chained symlinks.
|
||||||
ls=`ls -ld "$PRG"`
|
while
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
[ -h "$app_path" ]
|
||||||
PRG="$link"
|
do
|
||||||
else
|
ls=$( ls -ld "$app_path" )
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
link=${ls#*' -> '}
|
||||||
fi
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >/dev/null
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
APP_NAME="Gradle"
|
||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=${0##*/}
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD=maximum
|
||||||
|
|
||||||
warn () {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "`uname`" in
|
case "$( uname )" in #(
|
||||||
CYGWIN* )
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
cygwin=true
|
Darwin* ) darwin=true ;; #(
|
||||||
;;
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
Darwin* )
|
NONSTOP* ) nonstop=true ;;
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
NONSTOP* )
|
|
||||||
nonstop=true
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
else
|
else
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
@@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD="java"
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
@@ -106,80 +140,95 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
case $MAX_FD in #(
|
||||||
if [ $? -eq 0 ] ; then
|
max*)
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
warn "Could not query maximum file descriptor limit"
|
||||||
fi
|
esac
|
||||||
ulimit -n $MAX_FD
|
case $MAX_FD in #(
|
||||||
if [ $? -ne 0 ] ; then
|
'' | soft) :;; #(
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
*)
|
||||||
fi
|
ulimit -n "$MAX_FD" ||
|
||||||
else
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Darwin, add options to specify how the application appears in the dock
|
|
||||||
if $darwin; then
|
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
|
||||||
SEP=""
|
|
||||||
for dir in $ROOTDIRSRAW ; do
|
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
|
||||||
SEP="|"
|
|
||||||
done
|
|
||||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
|
||||||
# Add a user-defined pattern to the cygpath arguments
|
|
||||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
|
||||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
|
||||||
fi
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
i=0
|
|
||||||
for arg in "$@" ; do
|
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
|
||||||
else
|
|
||||||
eval `echo args$i`="\"$arg\""
|
|
||||||
fi
|
|
||||||
i=`expr $i + 1`
|
|
||||||
done
|
|
||||||
case $i in
|
|
||||||
0) set -- ;;
|
|
||||||
1) set -- "$args0" ;;
|
|
||||||
2) set -- "$args0" "$args1" ;;
|
|
||||||
3) set -- "$args0" "$args1" "$args2" ;;
|
|
||||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
|
||||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
|
||||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
|
||||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
|
||||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
|
||||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Escape application args
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
save () {
|
# * args from the command line
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
# * the main class name
|
||||||
echo " "
|
# * -classpath
|
||||||
}
|
# * -D...appname settings
|
||||||
APP_ARGS=`save "$@"`
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
@@ -6,17 +6,18 @@ dependencies {
|
|||||||
implementation(project(':jadx-core'))
|
implementation(project(':jadx-core'))
|
||||||
|
|
||||||
runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
||||||
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
runtimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||||
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||||
|
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||||
|
|
||||||
implementation 'com.beust:jcommander:1.80'
|
implementation 'com.beust:jcommander:1.82'
|
||||||
implementation 'ch.qos.logback:logback-classic:1.2.3'
|
implementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
applicationName = 'jadx'
|
applicationName = 'jadx'
|
||||||
mainClassName = 'jadx.cli.JadxCLI'
|
mainClass.set('jadx.cli.JadxCLI')
|
||||||
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
|
applicationDefaultJvmArgs = ['-Xms128M', '-XX:MaxRAMPercentage=70.0', '-XX:+UseG1GC']
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationDistribution.with {
|
applicationDistribution.with {
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import java.lang.reflect.Field;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
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;
|
||||||
|
|
||||||
@@ -17,6 +18,12 @@ import com.beust.jcommander.ParameterException;
|
|||||||
import com.beust.jcommander.Parameterized;
|
import com.beust.jcommander.Parameterized;
|
||||||
|
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.api.plugins.JadxPlugin;
|
||||||
|
import jadx.api.plugins.JadxPluginInfo;
|
||||||
|
import jadx.api.plugins.JadxPluginManager;
|
||||||
|
import jadx.api.plugins.options.JadxPluginOptions;
|
||||||
|
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;
|
||||||
@@ -45,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;
|
||||||
@@ -70,36 +89,51 @@ public class JCommanderWrapper<T> {
|
|||||||
maxNamesLen = len;
|
maxNamesLen = len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
maxNamesLen += 3;
|
||||||
|
|
||||||
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
|
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
|
||||||
for (Field f : getFields(args.getClass())) {
|
for (Field f : getFields(args.getClass())) {
|
||||||
String name = f.getName();
|
String name = f.getName();
|
||||||
ParameterDescription p = paramsMap.get(name);
|
ParameterDescription p = paramsMap.get(name);
|
||||||
if (p == null) {
|
if (p == null || p.getParameter().hidden()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
StringBuilder opt = new StringBuilder();
|
StringBuilder opt = new StringBuilder();
|
||||||
opt.append(" ").append(p.getNames());
|
opt.append(" ").append(p.getNames());
|
||||||
addSpaces(opt, maxNamesLen - opt.length() + 3);
|
String description = p.getDescription();
|
||||||
opt.append("- ").append(p.getDescription());
|
addSpaces(opt, maxNamesLen - opt.length());
|
||||||
|
if (description.contains("\n")) {
|
||||||
|
String[] lines = description.split("\n");
|
||||||
|
opt.append("- ").append(lines[0]);
|
||||||
|
for (int i = 1; i < lines.length; i++) {
|
||||||
|
opt.append('\n');
|
||||||
|
addSpaces(opt, maxNamesLen + 2);
|
||||||
|
opt.append(lines[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opt.append("- ").append(description);
|
||||||
|
}
|
||||||
String defaultValue = getDefaultValue(args, f, opt);
|
String defaultValue = getDefaultValue(args, f, opt);
|
||||||
if (defaultValue != null) {
|
if (defaultValue != null && !description.contains("(default)")) {
|
||||||
opt.append(", default: ").append(defaultValue);
|
opt.append(", default: ").append(defaultValue);
|
||||||
}
|
}
|
||||||
out.println(opt);
|
out.println(opt);
|
||||||
}
|
}
|
||||||
out.println("Example:");
|
out.println(appendPluginOptions(maxNamesLen));
|
||||||
|
out.println();
|
||||||
|
out.println("Examples:");
|
||||||
out.println(" jadx -d out classes.dex");
|
out.println(" jadx -d out classes.dex");
|
||||||
|
out.println(" jadx --rename-flags \"none\" classes.dex");
|
||||||
|
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
|
||||||
|
out.println(" jadx --log-level ERROR app.apk");
|
||||||
|
out.println(" jadx -Pdex-input.verify-checksum=no app.apk");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all declared fields of the specified class and all super classes
|
* Get all declared fields of the specified class and all super classes
|
||||||
*
|
|
||||||
* @param clazz
|
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
private List<Field> getFields(Class<?> clazz) {
|
private List<Field> getFields(Class<?> clazz) {
|
||||||
List<Field> fieldList = new LinkedList<>();
|
List<Field> fieldList = new ArrayList<>();
|
||||||
while (clazz != null) {
|
while (clazz != null) {
|
||||||
fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
|
fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
|
||||||
clazz = clazz.getSuperclass();
|
clazz = clazz.getSuperclass();
|
||||||
@@ -120,7 +154,7 @@ public class JCommanderWrapper<T> {
|
|||||||
if (Enum.class.isAssignableFrom(fieldType)) {
|
if (Enum.class.isAssignableFrom(fieldType)) {
|
||||||
Enum<?> val = (Enum<?>) f.get(args);
|
Enum<?> val = (Enum<?>) f.get(args);
|
||||||
if (val != null) {
|
if (val != null) {
|
||||||
return val.name();
|
return val.name().toLowerCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -134,4 +168,46 @@ public class JCommanderWrapper<T> {
|
|||||||
str.append(' ');
|
str.append(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String appendPluginOptions(int maxNamesLen) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
JadxPluginManager pluginManager = new JadxPluginManager();
|
||||||
|
pluginManager.load();
|
||||||
|
int k = 1;
|
||||||
|
for (JadxPlugin plugin : pluginManager.getAllPlugins()) {
|
||||||
|
if (plugin instanceof JadxPluginOptions) {
|
||||||
|
if (appendPlugin(((JadxPluginOptions) plugin), sb, maxNamesLen, k)) {
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sb.length() == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return "\nPlugin options (-P<name>=<value>):" + sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean appendPlugin(JadxPluginOptions plugin, StringBuilder out, int maxNamesLen, int k) {
|
||||||
|
List<OptionDescription> descs = plugin.getOptionsDescriptions();
|
||||||
|
if (descs.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
JadxPluginInfo pluginInfo = plugin.getPluginInfo();
|
||||||
|
out.append("\n ").append(k).append(") ");
|
||||||
|
out.append(pluginInfo.getPluginId()).append(": ").append(pluginInfo.getDescription());
|
||||||
|
for (OptionDescription desc : descs) {
|
||||||
|
StringBuilder opt = new StringBuilder();
|
||||||
|
opt.append(" - ").append(desc.name());
|
||||||
|
addSpaces(opt, maxNamesLen - opt.length());
|
||||||
|
opt.append("- ").append(desc.description());
|
||||||
|
if (!desc.values().isEmpty()) {
|
||||||
|
opt.append(", values: ").append(desc.values());
|
||||||
|
}
|
||||||
|
if (desc.defaultValue() != null) {
|
||||||
|
opt.append(", default: ").append(desc.defaultValue());
|
||||||
|
}
|
||||||
|
out.append("\n").append(opt);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.impl.NoOpCodeCache;
|
import jadx.api.impl.NoOpCodeCache;
|
||||||
|
import jadx.api.impl.SimpleCodeWriter;
|
||||||
|
import jadx.cli.LogHelper.LogLevelEnum;
|
||||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
@@ -19,8 +21,8 @@ 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("jadx error: {}", e.getMessage(), e);
|
LOG.error("Process error:", e);
|
||||||
result = 1;
|
result = 1;
|
||||||
} finally {
|
} finally {
|
||||||
FileUtils.deleteTempRootDir();
|
FileUtils.deleteTempRootDir();
|
||||||
@@ -31,16 +33,26 @@ public class JadxCLI {
|
|||||||
public static int execute(String[] args) {
|
public static int execute(String[] args) {
|
||||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||||
if (jadxArgs.processArgs(args)) {
|
if (jadxArgs.processArgs(args)) {
|
||||||
return processAndSave(jadxArgs.toJadxArgs());
|
return processAndSave(jadxArgs);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int processAndSave(JadxArgs jadxArgs) {
|
private static int processAndSave(JadxCLIArgs cliArgs) {
|
||||||
|
LogHelper.initLogLevel(cliArgs);
|
||||||
|
LogHelper.setLogLevelsForLoadingStage();
|
||||||
|
JadxArgs jadxArgs = cliArgs.toJadxArgs();
|
||||||
jadxArgs.setCodeCache(new NoOpCodeCache());
|
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||||
|
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
|
||||||
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
||||||
jadx.load();
|
jadx.load();
|
||||||
jadx.save();
|
if (checkForErrors(jadx)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
LogHelper.setLogLevelsForDecompileStage();
|
||||||
|
if (!SingleClassMode.process(jadx, cliArgs)) {
|
||||||
|
save(jadx);
|
||||||
|
}
|
||||||
int errorsCount = jadx.getErrorsCount();
|
int errorsCount = jadx.getErrorsCount();
|
||||||
if (errorsCount != 0) {
|
if (errorsCount != 0) {
|
||||||
jadx.printErrorsReport();
|
jadx.printErrorsReport();
|
||||||
@@ -51,4 +63,36 @@ public class JadxCLI {
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean checkForErrors(JadxDecompiler jadx) {
|
||||||
|
if (jadx.getRoot().getClasses().isEmpty()) {
|
||||||
|
if (jadx.getArgs().isSkipResources()) {
|
||||||
|
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) {
|
||||||
|
LOG.error("Load with errors! Check log for details");
|
||||||
|
// continue processing
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void save(JadxDecompiler jadx) {
|
||||||
|
if (LogHelper.getLogLevel() == LogLevelEnum.QUIET) {
|
||||||
|
jadx.save();
|
||||||
|
} else {
|
||||||
|
jadx.save(500, (done, total) -> {
|
||||||
|
int progress = (int) (done * 100.0 / total);
|
||||||
|
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
|
||||||
|
});
|
||||||
|
// dumb line clear :)
|
||||||
|
System.out.print(" \r");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,31 @@ package jadx.cli;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.beust.jcommander.DynamicParameter;
|
||||||
import com.beust.jcommander.IStringConverter;
|
import com.beust.jcommander.IStringConverter;
|
||||||
import com.beust.jcommander.Parameter;
|
import com.beust.jcommander.Parameter;
|
||||||
|
|
||||||
|
import jadx.api.CommentsLevel;
|
||||||
|
import jadx.api.DecompilationMode;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.JadxArgs.RenameEnum;
|
import jadx.api.JadxArgs.RenameEnum;
|
||||||
|
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
public class JadxCLIArgs {
|
public class JadxCLIArgs {
|
||||||
|
|
||||||
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)")
|
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab)")
|
||||||
protected List<String> files = new ArrayList<>(1);
|
protected List<String> files = new ArrayList<>(1);
|
||||||
|
|
||||||
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
||||||
@@ -37,9 +44,12 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
|
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
|
||||||
protected boolean skipSources = false;
|
protected boolean skipSources = false;
|
||||||
|
|
||||||
@Parameter(names = { "--single-class" }, description = "decompile a single class")
|
@Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
|
||||||
protected String singleClass = null;
|
protected String singleClass = null;
|
||||||
|
|
||||||
|
@Parameter(names = { "--single-class-output" }, description = "file or dir for write if decompile a single class")
|
||||||
|
protected String singleClassOutput = null;
|
||||||
|
|
||||||
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
|
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
|
||||||
protected String outputFormat = "java";
|
protected String outputFormat = "java";
|
||||||
|
|
||||||
@@ -49,6 +59,17 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
||||||
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "-m", "--decompilation-mode" },
|
||||||
|
description = "code output mode:"
|
||||||
|
+ "\n 'auto' - trying best options (default)"
|
||||||
|
+ "\n 'restructure' - restore code structure (normal java code)"
|
||||||
|
+ "\n 'simple' - simplified instructions (linear, with goto's)"
|
||||||
|
+ "\n 'fallback' - raw instructions without modifications",
|
||||||
|
converter = DecompilationModeConverter.class
|
||||||
|
)
|
||||||
|
protected DecompilationMode decompilationMode = DecompilationMode.AUTO;
|
||||||
|
|
||||||
@Parameter(names = { "--show-bad-code" }, description = "show inconsistent code (incorrectly decompiled)")
|
@Parameter(names = { "--show-bad-code" }, description = "show inconsistent code (incorrectly decompiled)")
|
||||||
protected boolean showInconsistentCode = false;
|
protected boolean showInconsistentCode = false;
|
||||||
|
|
||||||
@@ -58,9 +79,15 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "--no-debug-info" }, description = "disable debug info")
|
@Parameter(names = { "--no-debug-info" }, description = "disable debug info")
|
||||||
protected boolean debugInfo = true;
|
protected boolean debugInfo = true;
|
||||||
|
|
||||||
|
@Parameter(names = { "--add-debug-lines" }, description = "add comments with debug line numbers if available")
|
||||||
|
protected boolean addDebugLines = false;
|
||||||
|
|
||||||
@Parameter(names = { "--no-inline-anonymous" }, description = "disable anonymous classes inline")
|
@Parameter(names = { "--no-inline-anonymous" }, description = "disable anonymous classes inline")
|
||||||
protected boolean inlineAnonymousClasses = true;
|
protected boolean inlineAnonymousClasses = true;
|
||||||
|
|
||||||
|
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
|
||||||
|
protected boolean inlineMethods = 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;
|
||||||
|
|
||||||
@@ -79,8 +106,22 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer")
|
@Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer")
|
||||||
protected int deobfuscationMaxLength = 64;
|
protected int deobfuscationMaxLength = 64;
|
||||||
|
|
||||||
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "force to save deobfuscation map")
|
@Parameter(
|
||||||
protected boolean deobfuscationForceSave = false;
|
names = { "--deobf-cfg-file" },
|
||||||
|
description = "deobfuscation map file, default: same dir and name as input file with '.jobf' extension"
|
||||||
|
)
|
||||||
|
protected String deobfuscationMapFile;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--deobf-cfg-file-mode" },
|
||||||
|
description = "set mode for handle deobfuscation map file:"
|
||||||
|
+ "\n 'read' - read if found, don't save (default)"
|
||||||
|
+ "\n 'read-or-save' - read if found, save otherwise (don't overwrite)"
|
||||||
|
+ "\n 'overwrite' - don't read, always save"
|
||||||
|
+ "\n 'ignore' - don't read and don't save",
|
||||||
|
converter = DeobfuscationMapFileModeConverter.class
|
||||||
|
)
|
||||||
|
protected DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||||
|
|
||||||
@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;
|
||||||
@@ -88,13 +129,21 @@ public class JadxCLIArgs {
|
|||||||
@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 = { "--use-kotlin-methods-for-var-names" },
|
||||||
|
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
|
||||||
|
converter = UseKotlinMethodsForVarNamesConverter.class
|
||||||
|
)
|
||||||
|
protected UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
|
||||||
|
|
||||||
@Parameter(
|
@Parameter(
|
||||||
names = { "--rename-flags" },
|
names = { "--rename-flags" },
|
||||||
description = "what to rename, comma-separated,"
|
description = "fix options (comma-separated list of):"
|
||||||
+ " 'case' for system case sensitivity,"
|
+ "\n 'case' - fix case sensitivity issues (according to --fs-case-sensitive option),"
|
||||||
+ " 'valid' for java identifiers,"
|
+ "\n 'valid' - rename java identifiers to make them valid,"
|
||||||
+ " 'printable' characters,"
|
+ "\n 'printable' - remove non-printable chars from identifiers,"
|
||||||
+ " 'none' or 'all' (default)",
|
+ "\nor single 'none' - to disable all renames"
|
||||||
|
+ "\nor single 'all' - to enable all (default)",
|
||||||
converter = RenameConverter.class
|
converter = RenameConverter.class
|
||||||
)
|
)
|
||||||
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
|
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
|
||||||
@@ -108,28 +157,41 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
|
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
|
||||||
protected boolean rawCfgOutput = false;
|
protected boolean rawCfgOutput = false;
|
||||||
|
|
||||||
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
|
@Parameter(names = { "-f", "--fallback" }, description = "set '--decompilation-mode' to 'fallback' (deprecated)")
|
||||||
protected boolean fallbackMode = false;
|
protected boolean fallbackMode = false;
|
||||||
|
|
||||||
|
@Parameter(names = { "--use-dx" }, description = "use dx/d8 to convert java bytecode")
|
||||||
|
protected boolean useDx = false;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--comments-level" },
|
||||||
|
description = "set code comments level, values: error, warn, info, debug, user-only, none",
|
||||||
|
converter = CommentsLevelConverter.class
|
||||||
|
)
|
||||||
|
protected CommentsLevel commentsLevel = CommentsLevel.INFO;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--log-level" },
|
||||||
|
description = "set log level, values: quiet, progress, error, warn, info, debug",
|
||||||
|
converter = LogHelper.LogLevelConverter.class
|
||||||
|
)
|
||||||
|
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
|
||||||
|
|
||||||
@Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)")
|
@Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)")
|
||||||
protected boolean verbose = false;
|
protected boolean verbose = false;
|
||||||
|
|
||||||
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
|
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
|
||||||
protected boolean quiet = false;
|
protected boolean quiet = false;
|
||||||
|
|
||||||
@Parameter(
|
|
||||||
names = { "--log-level" },
|
|
||||||
description = "set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG",
|
|
||||||
converter = LogHelper.LogLevelConverter.class
|
|
||||||
)
|
|
||||||
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
|
|
||||||
|
|
||||||
@Parameter(names = { "--version" }, description = "print jadx version")
|
@Parameter(names = { "--version" }, description = "print jadx version")
|
||||||
protected boolean printVersion = false;
|
protected boolean printVersion = false;
|
||||||
|
|
||||||
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
||||||
protected boolean printHelp = false;
|
protected boolean printHelp = false;
|
||||||
|
|
||||||
|
@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
|
||||||
|
protected Map<String, String> pluginOptions = new HashMap<>();
|
||||||
|
|
||||||
public boolean processArgs(String[] args) {
|
public boolean processArgs(String[] args) {
|
||||||
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
|
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
|
||||||
return jcw.parse(args) && process(jcw);
|
return jcw.parse(args) && process(jcw);
|
||||||
@@ -165,7 +227,6 @@ public class JadxCLIArgs {
|
|||||||
if (threadsCount <= 0) {
|
if (threadsCount <= 0) {
|
||||||
throw new JadxException("Threads count must be positive, got: " + threadsCount);
|
throw new JadxException("Threads count must be positive, got: " + threadsCount);
|
||||||
}
|
}
|
||||||
LogHelper.setLogLevelFromArgs(this);
|
|
||||||
} catch (JadxException e) {
|
} catch (JadxException e) {
|
||||||
System.err.println("ERROR: " + e.getMessage());
|
System.err.println("ERROR: " + e.getMessage());
|
||||||
jcw.printUsage();
|
jcw.printUsage();
|
||||||
@@ -183,31 +244,37 @@ public class JadxCLIArgs {
|
|||||||
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
|
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
|
||||||
args.setThreadsCount(threadsCount);
|
args.setThreadsCount(threadsCount);
|
||||||
args.setSkipSources(skipSources);
|
args.setSkipSources(skipSources);
|
||||||
if (singleClass != null) {
|
|
||||||
args.setClassFilter(className -> singleClass.equals(className));
|
|
||||||
}
|
|
||||||
args.setSkipResources(skipResources);
|
args.setSkipResources(skipResources);
|
||||||
args.setFallbackMode(fallbackMode);
|
if (fallbackMode) {
|
||||||
|
args.setDecompilationMode(DecompilationMode.FALLBACK);
|
||||||
|
} else {
|
||||||
|
args.setDecompilationMode(decompilationMode);
|
||||||
|
}
|
||||||
args.setShowInconsistentCode(showInconsistentCode);
|
args.setShowInconsistentCode(showInconsistentCode);
|
||||||
args.setCfgOutput(cfgOutput);
|
args.setCfgOutput(cfgOutput);
|
||||||
args.setRawCFGOutput(rawCfgOutput);
|
args.setRawCFGOutput(rawCfgOutput);
|
||||||
args.setReplaceConsts(replaceConsts);
|
args.setReplaceConsts(replaceConsts);
|
||||||
args.setDeobfuscationOn(deobfuscationOn);
|
args.setDeobfuscationOn(deobfuscationOn);
|
||||||
args.setDeobfuscationForceSave(deobfuscationForceSave);
|
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
|
||||||
|
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.setEscapeUnicode(escapeUnicode);
|
args.setEscapeUnicode(escapeUnicode);
|
||||||
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||||
args.setExportAsGradleProject(exportAsGradleProject);
|
args.setExportAsGradleProject(exportAsGradleProject);
|
||||||
args.setUseImports(useImports);
|
args.setUseImports(useImports);
|
||||||
args.setDebugInfo(debugInfo);
|
args.setDebugInfo(debugInfo);
|
||||||
|
args.setInsertDebugLines(addDebugLines);
|
||||||
args.setInlineAnonymousClasses(inlineAnonymousClasses);
|
args.setInlineAnonymousClasses(inlineAnonymousClasses);
|
||||||
args.setRenameCaseSensitive(isRenameCaseSensitive());
|
args.setInlineMethods(inlineMethods);
|
||||||
args.setRenameValid(isRenameValid());
|
args.setRenameFlags(renameFlags);
|
||||||
args.setRenamePrintable(isRenamePrintable());
|
|
||||||
args.setFsCaseSensitive(fsCaseSensitive);
|
args.setFsCaseSensitive(fsCaseSensitive);
|
||||||
|
args.setCommentsLevel(commentsLevel);
|
||||||
|
args.setUseDxInput(useDx);
|
||||||
|
args.setPluginOptions(pluginOptions);
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,6 +294,14 @@ public class JadxCLIArgs {
|
|||||||
return outDirRes;
|
return outDirRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSingleClass() {
|
||||||
|
return singleClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSingleClassOutput() {
|
||||||
|
return singleClassOutput;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSkipResources() {
|
public boolean isSkipResources() {
|
||||||
return skipResources;
|
return skipResources;
|
||||||
}
|
}
|
||||||
@@ -243,6 +318,14 @@ public class JadxCLIArgs {
|
|||||||
return fallbackMode;
|
return fallbackMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isUseDx() {
|
||||||
|
return useDx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DecompilationMode getDecompilationMode() {
|
||||||
|
return decompilationMode;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isShowInconsistentCode() {
|
public boolean isShowInconsistentCode() {
|
||||||
return showInconsistentCode;
|
return showInconsistentCode;
|
||||||
}
|
}
|
||||||
@@ -255,10 +338,18 @@ public class JadxCLIArgs {
|
|||||||
return debugInfo;
|
return debugInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAddDebugLines() {
|
||||||
|
return addDebugLines;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isInlineAnonymousClasses() {
|
public boolean isInlineAnonymousClasses() {
|
||||||
return inlineAnonymousClasses;
|
return inlineAnonymousClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInlineMethods() {
|
||||||
|
return inlineMethods;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDeobfuscationOn() {
|
public boolean isDeobfuscationOn() {
|
||||||
return deobfuscationOn;
|
return deobfuscationOn;
|
||||||
}
|
}
|
||||||
@@ -271,8 +362,12 @@ public class JadxCLIArgs {
|
|||||||
return deobfuscationMaxLength;
|
return deobfuscationMaxLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDeobfuscationForceSave() {
|
public String getDeobfuscationMapFile() {
|
||||||
return deobfuscationForceSave;
|
return deobfuscationMapFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
|
||||||
|
return deobfuscationMapFileMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDeobfuscationUseSourceNameAsAlias() {
|
public boolean isDeobfuscationUseSourceNameAsAlias() {
|
||||||
@@ -283,6 +378,10 @@ public class JadxCLIArgs {
|
|||||||
return deobfuscationParseKotlinMetadata;
|
return deobfuscationParseKotlinMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
||||||
|
return useKotlinMethodsForVarNames;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEscapeUnicode() {
|
public boolean isEscapeUnicode() {
|
||||||
return escapeUnicode;
|
return escapeUnicode;
|
||||||
}
|
}
|
||||||
@@ -323,6 +422,18 @@ public class JadxCLIArgs {
|
|||||||
return fsCaseSensitive;
|
return fsCaseSensitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommentsLevel getCommentsLevel() {
|
||||||
|
return commentsLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogHelper.LogLevelEnum getLogLevel() {
|
||||||
|
return logLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getPluginOptions() {
|
||||||
|
return pluginOptions;
|
||||||
|
}
|
||||||
|
|
||||||
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
||||||
private final String paramName;
|
private final String paramName;
|
||||||
|
|
||||||
@@ -341,7 +452,7 @@ public class JadxCLIArgs {
|
|||||||
Set<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
|
Set<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
|
||||||
for (String s : value.split(",")) {
|
for (String s : value.split(",")) {
|
||||||
try {
|
try {
|
||||||
set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT)));
|
set.add(RenameEnum.valueOf(s.trim().toUpperCase(Locale.ROOT)));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
'\'' + s + "' is unknown for parameter " + paramName
|
'\'' + s + "' is unknown for parameter " + paramName
|
||||||
@@ -352,9 +463,61 @@ public class JadxCLIArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class CommentsLevelConverter implements IStringConverter<CommentsLevel> {
|
||||||
|
@Override
|
||||||
|
public CommentsLevel convert(String value) {
|
||||||
|
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> {
|
||||||
|
@Override
|
||||||
|
public UseKotlinMethodsForVarNames convert(String value) {
|
||||||
|
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> {
|
||||||
|
@Override
|
||||||
|
public DeobfuscationMapFileMode convert(String value) {
|
||||||
|
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> {
|
||||||
|
@Override
|
||||||
|
public DecompilationMode convert(String value) {
|
||||||
|
try {
|
||||||
|
return DecompilationMode.valueOf(value.toUpperCase());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
'\'' + value + "' is unknown, possible values are: "
|
||||||
|
+ JadxCLIArgs.enumValuesString(DecompilationMode.values()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static String enumValuesString(Enum<?>[] values) {
|
public static String enumValuesString(Enum<?>[] values) {
|
||||||
return Stream.of(values)
|
return Stream.of(values)
|
||||||
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
|
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
|
||||||
.collect(Collectors.joining(", "));
|
.collect(Collectors.joining(", "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package jadx.cli;
|
package jadx.cli;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.beust.jcommander.IStringConverter;
|
import com.beust.jcommander.IStringConverter;
|
||||||
@@ -31,35 +33,76 @@ public class LogHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setLogLevelFromArgs(JadxCLIArgs args) {
|
@Nullable("For disable log level control")
|
||||||
|
private static LogLevelEnum logLevelValue;
|
||||||
|
|
||||||
|
public static void initLogLevel(JadxCLIArgs args) {
|
||||||
|
logLevelValue = getLogLevelFromArgs(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LogLevelEnum getLogLevelFromArgs(JadxCLIArgs args) {
|
||||||
if (isCustomLogConfig()) {
|
if (isCustomLogConfig()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (args.quiet) {
|
||||||
|
return LogLevelEnum.QUIET;
|
||||||
|
}
|
||||||
|
if (args.verbose) {
|
||||||
|
return LogLevelEnum.DEBUG;
|
||||||
|
}
|
||||||
|
return args.logLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLogLevelsForLoadingStage() {
|
||||||
|
if (logLevelValue == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LogLevelEnum logLevel = args.logLevel;
|
if (logLevelValue == LogLevelEnum.PROGRESS) {
|
||||||
if (args.quiet) {
|
// show load errors
|
||||||
logLevel = LogLevelEnum.QUIET;
|
LogHelper.applyLogLevel(LogLevelEnum.ERROR);
|
||||||
} else if (args.verbose) {
|
fixForShowProgress();
|
||||||
logLevel = LogLevelEnum.DEBUG;
|
return;
|
||||||
}
|
}
|
||||||
|
applyLogLevel(logLevelValue);
|
||||||
applyLogLevel(logLevel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void applyLogLevel(LogLevelEnum logLevel) {
|
public static void setLogLevelsForDecompileStage() {
|
||||||
|
if (logLevelValue == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyLogLevel(logLevelValue);
|
||||||
|
if (logLevelValue == LogLevelEnum.PROGRESS) {
|
||||||
|
fixForShowProgress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show progress: change to 'INFO' for control classes
|
||||||
|
*/
|
||||||
|
private static void fixForShowProgress() {
|
||||||
|
setLevelForClass(JadxCLI.class, Level.INFO);
|
||||||
|
setLevelForClass(JadxDecompiler.class, Level.INFO);
|
||||||
|
setLevelForClass(SingleClassMode.class, Level.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyLogLevel(@NotNull LogLevelEnum logLevel) {
|
||||||
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||||
rootLogger.setLevel(logLevel.getLevel());
|
rootLogger.setLevel(logLevel.getLevel());
|
||||||
|
|
||||||
if (logLevel != LogLevelEnum.QUIET) {
|
|
||||||
// show progress for all levels except quiet
|
|
||||||
setLevelForClass(JadxCLI.class, Level.INFO);
|
|
||||||
setLevelForClass(JadxDecompiler.class, Level.INFO);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setLevelForClass(Class<?> cls, Level level) {
|
@Nullable
|
||||||
|
public static LogLevelEnum getLogLevel() {
|
||||||
|
return logLevelValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLevelForClass(Class<?> cls, Level level) {
|
||||||
((Logger) LoggerFactory.getLogger(cls)).setLevel(level);
|
((Logger) LoggerFactory.getLogger(cls)).setLevel(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setLevelForPackage(String pkgName, Level level) {
|
||||||
|
((Logger) LoggerFactory.getLogger(pkgName)).setLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to detect if user provide custom logback config via -Dlogback.configurationFile=
|
* Try to detect if user provide custom logback config via -Dlogback.configurationFile=
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package jadx.cli;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.JadxDecompiler;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.visitors.SaveCode;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
|
public class SingleClassMode {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SingleClassMode.class);
|
||||||
|
|
||||||
|
public static boolean process(JadxDecompiler jadx, JadxCLIArgs cliArgs) {
|
||||||
|
String singleClass = cliArgs.getSingleClass();
|
||||||
|
String singleClassOutput = cliArgs.getSingleClassOutput();
|
||||||
|
if (singleClass == null && singleClassOutput == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ClassNode clsForProcess;
|
||||||
|
if (singleClass != null) {
|
||||||
|
clsForProcess = jadx.getRoot().resolveClass(singleClass);
|
||||||
|
if (clsForProcess == null) {
|
||||||
|
clsForProcess = jadx.getRoot().getClasses().stream()
|
||||||
|
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(singleClass))
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
if (clsForProcess == null) {
|
||||||
|
throw new JadxRuntimeException("Input class not found: " + singleClass);
|
||||||
|
}
|
||||||
|
if (clsForProcess.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
throw new JadxRuntimeException("Input class can't be saved by currect jadx settings (marked as DONT_GENERATE)");
|
||||||
|
}
|
||||||
|
if (clsForProcess.isInner()) {
|
||||||
|
clsForProcess = clsForProcess.getTopParentClass();
|
||||||
|
LOG.warn("Input class is inner, parent class will be saved: {}", clsForProcess.getFullName());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// singleClassOutput is set
|
||||||
|
// expect only one class to be loaded
|
||||||
|
List<ClassNode> classes = jadx.getRoot().getClasses().stream()
|
||||||
|
.filter(c -> !c.isInner() && !c.contains(AFlag.DONT_GENERATE))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
int size = classes.size();
|
||||||
|
if (size == 1) {
|
||||||
|
clsForProcess = classes.get(0);
|
||||||
|
} else {
|
||||||
|
throw new JadxRuntimeException("Found " + size + " classes, single class output can't be used");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ICodeInfo codeInfo;
|
||||||
|
try {
|
||||||
|
codeInfo = clsForProcess.decompile();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Class decompilation failed", e);
|
||||||
|
}
|
||||||
|
String fileExt = SaveCode.getFileExtension(jadx.getRoot());
|
||||||
|
File out;
|
||||||
|
if (singleClassOutput == null) {
|
||||||
|
out = new File(jadx.getArgs().getOutDirSrc(), clsForProcess.getClassInfo().getAliasFullPath() + fileExt);
|
||||||
|
} else {
|
||||||
|
if (singleClassOutput.endsWith(fileExt)) {
|
||||||
|
// treat as file name
|
||||||
|
out = new File(singleClassOutput);
|
||||||
|
} else {
|
||||||
|
// treat as directory
|
||||||
|
out = new File(singleClassOutput, clsForProcess.getShortName() + fileExt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File resultOut = FileUtils.prepareFile(out);
|
||||||
|
if (clsForProcess.getClassInfo().hasAlias()) {
|
||||||
|
LOG.info("Saving class '{}' (alias: '{}') to file '{}'",
|
||||||
|
clsForProcess.getClassInfo().getFullName(), clsForProcess.getFullName(), resultOut.getAbsolutePath());
|
||||||
|
} else {
|
||||||
|
LOG.info("Saving class '{}' to file '{}'", clsForProcess.getFullName(), resultOut.getAbsolutePath());
|
||||||
|
}
|
||||||
|
SaveCode.save(codeInfo.getCodeStr(), resultOut);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package jadx.cli.clst;
|
package jadx.cli.clst;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -17,7 +16,9 @@ import jadx.api.plugins.JadxPluginManager;
|
|||||||
import jadx.api.plugins.input.JadxInputPlugin;
|
import jadx.api.plugins.input.JadxInputPlugin;
|
||||||
import jadx.api.plugins.input.data.ILoadResult;
|
import jadx.api.plugins.input.data.ILoadResult;
|
||||||
import jadx.core.clsp.ClsSet;
|
import jadx.core.clsp.ClsSet;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.dex.visitors.SignatureProcessor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for convert dex or jar to jadx classes set (.jcst)
|
* Utility class for convert dex or jar to jadx classes set (.jcst)
|
||||||
@@ -29,7 +30,7 @@ public class ConvertToClsSet {
|
|||||||
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
|
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws Exception {
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
usage();
|
usage();
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
@@ -38,6 +39,7 @@ public class ConvertToClsSet {
|
|||||||
Path output = inputPaths.remove(0);
|
Path output = inputPaths.remove(0);
|
||||||
|
|
||||||
JadxPluginManager pluginManager = new JadxPluginManager();
|
JadxPluginManager pluginManager = new JadxPluginManager();
|
||||||
|
pluginManager.load();
|
||||||
List<ILoadResult> loadedInputs = new ArrayList<>();
|
List<ILoadResult> loadedInputs = new ArrayList<>();
|
||||||
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||||
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
|
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
|
||||||
@@ -48,11 +50,18 @@ public class ConvertToClsSet {
|
|||||||
RootNode root = new RootNode(jadxArgs);
|
RootNode root = new RootNode(jadxArgs);
|
||||||
root.loadClasses(loadedInputs);
|
root.loadClasses(loadedInputs);
|
||||||
|
|
||||||
|
// from pre-decompilation stage run only SignatureProcessor
|
||||||
|
SignatureProcessor signatureProcessor = new SignatureProcessor();
|
||||||
|
signatureProcessor.init(root);
|
||||||
|
for (ClassNode classNode : root.getClasses()) {
|
||||||
|
signatureProcessor.visit(classNode);
|
||||||
|
}
|
||||||
|
|
||||||
ClsSet set = new ClsSet(root);
|
ClsSet set = new ClsSet(root);
|
||||||
set.loadFrom(root);
|
set.loadFrom(root);
|
||||||
set.save(output);
|
set.save(output);
|
||||||
|
|
||||||
LOG.info("Output: {}, file size: {}B", output, output.toFile().length());
|
LOG.info("Output: {}", output);
|
||||||
LOG.info("done");
|
LOG.info("done");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package jadx.cli.tools;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.android.TextResMapFile;
|
||||||
|
import jadx.core.xmlgen.ResTableParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for convert '.arsc' to simple text file with mapping id to resource name
|
||||||
|
*/
|
||||||
|
public class ConvertArscFile {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ConvertArscFile.class);
|
||||||
|
private static int rewritesCount;
|
||||||
|
|
||||||
|
public static void usage() {
|
||||||
|
LOG.info("<res-map file> <input .arsc files>");
|
||||||
|
LOG.info("");
|
||||||
|
LOG.info("Note: If res-map already exists - it will be merged and updated");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
if (args.length < 2) {
|
||||||
|
usage();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
|
||||||
|
Path resMapFile = inputPaths.remove(0);
|
||||||
|
Map<Integer, String> resMap;
|
||||||
|
if (Files.isReadable(resMapFile)) {
|
||||||
|
resMap = TextResMapFile.read(resMapFile);
|
||||||
|
} else {
|
||||||
|
resMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
LOG.info("Input entries count: {}", resMap.size());
|
||||||
|
|
||||||
|
RootNode root = new RootNode(new JadxArgs()); // not really needed
|
||||||
|
rewritesCount = 0;
|
||||||
|
for (Path resFile : inputPaths) {
|
||||||
|
LOG.info("Processing {}", resFile);
|
||||||
|
ResTableParser resTableParser = new ResTableParser(root, true);
|
||||||
|
if (resFile.getFileName().toString().endsWith(".jar")) {
|
||||||
|
// Load resources.arsc from android.jar
|
||||||
|
try (ZipFile zip = new ZipFile(resFile.toFile())) {
|
||||||
|
ZipEntry entry = zip.getEntry("resources.arsc");
|
||||||
|
if (entry == null) {
|
||||||
|
LOG.error("Failed to load \"resources.arsc\" from {}", resFile);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try (InputStream inputStream = zip.getInputStream(entry)) {
|
||||||
|
resTableParser.decode(inputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Load resources.arsc from extracted file
|
||||||
|
try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(resFile))) {
|
||||||
|
resTableParser.decode(inputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<Integer, String> singleResMap = resTableParser.getResStorage().getResourcesNames();
|
||||||
|
mergeResMaps(resMap, singleResMap);
|
||||||
|
LOG.info("{} entries count: {}, after merge: {}", resFile.getFileName(), singleResMap.size(), resMap.size());
|
||||||
|
}
|
||||||
|
LOG.info("Output entries count: {}", resMap.size());
|
||||||
|
LOG.info("Total rewrites count: {}", rewritesCount);
|
||||||
|
TextResMapFile.write(resMapFile, resMap);
|
||||||
|
LOG.info("Result file size: {} B", resMapFile.toFile().length());
|
||||||
|
LOG.info("done");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void mergeResMaps(Map<Integer, String> mainResMap, Map<Integer, String> newResMap) {
|
||||||
|
for (Map.Entry<Integer, String> entry : newResMap.entrySet()) {
|
||||||
|
Integer id = entry.getKey();
|
||||||
|
String name = entry.getValue();
|
||||||
|
String prevName = mainResMap.put(id, name);
|
||||||
|
if (prevName != null && !name.equals(prevName)) {
|
||||||
|
LOG.debug("Rewrite id: {} from: '{}' to: '{}'", Integer.toHexString(id), prevName, name);
|
||||||
|
rewritesCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ public class RenameConverterTest {
|
|||||||
() -> converter.convert("wrong"),
|
() -> converter.convert("wrong"),
|
||||||
"Expected convert() to throw, but it didn't");
|
"Expected convert() to throw, but it didn't");
|
||||||
|
|
||||||
assertEquals("'wrong' is unknown for parameter someParam, "
|
assertEquals("'wrong' is unknown for parameter someParam, possible values are case, valid, printable",
|
||||||
+ "possible values are 'case', 'valid', 'printable'",
|
|
||||||
thrown.getMessage());
|
thrown.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.LinkOption;
|
import java.nio.file.LinkOption;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.PathMatcher;
|
import java.nio.file.PathMatcher;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@@ -43,20 +44,48 @@ public class TestInput {
|
|||||||
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
|
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
|
@Test
|
||||||
StringBuilder args = new StringBuilder();
|
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);
|
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
||||||
args.append("-v");
|
args.add("-v");
|
||||||
args.append(" -d ").append(tempDir.toAbsolutePath());
|
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 {
|
||||||
|
List<String> args = new ArrayList<>();
|
||||||
|
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
||||||
|
args.add("-v");
|
||||||
|
args.add("-d");
|
||||||
|
args.add(tempDir.toAbsolutePath().toString());
|
||||||
|
|
||||||
for (String inputSample : inputSamples) {
|
for (String inputSample : inputSamples) {
|
||||||
URL resource = getClass().getClassLoader().getResource(inputSample);
|
URL resource = getClass().getClassLoader().getResource(inputSample);
|
||||||
assertThat(resource).isNotNull();
|
assertThat(resource).isNotNull();
|
||||||
String sampleFile = resource.toURI().getRawPath();
|
String sampleFile = resource.toURI().getRawPath();
|
||||||
args.append(' ').append(sampleFile);
|
args.add(sampleFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
int result = JadxCLI.execute(args.toString().split(" "));
|
int result = JadxCLI.execute(args.toArray(new String[0]));
|
||||||
assertThat(result).isEqualTo(0);
|
assertThat(result).isEqualTo(0);
|
||||||
List<Path> resultJavaFiles = collectJavaFilesInDir(tempDir);
|
List<Path> resultJavaFiles = collectJavaFilesInDir(tempDir);
|
||||||
assertThat(resultJavaFiles).isNotEmpty();
|
assertThat(resultJavaFiles).isNotEmpty();
|
||||||
|
|||||||
Binary file not shown.
+14
-7
@@ -1,20 +1,27 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java-library'
|
id 'jadx-library'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
runtimeOnly files('clsp-data/android-29-clst.jar')
|
|
||||||
runtimeOnly files('clsp-data/android-29-res.jar')
|
|
||||||
|
|
||||||
api(project(':jadx-plugins:jadx-plugins-api'))
|
api(project(':jadx-plugins:jadx-plugins-api'))
|
||||||
|
|
||||||
implementation 'com.google.code.gson:gson:2.8.6'
|
implementation 'com.google.code.gson:gson:2.9.0'
|
||||||
|
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
|
||||||
|
constraints {
|
||||||
|
// Force protobuf version to prevent Java-7 issue
|
||||||
|
implementation 'com.google.protobuf:protobuf-java:3.11.4'
|
||||||
|
}
|
||||||
|
|
||||||
testImplementation 'org.apache.commons:commons-lang3:3.11'
|
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-raung-input'))
|
||||||
|
|
||||||
|
testImplementation 'org.eclipse.jdt:ecj:3.29.0'
|
||||||
|
testImplementation 'tools.profiler:async-profiler:1.8.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,70 +0,0 @@
|
|||||||
package jadx.api;
|
|
||||||
|
|
||||||
public final class CodePosition {
|
|
||||||
|
|
||||||
private final JavaNode node;
|
|
||||||
private final int line;
|
|
||||||
private final int offset;
|
|
||||||
|
|
||||||
public CodePosition(JavaNode node, int line, int offset) {
|
|
||||||
this.node = node;
|
|
||||||
this.line = line;
|
|
||||||
this.offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CodePosition(int line, int offset) {
|
|
||||||
this.node = null;
|
|
||||||
this.line = line;
|
|
||||||
this.offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JavaNode getNode() {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JavaClass getJavaClass() {
|
|
||||||
JavaClass parent = node.getDeclaringClass();
|
|
||||||
if (parent == null && node instanceof JavaClass) {
|
|
||||||
return (JavaClass) node;
|
|
||||||
}
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (node != null) {
|
|
||||||
sb.append(' ').append(node);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
public enum CommentsLevel {
|
||||||
|
NONE,
|
||||||
|
USER_ONLY,
|
||||||
|
ERROR,
|
||||||
|
WARN,
|
||||||
|
INFO,
|
||||||
|
DEBUG;
|
||||||
|
|
||||||
|
public boolean filter(CommentsLevel limit) {
|
||||||
|
return this.ordinal() <= limit.ordinal();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
public enum DecompilationMode {
|
||||||
|
/**
|
||||||
|
* Trying best options (default)
|
||||||
|
*/
|
||||||
|
AUTO,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore code structure (normal java code)
|
||||||
|
*/
|
||||||
|
RESTRUCTURE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplified instructions (linear with goto's)
|
||||||
|
*/
|
||||||
|
SIMPLE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw instructions without modifications
|
||||||
|
*/
|
||||||
|
FALLBACK
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
|
||||||
|
public interface ICodeWriter {
|
||||||
|
String NL = System.getProperty("line.separator");
|
||||||
|
String INDENT_STR = " ";
|
||||||
|
|
||||||
|
boolean isMetadataSupported();
|
||||||
|
|
||||||
|
ICodeWriter startLine();
|
||||||
|
|
||||||
|
ICodeWriter startLine(char c);
|
||||||
|
|
||||||
|
ICodeWriter startLine(String str);
|
||||||
|
|
||||||
|
ICodeWriter startLineWithNum(int sourceLine);
|
||||||
|
|
||||||
|
ICodeWriter addMultiLine(String str);
|
||||||
|
|
||||||
|
ICodeWriter add(String str);
|
||||||
|
|
||||||
|
ICodeWriter add(char c);
|
||||||
|
|
||||||
|
ICodeWriter add(ICodeWriter code);
|
||||||
|
|
||||||
|
ICodeWriter newLine();
|
||||||
|
|
||||||
|
ICodeWriter addIndent();
|
||||||
|
|
||||||
|
void incIndent();
|
||||||
|
|
||||||
|
void decIndent();
|
||||||
|
|
||||||
|
int getIndent();
|
||||||
|
|
||||||
|
void setIndent(int indent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return current line (only if metadata is supported)
|
||||||
|
*/
|
||||||
|
int getLine();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return start line position (only if metadata is supported)
|
||||||
|
*/
|
||||||
|
int getLineStartPos();
|
||||||
|
|
||||||
|
void attachDefinition(ICodeNodeRef obj);
|
||||||
|
|
||||||
|
void attachAnnotation(ICodeAnnotation obj);
|
||||||
|
|
||||||
|
void attachLineAnnotation(ICodeAnnotation obj);
|
||||||
|
|
||||||
|
void attachSourceLine(int sourceLine);
|
||||||
|
|
||||||
|
ICodeInfo finish();
|
||||||
|
|
||||||
|
String getCodeStr();
|
||||||
|
|
||||||
|
int getLength();
|
||||||
|
|
||||||
|
StringBuilder getRawBuf();
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
Map<Integer, ICodeAnnotation> getRawAnnotations();
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IDecompileScheduler {
|
||||||
|
List<List<JavaClass>> buildBatches(List<JavaClass> classes);
|
||||||
|
}
|
||||||
@@ -1,16 +1,28 @@
|
|||||||
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;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
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.data.ICodeData;
|
||||||
|
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);
|
||||||
|
|
||||||
@@ -25,18 +37,21 @@ public class JadxArgs {
|
|||||||
private File outDirRes;
|
private File outDirRes;
|
||||||
|
|
||||||
private ICodeCache codeCache = new InMemoryCodeCache();
|
private ICodeCache codeCache = new InMemoryCodeCache();
|
||||||
|
private Function<JadxArgs, ICodeWriter> codeWriterProvider = AnnotatedCodeWriter::new;
|
||||||
|
|
||||||
private int threadsCount = DEFAULT_THREADS_COUNT;
|
private int threadsCount = DEFAULT_THREADS_COUNT;
|
||||||
|
|
||||||
private boolean cfgOutput = false;
|
private boolean cfgOutput = false;
|
||||||
private boolean rawCFGOutput = false;
|
private boolean rawCFGOutput = false;
|
||||||
|
|
||||||
private boolean fallbackMode = false;
|
|
||||||
private boolean showInconsistentCode = false;
|
private boolean showInconsistentCode = false;
|
||||||
|
|
||||||
private boolean useImports = true;
|
private boolean useImports = true;
|
||||||
private boolean debugInfo = true;
|
private boolean debugInfo = true;
|
||||||
|
private boolean insertDebugLines = false;
|
||||||
|
private boolean extractFinally = true;
|
||||||
private boolean inlineAnonymousClasses = true;
|
private boolean inlineAnonymousClasses = true;
|
||||||
|
private boolean inlineMethods = true;
|
||||||
|
|
||||||
private boolean skipResources = false;
|
private boolean skipResources = false;
|
||||||
private boolean skipSources = false;
|
private boolean skipSources = false;
|
||||||
@@ -46,10 +61,17 @@ 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 deobfuscationForceSave = false;
|
|
||||||
private boolean useSourceNameAsClassAlias = false;
|
private boolean useSourceNameAsClassAlias = false;
|
||||||
private boolean parseKotlinMetadata = false;
|
private boolean parseKotlinMetadata = false;
|
||||||
|
private File deobfuscationMapFile = null;
|
||||||
|
|
||||||
|
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||||
|
|
||||||
private int deobfuscationMinLength = 0;
|
private int deobfuscationMinLength = 0;
|
||||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||||
@@ -73,6 +95,27 @@ public class JadxArgs {
|
|||||||
|
|
||||||
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
|
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
|
||||||
|
|
||||||
|
private DecompilationMode decompilationMode = DecompilationMode.AUTO;
|
||||||
|
|
||||||
|
private ICodeData codeData;
|
||||||
|
|
||||||
|
private CommentsLevel commentsLevel = CommentsLevel.INFO;
|
||||||
|
|
||||||
|
private boolean useDxInput = false;
|
||||||
|
|
||||||
|
public enum UseKotlinMethodsForVarNames {
|
||||||
|
DISABLE, APPLY, APPLY_AND_HIDE
|
||||||
|
}
|
||||||
|
|
||||||
|
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't save files (can be using for performance testing)
|
||||||
|
*/
|
||||||
|
private boolean skipFilesSave = false;
|
||||||
|
|
||||||
|
private Map<String, String> pluginOptions = new HashMap<>();
|
||||||
|
|
||||||
public JadxArgs() {
|
public JadxArgs() {
|
||||||
// use default options
|
// use default options
|
||||||
}
|
}
|
||||||
@@ -83,6 +126,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;
|
||||||
}
|
}
|
||||||
@@ -124,7 +180,7 @@ public class JadxArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setThreadsCount(int threadsCount) {
|
public void setThreadsCount(int threadsCount) {
|
||||||
this.threadsCount = threadsCount;
|
this.threadsCount = Math.max(1, threadsCount); // make sure threadsCount >= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCfgOutput() {
|
public boolean isCfgOutput() {
|
||||||
@@ -144,11 +200,17 @@ public class JadxArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFallbackMode() {
|
public boolean isFallbackMode() {
|
||||||
return fallbackMode;
|
return decompilationMode == DecompilationMode.FALLBACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deprecated: use 'decompilation mode' property
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setFallbackMode(boolean fallbackMode) {
|
public void setFallbackMode(boolean fallbackMode) {
|
||||||
this.fallbackMode = fallbackMode;
|
if (fallbackMode) {
|
||||||
|
this.decompilationMode = DecompilationMode.FALLBACK;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isShowInconsistentCode() {
|
public boolean isShowInconsistentCode() {
|
||||||
@@ -175,6 +237,14 @@ public class JadxArgs {
|
|||||||
this.debugInfo = debugInfo;
|
this.debugInfo = debugInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInsertDebugLines() {
|
||||||
|
return insertDebugLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInsertDebugLines(boolean insertDebugLines) {
|
||||||
|
this.insertDebugLines = insertDebugLines;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isInlineAnonymousClasses() {
|
public boolean isInlineAnonymousClasses() {
|
||||||
return inlineAnonymousClasses;
|
return inlineAnonymousClasses;
|
||||||
}
|
}
|
||||||
@@ -183,6 +253,22 @@ public class JadxArgs {
|
|||||||
this.inlineAnonymousClasses = inlineAnonymousClasses;
|
this.inlineAnonymousClasses = inlineAnonymousClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInlineMethods() {
|
||||||
|
return inlineMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInlineMethods(boolean inlineMethods) {
|
||||||
|
this.inlineMethods = inlineMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExtractFinally() {
|
||||||
|
return extractFinally;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExtractFinally(boolean extractFinally) {
|
||||||
|
this.extractFinally = extractFinally;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSkipResources() {
|
public boolean isSkipResources() {
|
||||||
return skipResources;
|
return skipResources;
|
||||||
}
|
}
|
||||||
@@ -199,6 +285,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;
|
||||||
}
|
}
|
||||||
@@ -215,12 +309,24 @@ public class JadxArgs {
|
|||||||
this.deobfuscationOn = deobfuscationOn;
|
this.deobfuscationOn = deobfuscationOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public boolean isDeobfuscationForceSave() {
|
public boolean isDeobfuscationForceSave() {
|
||||||
return deobfuscationForceSave;
|
return deobfuscationMapFileMode == DeobfuscationMapFileMode.OVERWRITE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
|
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
|
||||||
this.deobfuscationForceSave = deobfuscationForceSave;
|
if (deobfuscationForceSave) {
|
||||||
|
this.deobfuscationMapFileMode = DeobfuscationMapFileMode.OVERWRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
|
||||||
|
return deobfuscationMapFileMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeobfuscationMapFileMode(DeobfuscationMapFileMode deobfuscationMapFileMode) {
|
||||||
|
this.deobfuscationMapFileMode = deobfuscationMapFileMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUseSourceNameAsClassAlias() {
|
public boolean isUseSourceNameAsClassAlias() {
|
||||||
@@ -255,6 +361,14 @@ public class JadxArgs {
|
|||||||
this.deobfuscationMaxLength = deobfuscationMaxLength;
|
this.deobfuscationMaxLength = deobfuscationMaxLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public File getDeobfuscationMapFile() {
|
||||||
|
return deobfuscationMapFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeobfuscationMapFile(File deobfuscationMapFile) {
|
||||||
|
this.deobfuscationMapFile = deobfuscationMapFile;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEscapeUnicode() {
|
public boolean isEscapeUnicode() {
|
||||||
return escapeUnicode;
|
return escapeUnicode;
|
||||||
}
|
}
|
||||||
@@ -347,6 +461,14 @@ public class JadxArgs {
|
|||||||
this.outputFormat = outputFormat;
|
this.outputFormat = outputFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DecompilationMode getDecompilationMode() {
|
||||||
|
return decompilationMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDecompilationMode(DecompilationMode decompilationMode) {
|
||||||
|
this.decompilationMode = decompilationMode;
|
||||||
|
}
|
||||||
|
|
||||||
public ICodeCache getCodeCache() {
|
public ICodeCache getCodeCache() {
|
||||||
return codeCache;
|
return codeCache;
|
||||||
}
|
}
|
||||||
@@ -355,6 +477,77 @@ public class JadxArgs {
|
|||||||
this.codeCache = codeCache;
|
this.codeCache = codeCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Function<JadxArgs, ICodeWriter> getCodeWriterProvider() {
|
||||||
|
return codeWriterProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCodeWriterProvider(Function<JadxArgs, ICodeWriter> codeWriterProvider) {
|
||||||
|
this.codeWriterProvider = codeWriterProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICodeData getCodeData() {
|
||||||
|
return codeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCodeData(ICodeData codeData) {
|
||||||
|
this.codeData = codeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommentsLevel getCommentsLevel() {
|
||||||
|
return commentsLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommentsLevel(CommentsLevel commentsLevel) {
|
||||||
|
this.commentsLevel = commentsLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUseDxInput() {
|
||||||
|
return useDxInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUseDxInput(boolean useDxInput) {
|
||||||
|
this.useDxInput = useDxInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
||||||
|
return useKotlinMethodsForVarNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUseKotlinMethodsForVarNames(UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
|
||||||
|
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSkipFilesSave() {
|
||||||
|
return skipFilesSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSkipFilesSave(boolean skipFilesSave) {
|
||||||
|
this.skipFilesSave = skipFilesSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getPluginOptions() {
|
||||||
|
return pluginOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPluginOptions(Map<String, String> 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
|
||||||
|
+ 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
|
||||||
@@ -362,17 +555,20 @@ public class JadxArgs {
|
|||||||
+ ", outDirSrc=" + outDirSrc
|
+ ", outDirSrc=" + outDirSrc
|
||||||
+ ", outDirRes=" + outDirRes
|
+ ", outDirRes=" + outDirRes
|
||||||
+ ", threadsCount=" + threadsCount
|
+ ", threadsCount=" + threadsCount
|
||||||
+ ", cfgOutput=" + cfgOutput
|
+ ", decompilationMode=" + decompilationMode
|
||||||
+ ", rawCFGOutput=" + rawCFGOutput
|
|
||||||
+ ", fallbackMode=" + fallbackMode
|
|
||||||
+ ", showInconsistentCode=" + showInconsistentCode
|
+ ", showInconsistentCode=" + showInconsistentCode
|
||||||
+ ", useImports=" + useImports
|
+ ", useImports=" + useImports
|
||||||
+ ", skipResources=" + skipResources
|
+ ", skipResources=" + skipResources
|
||||||
+ ", skipSources=" + skipSources
|
+ ", skipSources=" + skipSources
|
||||||
|
+ ", includeDependencies=" + includeDependencies
|
||||||
+ ", deobfuscationOn=" + deobfuscationOn
|
+ ", deobfuscationOn=" + deobfuscationOn
|
||||||
+ ", deobfuscationForceSave=" + deobfuscationForceSave
|
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
||||||
|
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
|
||||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||||
|
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||||
|
+ ", insertDebugLines=" + insertDebugLines
|
||||||
|
+ ", extractFinally=" + extractFinally
|
||||||
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
||||||
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
||||||
+ ", escapeUnicode=" + escapeUnicode
|
+ ", escapeUnicode=" + escapeUnicode
|
||||||
@@ -382,7 +578,13 @@ public class JadxArgs {
|
|||||||
+ ", fsCaseSensitive=" + fsCaseSensitive
|
+ ", fsCaseSensitive=" + fsCaseSensitive
|
||||||
+ ", renameFlags=" + renameFlags
|
+ ", renameFlags=" + renameFlags
|
||||||
+ ", outputFormat=" + outputFormat
|
+ ", outputFormat=" + outputFormat
|
||||||
|
+ ", commentsLevel=" + commentsLevel
|
||||||
+ ", codeCache=" + codeCache
|
+ ", codeCache=" + codeCache
|
||||||
|
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
|
||||||
|
+ ", useDxInput=" + useDxInput
|
||||||
|
+ ", pluginOptions=" + pluginOptions
|
||||||
|
+ ", cfgOutput=" + cfgOutput
|
||||||
|
+ ", rawCFGOutput=" + rawCFGOutput
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,28 +67,28 @@ 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) {
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
throw new JadxArgsValidateException("File not found " + file.getAbsolutePath());
|
throw new JadxArgsValidateException("File not found " + file.getAbsolutePath());
|
||||||
}
|
}
|
||||||
if (file.isDirectory()) {
|
|
||||||
throw new JadxArgsValidateException("Expected file but found directory instead: " + file.getAbsolutePath());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkDir(File dir, String desc) {
|
private static void checkDir(File dir, String desc) {
|
||||||
|
|||||||
@@ -15,30 +15,43 @@ import java.util.Set;
|
|||||||
import java.util.concurrent.ConcurrentHashMap;
|
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.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.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;
|
||||||
import jadx.api.plugins.input.data.ILoadResult;
|
import jadx.api.plugins.input.data.ILoadResult;
|
||||||
|
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.nodes.LineAttrNode;
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
||||||
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.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.visitors.SaveCode;
|
import jadx.core.dex.visitors.SaveCode;
|
||||||
import jadx.core.export.ExportGradleProject;
|
import jadx.core.export.ExportGradleProject;
|
||||||
|
import jadx.core.utils.DecompilerScheduler;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
import jadx.core.xmlgen.BinaryXMLParser;
|
import jadx.core.xmlgen.BinaryXMLParser;
|
||||||
|
import jadx.core.xmlgen.ProtoXMLParser;
|
||||||
|
import jadx.core.xmlgen.ResContainer;
|
||||||
import jadx.core.xmlgen.ResourcesSaver;
|
import jadx.core.xmlgen.ResourcesSaver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,6 +59,7 @@ import jadx.core.xmlgen.ResourcesSaver;
|
|||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* <code>
|
* <code>
|
||||||
|
*
|
||||||
* JadxArgs args = new JadxArgs();
|
* JadxArgs args = new JadxArgs();
|
||||||
* args.getInputFiles().add(new File("test.apk"));
|
* args.getInputFiles().add(new File("test.apk"));
|
||||||
* args.setOutDir(new File("jadx-test-output"));
|
* args.setOutDir(new File("jadx-test-output"));
|
||||||
@@ -60,6 +74,7 @@ import jadx.core.xmlgen.ResourcesSaver;
|
|||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* <code>
|
* <code>
|
||||||
|
*
|
||||||
* for(JavaClass cls : jadx.getClasses()) {
|
* for(JavaClass cls : jadx.getClasses()) {
|
||||||
* System.out.println(cls.getCode());
|
* System.out.println(cls.getCode());
|
||||||
* }
|
* }
|
||||||
@@ -77,12 +92,17 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
private List<JavaClass> classes;
|
private List<JavaClass> classes;
|
||||||
private List<ResourceFile> resources;
|
private List<ResourceFile> resources;
|
||||||
|
|
||||||
private BinaryXMLParser xmlParser;
|
private BinaryXMLParser binaryXmlParser;
|
||||||
|
private ProtoXMLParser protoXmlParser;
|
||||||
|
|
||||||
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
|
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
|
||||||
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
||||||
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
|
||||||
|
|
||||||
|
private final List<ILoadResult> customLoads = new ArrayList<>();
|
||||||
|
|
||||||
public JadxDecompiler() {
|
public JadxDecompiler() {
|
||||||
this(new JadxArgs());
|
this(new JadxArgs());
|
||||||
}
|
}
|
||||||
@@ -93,8 +113,9 @@ 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);
|
||||||
loadInputFiles();
|
loadInputFiles();
|
||||||
|
|
||||||
root = new RootNode(args);
|
root = new RootNode(args);
|
||||||
@@ -108,25 +129,45 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
private void loadInputFiles() {
|
private void loadInputFiles() {
|
||||||
loadedInputs.clear();
|
loadedInputs.clear();
|
||||||
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
||||||
|
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||||
ILoadResult loadResult = inputPlugin.loadFiles(inputPaths);
|
ILoadResult loadResult = inputPlugin.loadFiles(inputFiles);
|
||||||
if (loadResult != null && !loadResult.isEmpty()) {
|
if (loadResult != null && !loadResult.isEmpty()) {
|
||||||
loadedInputs.add(loadResult);
|
loadedInputs.add(loadResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
loadedInputs.addAll(customLoads);
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
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;
|
||||||
xmlParser = null;
|
binaryXmlParser = null;
|
||||||
|
protoXmlParser = null;
|
||||||
|
|
||||||
classesMap.clear();
|
classesMap.clear();
|
||||||
methodsMap.clear();
|
methodsMap.clear();
|
||||||
fieldsMap.clear();
|
fieldsMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
reset();
|
||||||
closeInputs();
|
closeInputs();
|
||||||
|
args.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeInputs() {
|
private void closeInputs() {
|
||||||
@@ -140,11 +181,28 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
loadedInputs.clear();
|
loadedInputs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void loadPlugins(JadxArgs args) {
|
||||||
public void close() {
|
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
|
||||||
reset();
|
pluginManager.load();
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
|
||||||
|
p -> p.getPluginInfo().getPluginId()));
|
||||||
|
}
|
||||||
|
Map<String, String> pluginOptions = args.getPluginOptions();
|
||||||
|
if (!pluginOptions.isEmpty()) {
|
||||||
|
LOG.debug("Applying plugin options: {}", pluginOptions);
|
||||||
|
for (JadxPluginOptions plugin : pluginManager.getPluginsWithOptions()) {
|
||||||
|
try {
|
||||||
|
plugin.setOptions(pluginOptions);
|
||||||
|
} catch (Exception e) {
|
||||||
|
String pluginId = plugin.getPluginInfo().getPluginId();
|
||||||
|
throw new JadxRuntimeException("Failed to apply options for plugin: " + pluginId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public void registerPlugin(JadxPlugin plugin) {
|
public void registerPlugin(JadxPlugin plugin) {
|
||||||
pluginManager.register(plugin);
|
pluginManager.register(plugin);
|
||||||
}
|
}
|
||||||
@@ -157,6 +215,27 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
save(!args.isSkipSources(), !args.isSkipResources());
|
save(!args.isSkipSources(), !args.isSkipResources());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ProgressListener {
|
||||||
|
void progress(long done, long total);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("BusyWait")
|
||||||
|
public void save(int intervalInMillis, ProgressListener listener) {
|
||||||
|
ThreadPoolExecutor ex = (ThreadPoolExecutor) getSaveExecutor();
|
||||||
|
ex.shutdown();
|
||||||
|
try {
|
||||||
|
long total = ex.getTaskCount();
|
||||||
|
while (ex.isTerminating()) {
|
||||||
|
long done = ex.getCompletedTaskCount();
|
||||||
|
listener.progress(done, total);
|
||||||
|
Thread.sleep(intervalInMillis);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.error("Save interrupted", e);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void saveSources() {
|
public void saveSources() {
|
||||||
save(true, false);
|
save(true, false);
|
||||||
}
|
}
|
||||||
@@ -165,6 +244,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();
|
||||||
@@ -180,20 +260,44 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
|
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Runnable> getSaveTasks() {
|
||||||
|
return getSaveTasks(!args.isSkipSources(), !args.isSkipResources());
|
||||||
|
}
|
||||||
|
|
||||||
private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) {
|
private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) {
|
||||||
|
int threadsCount = args.getThreadsCount();
|
||||||
|
LOG.debug("processing threads count: {}", threadsCount);
|
||||||
|
LOG.info("processing ...");
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
|
||||||
|
List<Runnable> tasks = getSaveTasks(saveSources, saveResources);
|
||||||
|
tasks.forEach(executor::execute);
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Runnable> getSaveTasks(boolean saveSources, boolean saveResources) {
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
throw new JadxRuntimeException("No loaded files");
|
throw new JadxRuntimeException("No loaded files");
|
||||||
}
|
}
|
||||||
int threadsCount = args.getThreadsCount();
|
|
||||||
LOG.debug("processing threads count: {}", threadsCount);
|
|
||||||
|
|
||||||
LOG.info("processing ...");
|
|
||||||
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
|
|
||||||
|
|
||||||
File sourcesOutDir;
|
File sourcesOutDir;
|
||||||
File resOutDir;
|
File resOutDir;
|
||||||
if (args.isExportAsGradleProject()) {
|
if (args.isExportAsGradleProject()) {
|
||||||
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir());
|
ResourceFile androidManifest = resources.stream()
|
||||||
|
.filter(resourceFile -> resourceFile.getType() == ResourceType.MANIFEST)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(IllegalStateException::new);
|
||||||
|
|
||||||
|
ResContainer strings = resources.stream()
|
||||||
|
.filter(resourceFile -> resourceFile.getType() == ResourceType.ARSC)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(IllegalStateException::new)
|
||||||
|
.loadContent()
|
||||||
|
.getSubFiles()
|
||||||
|
.stream()
|
||||||
|
.filter(resContainer -> resContainer.getFileName().contains("strings.xml"))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(IllegalStateException::new);
|
||||||
|
|
||||||
|
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir(), androidManifest, strings);
|
||||||
export.init();
|
export.init();
|
||||||
sourcesOutDir = export.getSrcOutDir();
|
sourcesOutDir = export.getSrcOutDir();
|
||||||
resOutDir = export.getResOutDir();
|
resOutDir = export.getResOutDir();
|
||||||
@@ -201,16 +305,21 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
sourcesOutDir = args.getOutDirSrc();
|
sourcesOutDir = args.getOutDirSrc();
|
||||||
resOutDir = args.getOutDirRes();
|
resOutDir = args.getOutDirRes();
|
||||||
}
|
}
|
||||||
|
List<Runnable> tasks = new ArrayList<>();
|
||||||
|
// save resources first because decompilation can hang or fail
|
||||||
if (saveResources) {
|
if (saveResources) {
|
||||||
appendResourcesSave(executor, resOutDir);
|
appendResourcesSaveTasks(tasks, resOutDir);
|
||||||
}
|
}
|
||||||
if (saveSources) {
|
if (saveSources) {
|
||||||
appendSourcesSave(executor, sourcesOutDir);
|
appendSourcesSave(tasks, sourcesOutDir);
|
||||||
}
|
}
|
||||||
return executor;
|
return tasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void appendResourcesSave(ExecutorService executor, File outDir) {
|
private void appendResourcesSaveTasks(List<Runnable> tasks, File outDir) {
|
||||||
|
if (args.isSkipFilesSave()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
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
|
if (resourceFile.getType() != ResourceType.ARSC
|
||||||
@@ -218,25 +327,42 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
// ignore resource made from input file
|
// ignore resource made from input file
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
executor.execute(new ResourcesSaver(outDir, resourceFile));
|
tasks.add(new ResourcesSaver(outDir, resourceFile));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void appendSourcesSave(ExecutorService executor, File outDir) {
|
private void appendSourcesSave(List<Runnable> tasks, File outDir) {
|
||||||
Predicate<String> classFilter = args.getClassFilter();
|
Predicate<String> classFilter = args.getClassFilter();
|
||||||
for (JavaClass cls : getClasses()) {
|
List<JavaClass> classes = getClasses();
|
||||||
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
|
List<JavaClass> processQueue = new ArrayList<>(classes.size());
|
||||||
|
for (JavaClass cls : classes) {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
executor.execute(() -> {
|
processQueue.add(cls);
|
||||||
try {
|
}
|
||||||
ICodeInfo code = cls.getCodeInfo();
|
List<List<JavaClass>> batches;
|
||||||
SaveCode.save(outDir, cls.getClassNode(), code);
|
try {
|
||||||
} catch (Exception e) {
|
batches = decompileScheduler.buildBatches(processQueue);
|
||||||
LOG.error("Error saving class: {}", cls.getFullName(), e);
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Decompilation batches build failed", e);
|
||||||
|
}
|
||||||
|
for (List<JavaClass> decompileBatch : batches) {
|
||||||
|
tasks.add(() -> {
|
||||||
|
for (JavaClass cls : decompileBatch) {
|
||||||
|
try {
|
||||||
|
ICodeInfo code = cls.getCodeInfo();
|
||||||
|
SaveCode.save(outDir, cls.getClassNode(), code);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Error saving class: {}", cls, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -247,14 +373,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);
|
||||||
@@ -262,6 +388,10 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<JavaClass> getClassesWithInners() {
|
||||||
|
return Utils.collectionMap(root.getClasses(), this::convertClassNode);
|
||||||
|
}
|
||||||
|
|
||||||
public List<ResourceFile> getResources() {
|
public List<ResourceFile> getResources() {
|
||||||
if (resources == null) {
|
if (resources == null) {
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
@@ -319,157 +449,215 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized BinaryXMLParser getXmlParser() {
|
synchronized BinaryXMLParser getBinaryXmlParser() {
|
||||||
if (xmlParser == null) {
|
if (binaryXmlParser == null) {
|
||||||
xmlParser = new BinaryXMLParser(root);
|
binaryXmlParser = new BinaryXMLParser(root);
|
||||||
}
|
}
|
||||||
return xmlParser;
|
return binaryXmlParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadJavaClass(JavaClass javaClass) {
|
synchronized ProtoXMLParser getProtoXmlParser() {
|
||||||
javaClass.getMethods().forEach(mth -> methodsMap.put(mth.getMethodNode(), mth));
|
if (protoXmlParser == null) {
|
||||||
javaClass.getFields().forEach(fld -> fieldsMap.put(fld.getFieldNode(), fld));
|
protoXmlParser = new ProtoXMLParser(root);
|
||||||
|
|
||||||
for (JavaClass innerCls : javaClass.getInnerClasses()) {
|
|
||||||
classesMap.put(innerCls.getClassNode(), innerCls);
|
|
||||||
loadJavaClass(innerCls);
|
|
||||||
}
|
}
|
||||||
|
return protoXmlParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable("For not generated classes")
|
/**
|
||||||
private JavaClass getJavaClassByNode(ClassNode cls) {
|
* Get JavaClass by ClassNode without loading and decompilation
|
||||||
JavaClass javaClass = classesMap.get(cls);
|
*/
|
||||||
if (javaClass != null) {
|
@ApiStatus.Internal
|
||||||
return javaClass;
|
JavaClass convertClassNode(ClassNode cls) {
|
||||||
}
|
return classesMap.compute(cls, (node, prevJavaCls) -> {
|
||||||
// load parent class if inner
|
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) {
|
||||||
ClassNode parentClass = cls.getTopParentClass();
|
// keep previous variable
|
||||||
if (parentClass.contains(AFlag.DONT_GENERATE)) {
|
return prevJavaCls;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (parentClass != cls) {
|
|
||||||
JavaClass parentJavaClass = classesMap.get(parentClass);
|
|
||||||
if (parentJavaClass == null) {
|
|
||||||
getClasses();
|
|
||||||
parentJavaClass = classesMap.get(parentClass);
|
|
||||||
}
|
}
|
||||||
loadJavaClass(parentJavaClass);
|
if (cls.isInner()) {
|
||||||
javaClass = classesMap.get(cls);
|
return new JavaClass(cls, convertClassNode(cls.getParentClass()));
|
||||||
if (javaClass != null) {
|
}
|
||||||
return javaClass;
|
return new JavaClass(cls, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
JavaField convertFieldNode(FieldNode field) {
|
||||||
|
return fieldsMap.computeIfAbsent(field, fldNode -> {
|
||||||
|
JavaClass parentCls = convertClassNode(fldNode.getParentClass());
|
||||||
|
return new JavaField(parentCls, fldNode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
JavaMethod convertMethodNode(MethodNode method) {
|
||||||
|
return methodsMap.computeIfAbsent(method, mthNode -> {
|
||||||
|
ClassNode codeCls = getCodeParentClass(mthNode.getParentClass());
|
||||||
|
return new JavaMethod(convertClassNode(codeCls), mthNode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClassNode getCodeParentClass(ClassNode cls) {
|
||||||
|
ClassNode codeCls;
|
||||||
|
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
|
||||||
|
if (inlinedAttr != null) {
|
||||||
|
codeCls = inlinedAttr.getInlineCls().getTopParentClass();
|
||||||
|
} else {
|
||||||
|
codeCls = cls.getTopParentClass();
|
||||||
|
}
|
||||||
|
if (codeCls == cls) {
|
||||||
|
return codeCls;
|
||||||
|
}
|
||||||
|
return getCodeParentClass(codeCls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public JavaClass searchJavaClassByOrigFullName(String fullName) {
|
||||||
|
return getRoot().getClasses().stream()
|
||||||
|
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
|
||||||
|
.findFirst()
|
||||||
|
.map(this::convertClassNode)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public ClassNode searchClassNodeByOrigFullName(String fullName) {
|
||||||
|
return getRoot().getClasses().stream()
|
||||||
|
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns parent if class contains DONT_GENERATE flag.
|
||||||
|
@Nullable
|
||||||
|
public JavaClass searchJavaClassOrItsParentByOrigFullName(String fullName) {
|
||||||
|
ClassNode node = getRoot().getClasses().stream()
|
||||||
|
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
if (node != null) {
|
||||||
|
if (node.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
return convertClassNode(node.getTopParentClass());
|
||||||
|
} else {
|
||||||
|
return convertClassNode(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// class or parent classes can be excluded from generation
|
return null;
|
||||||
if (cls.hasNotGeneratedParent()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
throw new JadxRuntimeException("JavaClass not found by ClassNode: " + cls);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private JavaMethod getJavaMethodByNode(MethodNode mth) {
|
public JavaClass searchJavaClassByAliasFullName(String fullName) {
|
||||||
JavaMethod javaMethod = methodsMap.get(mth);
|
return getRoot().getClasses().stream()
|
||||||
if (javaMethod != null) {
|
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
|
||||||
return javaMethod;
|
.findFirst()
|
||||||
}
|
.map(this::convertClassNode)
|
||||||
// parent class not loaded yet
|
.orElse(null);
|
||||||
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
|
|
||||||
if (javaClass == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
loadJavaClass(javaClass);
|
|
||||||
javaMethod = methodsMap.get(mth);
|
|
||||||
if (javaMethod != null) {
|
|
||||||
return javaMethod;
|
|
||||||
}
|
|
||||||
if (mth.getParentClass().hasNotGeneratedParent()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private JavaField getJavaFieldByNode(FieldNode fld) {
|
public JavaNode getJavaNodeByRef(ICodeNodeRef ann) {
|
||||||
JavaField javaField = fieldsMap.get(fld);
|
return getJavaNodeByCodeAnnotation(null, ann);
|
||||||
if (javaField != null) {
|
|
||||||
return javaField;
|
|
||||||
}
|
|
||||||
// parent class not loaded yet
|
|
||||||
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
|
||||||
JavaNode convertNode(Object obj) {
|
public JavaNode getJavaNodeByCodeAnnotation(@Nullable ICodeInfo codeInfo, @Nullable ICodeAnnotation ann) {
|
||||||
if (!(obj instanceof LineAttrNode)) {
|
if (ann == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
LineAttrNode node = (LineAttrNode) obj;
|
switch (ann.getAnnType()) {
|
||||||
if (node.contains(AFlag.DONT_GENERATE)) {
|
case CLASS:
|
||||||
return null;
|
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;
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unknown annotation type: " + ann.getAnnType() + ", class: " + ann.getClass());
|
||||||
}
|
}
|
||||||
if (obj instanceof ClassNode) {
|
|
||||||
return getJavaClassByNode((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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<JavaNode> convertNodes(Collection<?> nodesList) {
|
@Nullable
|
||||||
|
private JavaVariable resolveVarNode(VarNode varNode) {
|
||||||
|
MethodNode mthNode = varNode.getMth();
|
||||||
|
JavaMethod mth = convertMethodNode(mthNode);
|
||||||
|
if (mth == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new JavaVariable(mth, 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 null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<JavaNode> convertNodes(Collection<? extends ICodeNodeRef> 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(jCls, defLine, 0);
|
return getJavaNodeByRef(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reloadCodeData() {
|
||||||
|
root.notifyCodeDataListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
public JadxArgs getArgs() {
|
public JadxArgs getArgs() {
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JadxPluginManager getPluginManager() {
|
||||||
|
return pluginManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDecompileScheduler getDecompileScheduler() {
|
||||||
|
return decompileScheduler;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "jadx decompiler " + getVersion();
|
return "jadx decompiler " + getVersion();
|
||||||
|
|||||||
@@ -7,21 +7,32 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
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.nodes.AnonymousClassAttr;
|
||||||
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;
|
||||||
private final JavaClass parent;
|
private final JavaClass parent;
|
||||||
|
|
||||||
private List<JavaClass> innerClasses = Collections.emptyList();
|
private List<JavaClass> innerClasses = Collections.emptyList();
|
||||||
|
private List<JavaClass> inlinedClasses = Collections.emptyList();
|
||||||
private List<JavaField> fields = Collections.emptyList();
|
private List<JavaField> fields = Collections.emptyList();
|
||||||
private List<JavaMethod> methods = Collections.emptyList();
|
private List<JavaMethod> methods = Collections.emptyList();
|
||||||
private boolean listsLoaded;
|
private boolean listsLoaded;
|
||||||
@@ -42,69 +53,101 @@ 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() {
|
||||||
|
load();
|
||||||
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() {
|
||||||
|
listsLoaded = false;
|
||||||
|
cls.unloadCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNoCode() {
|
||||||
|
return cls.contains(AFlag.DONT_GENERATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInner() {
|
||||||
|
return cls.isInner();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized String getSmali() {
|
public synchronized String getSmali() {
|
||||||
return cls.getSmali();
|
return cls.getDisassembledCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void unload() {
|
@Override
|
||||||
cls.unload();
|
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||||
listsLoaded = false;
|
if (ann.getAnnType() == ICodeAnnotation.AnnType.CLASS) {
|
||||||
|
return ann.equals(cls);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal API. Not Stable!
|
* Internal API. Not Stable!
|
||||||
*/
|
*/
|
||||||
|
@ApiStatus.Internal
|
||||||
public ClassNode getClassNode() {
|
public ClassNode getClassNode() {
|
||||||
return cls;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void loadLists() {
|
/**
|
||||||
|
* Decompile class and loads internal lists of fields, methods, etc.
|
||||||
|
* Do nothing if already loaded.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private synchronized void load() {
|
||||||
if (listsLoaded) {
|
if (listsLoaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
listsLoaded = true;
|
listsLoaded = true;
|
||||||
decompile();
|
JadxDecompiler rootDecompiler = getRootDecompiler();
|
||||||
|
ICodeCache codeCache = rootDecompiler.getArgs().getCodeCache();
|
||||||
|
if (!codeCache.contains(cls.getRawName())) {
|
||||||
|
cls.decompile();
|
||||||
|
}
|
||||||
|
|
||||||
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 = new JavaClass(inner, this);
|
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
||||||
javaClass.loadLists();
|
javaClass.load();
|
||||||
list.add(javaClass);
|
list.add(javaClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.innerClasses = Collections.unmodifiableList(list);
|
this.innerClasses = Collections.unmodifiableList(list);
|
||||||
}
|
}
|
||||||
|
int inlinedClsCount = cls.getInlinedClasses().size();
|
||||||
|
if (inlinedClsCount != 0) {
|
||||||
|
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
|
||||||
|
for (ClassNode inner : cls.getInlinedClasses()) {
|
||||||
|
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
||||||
|
javaClass.load();
|
||||||
|
list.add(javaClass);
|
||||||
|
}
|
||||||
|
this.inlinedClasses = Collections.unmodifiableList(list);
|
||||||
|
}
|
||||||
|
|
||||||
int fieldsCount = cls.getFields().size();
|
int fieldsCount = cls.getFields().size();
|
||||||
if (fieldsCount != 0) {
|
if (fieldsCount != 0) {
|
||||||
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(f, this);
|
flds.add(rootDecompiler.convertFieldNode(f));
|
||||||
flds.add(javaField);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.fields = Collections.unmodifiableList(flds);
|
this.fields = Collections.unmodifiableList(flds);
|
||||||
@@ -115,8 +158,7 @@ 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));
|
||||||
@@ -124,57 +166,57 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected JadxDecompiler getRootDecompiler() {
|
JadxDecompiler getRootDecompiler() {
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
return parent.getRootDecompiler();
|
return parent.getRootDecompiler();
|
||||||
}
|
}
|
||||||
return decompiler;
|
return decompiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
private 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 Map<CodePosition, JavaNode> getUsageMap() {
|
public Map<Integer, JavaNode> getUsageMap() {
|
||||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
Map<Integer, ICodeAnnotation> map = getCodeInfo().getCodeMetadata().getAsMap();
|
||||||
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<Integer> getUsePlacesFor(ICodeInfo codeInfo, JavaNode javaNode) {
|
||||||
|
if (!codeInfo.hasMetadata()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<Integer> result = new ArrayList<>();
|
||||||
|
codeInfo.getCodeMetadata().searchDown(0, (pos, ann) -> {
|
||||||
|
if (javaNode.isOwnCodeAnnotation(ann)) {
|
||||||
|
result.add(pos);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<JavaNode> getUseIn() {
|
public List<JavaNode> getUseIn() {
|
||||||
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
|
||||||
@@ -202,31 +244,69 @@ public final class JavaClass implements JavaNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaClass getTopParentClass() {
|
public JavaClass getTopParentClass() {
|
||||||
|
if (cls.contains(AType.ANONYMOUS_CLASS)) {
|
||||||
|
// moved to usage class
|
||||||
|
return getParentForAnonymousClass();
|
||||||
|
}
|
||||||
return parent == null ? this : parent.getTopParentClass();
|
return parent == null ? this : parent.getTopParentClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private JavaClass getParentForAnonymousClass() {
|
||||||
|
AnonymousClassAttr attr = cls.get(AType.ANONYMOUS_CLASS);
|
||||||
|
ClassNode topParentClass = attr.getOuterCls().getTopParentClass();
|
||||||
|
return getRootDecompiler().convertClassNode(topParentClass);
|
||||||
|
}
|
||||||
|
|
||||||
public AccessInfo getAccessInfo() {
|
public AccessInfo getAccessInfo() {
|
||||||
return cls.getAccessFlags();
|
return cls.getAccessFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaClass> getInnerClasses() {
|
public List<JavaClass> getInnerClasses() {
|
||||||
loadLists();
|
load();
|
||||||
return innerClasses;
|
return innerClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<JavaClass> getInlinedClasses() {
|
||||||
|
load();
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public JavaMethod searchMethodByShortId(String shortId) {
|
||||||
|
MethodNode methodNode = cls.searchMethodByShortId(shortId);
|
||||||
|
if (methodNode == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
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
|
||||||
public int getDecompiledLine() {
|
public void removeAlias() {
|
||||||
return cls.getDecompiledLine();
|
this.cls.getClassInfo().removeAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefPos() {
|
||||||
|
return cls.getDefPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package jadx.api;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -11,7 +14,7 @@ public final class JavaField implements JavaNode {
|
|||||||
private final FieldNode field;
|
private final FieldNode field;
|
||||||
private final JavaClass parent;
|
private final JavaClass parent;
|
||||||
|
|
||||||
JavaField(FieldNode f, JavaClass cls) {
|
JavaField(JavaClass cls, FieldNode f) {
|
||||||
this.field = f;
|
this.field = f;
|
||||||
this.parent = cls;
|
this.parent = cls;
|
||||||
}
|
}
|
||||||
@@ -26,6 +29,10 @@ public final class JavaField implements JavaNode {
|
|||||||
return parent.getFullName() + '.' + getName();
|
return parent.getFullName() + '.' + getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRawName() {
|
||||||
|
return field.getName();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaClass getDeclaringClass() {
|
public JavaClass getDeclaringClass() {
|
||||||
return parent;
|
return parent;
|
||||||
@@ -45,8 +52,8 @@ public final class JavaField implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDecompiledLine() {
|
public int getDefPos() {
|
||||||
return field.getDecompiledLine();
|
return field.getDefPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -54,9 +61,23 @@ public final class JavaField implements JavaNode {
|
|||||||
return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn());
|
return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void 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!
|
||||||
*/
|
*/
|
||||||
|
@ApiStatus.Internal
|
||||||
public FieldNode getFieldNode() {
|
public FieldNode getFieldNode() {
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,24 @@ package jadx.api;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
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.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.utils.Utils;
|
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 final MethodNode mth;
|
private final MethodNode mth;
|
||||||
private final JavaClass parent;
|
private final JavaClass parent;
|
||||||
|
|
||||||
@@ -61,6 +72,24 @@ public final class JavaMethod implements JavaNode {
|
|||||||
return getDeclaringClass().getRootDecompiler().convertNodes(mth.getUseIn());
|
return getDeclaringClass().getRootDecompiler().convertNodes(mth.getUseIn());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<JavaMethod> getOverrideRelatedMethods() {
|
||||||
|
MethodOverrideAttr ovrdAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||||
|
if (ovrdAttr == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
||||||
|
return ovrdAttr.getRelatedMthNodes().stream()
|
||||||
|
.map(m -> {
|
||||||
|
JavaMethod javaMth = decompiler.convertMethodNode(m);
|
||||||
|
if (javaMth == null) {
|
||||||
|
LOG.warn("Failed convert to java method: {}", m);
|
||||||
|
}
|
||||||
|
return javaMth;
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isConstructor() {
|
public boolean isConstructor() {
|
||||||
return mth.getMethodInfo().isConstructor();
|
return mth.getMethodInfo().isConstructor();
|
||||||
}
|
}
|
||||||
@@ -70,13 +99,27 @@ public final class JavaMethod implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDecompiledLine() {
|
public int getDefPos() {
|
||||||
return mth.getDecompiledLine();
|
return mth.getDefPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void 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!
|
||||||
*/
|
*/
|
||||||
|
@ApiStatus.Internal
|
||||||
public MethodNode getMethodNode() {
|
public MethodNode getMethodNode() {
|
||||||
return mth;
|
return mth;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,7 +14,12 @@ public interface JavaNode {
|
|||||||
|
|
||||||
JavaClass getTopParentClass();
|
JavaClass getTopParentClass();
|
||||||
|
|
||||||
int getDecompiledLine();
|
int getDefPos();
|
||||||
|
|
||||||
List<JavaNode> getUseIn();
|
List<JavaNode> getUseIn();
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -40,7 +42,7 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDecompiledLine() {
|
public int getDefPos() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,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);
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
|
import jadx.api.metadata.annotations.VarRef;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
|
||||||
|
public class JavaVariable implements JavaNode {
|
||||||
|
private final JavaMethod mth;
|
||||||
|
private final VarNode varNode;
|
||||||
|
|
||||||
|
public JavaVariable(JavaMethod mth, VarNode varNode) {
|
||||||
|
this.mth = mth;
|
||||||
|
this.varNode = varNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JavaMethod getMth() {
|
||||||
|
return mth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReg() {
|
||||||
|
return varNode.getReg();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSsa() {
|
||||||
|
return varNode.getSsa();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return varNode.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public VarNode getVarNode() {
|
||||||
|
return varNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFullName() {
|
||||||
|
return varNode.getType() + " " + varNode.getName() + " (r" + varNode.getReg() + "v" + varNode.getSsa() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getType() {
|
||||||
|
return ArgType.tryToResolveClassAlias(mth.getMethodNode().root(), varNode.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaClass getDeclaringClass() {
|
||||||
|
return mth.getDeclaringClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaClass getTopParentClass() {
|
||||||
|
return mth.getTopParentClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefPos() {
|
||||||
|
return varNode.getDefPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<JavaNode> getUseIn() {
|
||||||
|
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
|
||||||
|
public int hashCode() {
|
||||||
|
return varNode.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof JavaVariable)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return varNode.equals(((JavaVariable) o).varNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,10 @@ public class ResourceFile {
|
|||||||
private ZipRef zipRef;
|
private ZipRef zipRef;
|
||||||
private String deobfName;
|
private String deobfName;
|
||||||
|
|
||||||
|
public static ResourceFile createResourceFile(JadxDecompiler decompiler, File file, ResourceType type) {
|
||||||
|
return new ResourceFile(decompiler, file.getAbsolutePath(), type);
|
||||||
|
}
|
||||||
|
|
||||||
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||||
if (!ZipSecurity.isValidZipEntryName(name)) {
|
if (!ZipSecurity.isValidZipEntryName(name)) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ public enum ResourceType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ResourceType getFileType(String fileName) {
|
public static ResourceType getFileType(String fileName) {
|
||||||
|
if (fileName.endsWith("/resources.pb")) {
|
||||||
|
return ARSC;
|
||||||
|
}
|
||||||
int dot = fileName.lastIndexOf('.');
|
int dot = fileName.lastIndexOf('.');
|
||||||
if (dot != -1) {
|
if (dot != -1) {
|
||||||
String ext = fileName.substring(dot).toLowerCase(Locale.ROOT);
|
String ext = fileName.substring(dot).toLowerCase(Locale.ROOT);
|
||||||
|
|||||||
@@ -17,12 +17,13 @@ import org.slf4j.LoggerFactory;
|
|||||||
import jadx.api.ResourceFile.ZipRef;
|
import jadx.api.ResourceFile.ZipRef;
|
||||||
import jadx.api.impl.SimpleCodeInfo;
|
import jadx.api.impl.SimpleCodeInfo;
|
||||||
import jadx.api.plugins.utils.ZipSecurity;
|
import jadx.api.plugins.utils.ZipSecurity;
|
||||||
import jadx.core.codegen.CodeWriter;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.android.Res9patchStreamDecoder;
|
import jadx.core.utils.android.Res9patchStreamDecoder;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
import jadx.core.xmlgen.ResContainer;
|
import jadx.core.xmlgen.ResContainer;
|
||||||
|
import jadx.core.xmlgen.ResProtoParser;
|
||||||
import jadx.core.xmlgen.ResTableParser;
|
import jadx.core.xmlgen.ResTableParser;
|
||||||
|
|
||||||
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
|
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
|
||||||
@@ -83,7 +84,7 @@ public final class ResourcesLoader {
|
|||||||
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
|
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
|
||||||
} catch (JadxException e) {
|
} catch (JadxException e) {
|
||||||
LOG.error("Decode error", e);
|
LOG.error("Decode error", e);
|
||||||
CodeWriter cw = new CodeWriter();
|
ICodeWriter cw = jadxRef.getRoot().makeCodeWriter();
|
||||||
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
|
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
|
||||||
Utils.appendStackTrace(cw, e.getCause());
|
Utils.appendStackTrace(cw, e.getCause());
|
||||||
return ResContainer.textResource(rf.getDeobfName(), cw.finish());
|
return ResContainer.textResource(rf.getDeobfName(), cw.finish());
|
||||||
@@ -92,14 +93,25 @@ public final class ResourcesLoader {
|
|||||||
|
|
||||||
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
|
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
|
||||||
InputStream inputStream) throws IOException {
|
InputStream inputStream) throws IOException {
|
||||||
|
RootNode root = jadxRef.getRoot();
|
||||||
switch (rf.getType()) {
|
switch (rf.getType()) {
|
||||||
case MANIFEST:
|
case MANIFEST:
|
||||||
case XML:
|
case XML: {
|
||||||
ICodeInfo content = jadxRef.getXmlParser().parse(inputStream);
|
ICodeInfo content;
|
||||||
|
if (root.isProto()) {
|
||||||
|
content = jadxRef.getProtoXmlParser().parse(inputStream);
|
||||||
|
} else {
|
||||||
|
content = jadxRef.getBinaryXmlParser().parse(inputStream);
|
||||||
|
}
|
||||||
return ResContainer.textResource(rf.getDeobfName(), content);
|
return ResContainer.textResource(rf.getDeobfName(), content);
|
||||||
|
}
|
||||||
|
|
||||||
case ARSC:
|
case ARSC:
|
||||||
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream);
|
if (root.isProto()) {
|
||||||
|
return new ResProtoParser(root).decodeFiles(inputStream);
|
||||||
|
} else {
|
||||||
|
return new ResTableParser(root).decodeFiles(inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
case IMG:
|
case IMG:
|
||||||
return decodeImage(rf, inputStream);
|
return decodeImage(rf, inputStream);
|
||||||
@@ -114,8 +126,9 @@ public final class ResourcesLoader {
|
|||||||
if (name.endsWith(".9.png")) {
|
if (name.endsWith(".9.png")) {
|
||||||
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
||||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||||
decoder.decode(inputStream, os);
|
if (decoder.decode(inputStream, os)) {
|
||||||
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
|
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
|
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
|
||||||
}
|
}
|
||||||
@@ -124,22 +137,17 @@ public final class ResourcesLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadFile(List<ResourceFile> list, File file) {
|
private void loadFile(List<ResourceFile> list, File file) {
|
||||||
if (file == null) {
|
if (file == null || file.isDirectory()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (FileUtils.isZipFile(file)) {
|
if (FileUtils.isZipFile(file)) {
|
||||||
ZipSecurity.visitZipEntries(file, (zipFile, entry) -> addEntry(list, file, entry));
|
ZipSecurity.visitZipEntries(file, (zipFile, entry) -> {
|
||||||
|
addEntry(list, file, entry);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
addResourceFile(list, file);
|
ResourceType type = ResourceType.getFileType(file.getAbsolutePath());
|
||||||
}
|
list.add(ResourceFile.createResourceFile(jadxRef, file, type));
|
||||||
}
|
|
||||||
|
|
||||||
private void addResourceFile(List<ResourceFile> list, File file) {
|
|
||||||
String name = file.getAbsolutePath();
|
|
||||||
ResourceType type = ResourceType.getFileType(name);
|
|
||||||
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
|
|
||||||
if (rf != null) {
|
|
||||||
list.add(rf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package jadx.api.args;
|
||||||
|
|
||||||
|
public enum DeobfuscationMapFileMode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load if found, don't save (default)
|
||||||
|
*/
|
||||||
|
READ,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load if found, save only if new (don't overwrite)
|
||||||
|
*/
|
||||||
|
READ_OR_SAVE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't load, always save
|
||||||
|
*/
|
||||||
|
OVERWRITE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't load and don't save
|
||||||
|
*/
|
||||||
|
IGNORE;
|
||||||
|
|
||||||
|
public boolean shouldRead() {
|
||||||
|
return this == READ || this == READ_OR_SAVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldWrite() {
|
||||||
|
return this == READ_OR_SAVE || this == OVERWRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package jadx.api.data;
|
||||||
|
|
||||||
|
public enum CodeRefType {
|
||||||
|
MTH_ARG,
|
||||||
|
VAR,
|
||||||
|
CATCH,
|
||||||
|
INSN,
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package jadx.api.data;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public interface ICodeComment extends Comparable<ICodeComment> {
|
||||||
|
|
||||||
|
IJavaNodeRef getNodeRef();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
IJavaCodeRef getCodeRef();
|
||||||
|
|
||||||
|
String getComment();
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package jadx.api.data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ICodeData {
|
||||||
|
|
||||||
|
List<ICodeComment> getComments();
|
||||||
|
|
||||||
|
List<ICodeRename> getRenames();
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package jadx.api.data;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public interface ICodeRename extends Comparable<ICodeRename> {
|
||||||
|
|
||||||
|
IJavaNodeRef getNodeRef();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
IJavaCodeRef getCodeRef();
|
||||||
|
|
||||||
|
String getNewName();
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package jadx.api.data;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface IJavaCodeRef extends Comparable<IJavaCodeRef> {
|
||||||
|
|
||||||
|
CodeRefType getAttachType();
|
||||||
|
|
||||||
|
int getIndex();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int compareTo(@NotNull IJavaCodeRef o) {
|
||||||
|
return Integer.compare(getIndex(), o.getIndex());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package jadx.api.data;
|
||||||
|
|
||||||
|
public interface IJavaNodeRef extends Comparable<IJavaNodeRef> {
|
||||||
|
|
||||||
|
enum RefType {
|
||||||
|
CLASS, FIELD, METHOD, PKG
|
||||||
|
}
|
||||||
|
|
||||||
|
RefType getType();
|
||||||
|
|
||||||
|
String getDeclaringClass();
|
||||||
|
|
||||||
|
String getShortId();
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package jadx.api.data.impl;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.data.ICodeComment;
|
||||||
|
import jadx.api.data.IJavaCodeRef;
|
||||||
|
import jadx.api.data.IJavaNodeRef;
|
||||||
|
|
||||||
|
public class JadxCodeComment implements ICodeComment {
|
||||||
|
|
||||||
|
private IJavaNodeRef nodeRef;
|
||||||
|
@Nullable
|
||||||
|
private IJavaCodeRef codeRef;
|
||||||
|
private String comment;
|
||||||
|
|
||||||
|
public JadxCodeComment(IJavaNodeRef nodeRef, String comment) {
|
||||||
|
this(nodeRef, null, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxCodeComment(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String comment) {
|
||||||
|
this.nodeRef = nodeRef;
|
||||||
|
this.codeRef = codeRef;
|
||||||
|
this.comment = comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxCodeComment() {
|
||||||
|
// for json deserialization
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IJavaNodeRef getNodeRef() {
|
||||||
|
return nodeRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNodeRef(IJavaNodeRef nodeRef) {
|
||||||
|
this.nodeRef = nodeRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public IJavaCodeRef getCodeRef() {
|
||||||
|
return codeRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCodeRef(@Nullable IJavaCodeRef codeRef) {
|
||||||
|
this.codeRef = codeRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getComment() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComment(String comment) {
|
||||||
|
this.comment = comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(@NotNull ICodeComment other) {
|
||||||
|
int cmpNodeRef = this.getNodeRef().compareTo(other.getNodeRef());
|
||||||
|
if (cmpNodeRef != 0) {
|
||||||
|
return cmpNodeRef;
|
||||||
|
}
|
||||||
|
if (this.getCodeRef() != null && other.getCodeRef() != null) {
|
||||||
|
return this.getCodeRef().compareTo(other.getCodeRef());
|
||||||
|
}
|
||||||
|
return this.getComment().compareTo(other.getComment());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "JadxCodeComment{" + nodeRef
|
||||||
|
+ ", ref=" + codeRef
|
||||||
|
+ ", comment='" + comment + '\''
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package jadx.api.data.impl;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.api.data.ICodeComment;
|
||||||
|
import jadx.api.data.ICodeData;
|
||||||
|
import jadx.api.data.ICodeRename;
|
||||||
|
|
||||||
|
public class JadxCodeData implements ICodeData {
|
||||||
|
private List<ICodeComment> comments = Collections.emptyList();
|
||||||
|
private List<ICodeRename> renames = Collections.emptyList();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ICodeComment> getComments() {
|
||||||
|
return comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComments(List<ICodeComment> comments) {
|
||||||
|
this.comments = comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ICodeRename> getRenames() {
|
||||||
|
return renames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRenames(List<ICodeRename> renames) {
|
||||||
|
this.renames = renames;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package jadx.api.data.impl;
|
||||||
|
|
||||||
|
import jadx.api.JavaVariable;
|
||||||
|
import jadx.api.data.CodeRefType;
|
||||||
|
import jadx.api.data.IJavaCodeRef;
|
||||||
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
|
|
||||||
|
public class JadxCodeRef implements IJavaCodeRef {
|
||||||
|
|
||||||
|
public static JadxCodeRef forInsn(int offset) {
|
||||||
|
return new JadxCodeRef(CodeRefType.INSN, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JadxCodeRef forMthArg(int argIndex) {
|
||||||
|
return new JadxCodeRef(CodeRefType.MTH_ARG, argIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JadxCodeRef forVar(int regNum, int ssaVersion) {
|
||||||
|
return new JadxCodeRef(CodeRefType.VAR, regNum << 16 | ssaVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JadxCodeRef forVar(JavaVariable javaVariable) {
|
||||||
|
return forVar(javaVariable.getReg(), javaVariable.getSsa());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JadxCodeRef forVar(VarNode varNode) {
|
||||||
|
return forVar(varNode.getReg(), varNode.getSsa());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JadxCodeRef forCatch(int handlerOffset) {
|
||||||
|
return new JadxCodeRef(CodeRefType.CATCH, handlerOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CodeRefType attachType;
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
public JadxCodeRef(CodeRefType attachType, int index) {
|
||||||
|
this.attachType = attachType;
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxCodeRef() {
|
||||||
|
// used for json serialization
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeRefType getAttachType() {
|
||||||
|
return attachType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttachType(CodeRefType attachType) {
|
||||||
|
this.attachType = attachType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIndex() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIndex(int index) {
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof JadxCodeRef)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
JadxCodeRef other = (JadxCodeRef) o;
|
||||||
|
return getIndex() == other.getIndex()
|
||||||
|
&& getAttachType() == other.getAttachType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return 31 * getAttachType().hashCode() + getIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "JadxCodeRef{"
|
||||||
|
+ "attachType=" + attachType
|
||||||
|
+ ", index=" + index
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package jadx.api.data.impl;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.data.ICodeRename;
|
||||||
|
import jadx.api.data.IJavaCodeRef;
|
||||||
|
import jadx.api.data.IJavaNodeRef;
|
||||||
|
|
||||||
|
public class JadxCodeRename implements ICodeRename {
|
||||||
|
private IJavaNodeRef nodeRef;
|
||||||
|
@Nullable
|
||||||
|
private IJavaCodeRef codeRef;
|
||||||
|
private String newName;
|
||||||
|
|
||||||
|
public JadxCodeRename(IJavaNodeRef nodeRef, String newName) {
|
||||||
|
this(nodeRef, null, newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxCodeRename(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String newName) {
|
||||||
|
this.nodeRef = nodeRef;
|
||||||
|
this.codeRef = codeRef;
|
||||||
|
this.newName = newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxCodeRename() {
|
||||||
|
// used in json serialization
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IJavaNodeRef getNodeRef() {
|
||||||
|
return nodeRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNodeRef(IJavaNodeRef nodeRef) {
|
||||||
|
this.nodeRef = nodeRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IJavaCodeRef getCodeRef() {
|
||||||
|
return codeRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCodeRef(IJavaCodeRef codeRef) {
|
||||||
|
this.codeRef = codeRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNewName() {
|
||||||
|
return newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNewName(String newName) {
|
||||||
|
this.newName = newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(@NotNull ICodeRename other) {
|
||||||
|
int cmpNodeRef = this.getNodeRef().compareTo(other.getNodeRef());
|
||||||
|
if (cmpNodeRef != 0) {
|
||||||
|
return cmpNodeRef;
|
||||||
|
}
|
||||||
|
if (this.getCodeRef() != null && other.getCodeRef() != null) {
|
||||||
|
return this.getCodeRef().compareTo(other.getCodeRef());
|
||||||
|
}
|
||||||
|
return this.getNewName().compareTo(other.getNewName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof ICodeRename)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ICodeRename other = (ICodeRename) o;
|
||||||
|
return getNodeRef().equals(other.getNodeRef())
|
||||||
|
&& Objects.equals(getCodeRef(), other.getCodeRef());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return 31 * getNodeRef().hashCode() + Objects.hashCode(getCodeRef());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package jadx.api.data.impl;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.JavaClass;
|
||||||
|
import jadx.api.JavaField;
|
||||||
|
import jadx.api.JavaMethod;
|
||||||
|
import jadx.api.JavaNode;
|
||||||
|
import jadx.api.data.IJavaNodeRef;
|
||||||
|
|
||||||
|
public class JadxNodeRef implements IJavaNodeRef {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static JadxNodeRef forJavaNode(JavaNode javaNode) {
|
||||||
|
if (javaNode instanceof JavaClass) {
|
||||||
|
return forCls((JavaClass) javaNode);
|
||||||
|
}
|
||||||
|
if (javaNode instanceof JavaMethod) {
|
||||||
|
return forMth((JavaMethod) javaNode);
|
||||||
|
}
|
||||||
|
if (javaNode instanceof JavaField) {
|
||||||
|
return forFld((JavaField) javaNode);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JadxNodeRef forCls(JavaClass cls) {
|
||||||
|
return new JadxNodeRef(RefType.CLASS, getClassRefStr(cls), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JadxNodeRef forCls(String clsFullName) {
|
||||||
|
return new JadxNodeRef(RefType.CLASS, clsFullName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JadxNodeRef forMth(JavaMethod mth) {
|
||||||
|
return new JadxNodeRef(RefType.METHOD,
|
||||||
|
getClassRefStr(mth.getDeclaringClass()),
|
||||||
|
mth.getMethodNode().getMethodInfo().getShortId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JadxNodeRef forFld(JavaField fld) {
|
||||||
|
return new JadxNodeRef(RefType.FIELD,
|
||||||
|
getClassRefStr(fld.getDeclaringClass()),
|
||||||
|
fld.getFieldNode().getFieldInfo().getShortId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JadxNodeRef forPkg(String pkgFullName) {
|
||||||
|
return new JadxNodeRef(RefType.PKG, pkgFullName, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getClassRefStr(JavaClass cls) {
|
||||||
|
return cls.getClassNode().getClassInfo().getRawName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RefType refType;
|
||||||
|
private String declClass;
|
||||||
|
@Nullable
|
||||||
|
private String shortId;
|
||||||
|
|
||||||
|
public JadxNodeRef(RefType refType, String declClass, @Nullable String shortId) {
|
||||||
|
this.refType = refType;
|
||||||
|
this.declClass = declClass;
|
||||||
|
this.shortId = shortId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadxNodeRef() {
|
||||||
|
// for json deserialization
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RefType getType() {
|
||||||
|
return refType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRefType(RefType refType) {
|
||||||
|
this.refType = refType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDeclaringClass() {
|
||||||
|
return declClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeclClass(String declClass) {
|
||||||
|
this.declClass = declClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getShortId() {
|
||||||
|
return shortId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShortId(@Nullable String shortId) {
|
||||||
|
this.shortId = shortId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Comparator<IJavaNodeRef> COMPARATOR = Comparator
|
||||||
|
.comparing(IJavaNodeRef::getType)
|
||||||
|
.thenComparing(IJavaNodeRef::getDeclaringClass)
|
||||||
|
.thenComparing(IJavaNodeRef::getShortId);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(@NotNull IJavaNodeRef other) {
|
||||||
|
return COMPARATOR.compare(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(refType, declClass, shortId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof JadxNodeRef)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
JadxNodeRef that = (JadxNodeRef) o;
|
||||||
|
return refType == that.refType
|
||||||
|
&& Objects.equals(declClass, that.declClass)
|
||||||
|
&& Objects.equals(shortId, that.shortId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
switch (refType) {
|
||||||
|
case CLASS:
|
||||||
|
case PKG:
|
||||||
|
return declClass;
|
||||||
|
case FIELD:
|
||||||
|
case METHOD:
|
||||||
|
return declClass + "->" + shortId;
|
||||||
|
default:
|
||||||
|
return "unknown node ref type";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final ICodeMetadata metadata;
|
||||||
|
|
||||||
|
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<Integer, ICodeAnnotation> annotations) {
|
||||||
|
this.code = code;
|
||||||
|
this.metadata = CodeMetadataStorage.build(lineMapping, annotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCodeStr() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICodeMetadata getCodeMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasMetadata() {
|
||||||
|
return metadata != ICodeMetadata.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter {
|
||||||
|
|
||||||
|
private int line = 1;
|
||||||
|
private int offset;
|
||||||
|
private Map<Integer, ICodeAnnotation> annotations = Collections.emptyMap();
|
||||||
|
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
||||||
|
|
||||||
|
public AnnotatedCodeWriter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnnotatedCodeWriter(JadxArgs args) {
|
||||||
|
super(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMetadataSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotatedCodeWriter addMultiLine(String str) {
|
||||||
|
if (str.contains(NL)) {
|
||||||
|
buf.append(str.replace(NL, NL + indentStr));
|
||||||
|
line += StringUtils.countMatches(str, NL);
|
||||||
|
offset = 0;
|
||||||
|
} else {
|
||||||
|
buf.append(str);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotatedCodeWriter add(String str) {
|
||||||
|
buf.append(str);
|
||||||
|
offset += str.length();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotatedCodeWriter add(char c) {
|
||||||
|
buf.append(c);
|
||||||
|
offset++;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICodeWriter add(ICodeWriter cw) {
|
||||||
|
if (!cw.isMetadataSupported()) {
|
||||||
|
buf.append(cw.getCodeStr());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
|
||||||
|
line--;
|
||||||
|
int startPos = getLength();
|
||||||
|
for (Map.Entry<Integer, ICodeAnnotation> entry : code.annotations.entrySet()) {
|
||||||
|
int pos = entry.getKey();
|
||||||
|
int newPos = startPos + pos;
|
||||||
|
attachAnnotation(entry.getValue(), newPos);
|
||||||
|
}
|
||||||
|
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
|
||||||
|
attachSourceLine(line + entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
line += code.line;
|
||||||
|
offset = code.offset;
|
||||||
|
buf.append(code.buf);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addLine() {
|
||||||
|
buf.append(NL);
|
||||||
|
line++;
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AnnotatedCodeWriter addLineIndent() {
|
||||||
|
buf.append(indentStr);
|
||||||
|
offset += indentStr.length();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLine() {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLineStartPos() {
|
||||||
|
return getLength() - offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachDefinition(ICodeNodeRef obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
attachAnnotation(new NodeDeclareRef(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachAnnotation(ICodeAnnotation obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
attachAnnotation(obj, getLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachLineAnnotation(ICodeAnnotation obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
attachAnnotation(obj, getLineStartPos());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attachAnnotation(ICodeAnnotation obj, int pos) {
|
||||||
|
if (annotations.isEmpty()) {
|
||||||
|
annotations = new HashMap<>();
|
||||||
|
}
|
||||||
|
annotations.put(pos, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachSourceLine(int sourceLine) {
|
||||||
|
if (sourceLine == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
attachSourceLine(line, sourceLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attachSourceLine(int decompiledLine, int sourceLine) {
|
||||||
|
if (lineMap.isEmpty()) {
|
||||||
|
lineMap = new TreeMap<>();
|
||||||
|
}
|
||||||
|
lineMap.put(decompiledLine, sourceLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICodeInfo finish() {
|
||||||
|
removeFirstEmptyLine();
|
||||||
|
processDefinitionAnnotations();
|
||||||
|
validateAnnotations();
|
||||||
|
String code = buf.toString();
|
||||||
|
buf = null;
|
||||||
|
return new AnnotatedCodeInfo(code, lineMap, annotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processDefinitionAnnotations() {
|
||||||
|
if (!annotations.isEmpty()) {
|
||||||
|
annotations.forEach((k, v) -> {
|
||||||
|
if (v instanceof NodeDeclareRef) {
|
||||||
|
NodeDeclareRef declareRef = (NodeDeclareRef) v;
|
||||||
|
declareRef.setDefPos(k);
|
||||||
|
declareRef.getNode().setDefPosition(k);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,13 +24,37 @@ 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
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "InMemoryCodeCache";
|
return "InMemoryCodeCache: size=" + storage.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,29 +1,14 @@
|
|||||||
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 {
|
||||||
|
|
||||||
private final String code;
|
private final String code;
|
||||||
private final Map<Integer, Integer> lineMapping;
|
|
||||||
private final Map<CodePosition, Object> annotations;
|
|
||||||
|
|
||||||
public SimpleCodeInfo(String code) {
|
public SimpleCodeInfo(String code) {
|
||||||
this(code, Collections.emptyMap(), Collections.emptyMap());
|
|
||||||
}
|
|
||||||
|
|
||||||
public SimpleCodeInfo(ICodeInfo codeInfo) {
|
|
||||||
this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations());
|
|
||||||
}
|
|
||||||
|
|
||||||
public SimpleCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<CodePosition, Object> annotations) {
|
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.lineMapping = lineMapping;
|
|
||||||
this.annotations = annotations;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -32,13 +17,13 @@ public class SimpleCodeInfo implements ICodeInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Integer, Integer> getLineMapping() {
|
public ICodeMetadata getCodeMetadata() {
|
||||||
return lineMapping;
|
return ICodeMetadata.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<CodePosition, Object> getAnnotations() {
|
public boolean hasMetadata() {
|
||||||
return annotations;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,260 @@
|
|||||||
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeWriter implementation without meta information support (only strings builder)
|
||||||
|
*/
|
||||||
|
public class SimpleCodeWriter implements ICodeWriter {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SimpleCodeWriter.class);
|
||||||
|
|
||||||
|
private static final String[] INDENT_CACHE = {
|
||||||
|
"",
|
||||||
|
INDENT_STR,
|
||||||
|
INDENT_STR + INDENT_STR,
|
||||||
|
INDENT_STR + INDENT_STR + INDENT_STR,
|
||||||
|
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
||||||
|
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
||||||
|
};
|
||||||
|
|
||||||
|
protected StringBuilder buf = new StringBuilder();
|
||||||
|
protected String indentStr = "";
|
||||||
|
protected int indent = 0;
|
||||||
|
|
||||||
|
private final boolean insertLineNumbers;
|
||||||
|
|
||||||
|
public SimpleCodeWriter() {
|
||||||
|
this.insertLineNumbers = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleCodeWriter(JadxArgs args) {
|
||||||
|
this.insertLineNumbers = args.isInsertDebugLines();
|
||||||
|
if (insertLineNumbers) {
|
||||||
|
incIndent(3);
|
||||||
|
add(indentStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMetadataSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleCodeWriter startLine() {
|
||||||
|
addLine();
|
||||||
|
addLineIndent();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleCodeWriter startLine(char c) {
|
||||||
|
startLine();
|
||||||
|
add(c);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleCodeWriter startLine(String str) {
|
||||||
|
startLine();
|
||||||
|
add(str);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleCodeWriter startLineWithNum(int sourceLine) {
|
||||||
|
if (sourceLine == 0) {
|
||||||
|
startLine();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (this.insertLineNumbers) {
|
||||||
|
newLine();
|
||||||
|
attachSourceLine(sourceLine);
|
||||||
|
int start = getLength();
|
||||||
|
add("/* ").add(Integer.toString(sourceLine)).add(" */ ");
|
||||||
|
int len = getLength() - start;
|
||||||
|
if (indentStr.length() > len) {
|
||||||
|
add(indentStr.substring(len));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
startLine();
|
||||||
|
attachSourceLine(sourceLine);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleCodeWriter addMultiLine(String str) {
|
||||||
|
if (str.contains(NL)) {
|
||||||
|
buf.append(str.replace(NL, NL + indentStr));
|
||||||
|
} else {
|
||||||
|
buf.append(str);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleCodeWriter add(String str) {
|
||||||
|
buf.append(str);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleCodeWriter add(char c) {
|
||||||
|
buf.append(c);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICodeWriter add(ICodeWriter cw) {
|
||||||
|
buf.append(cw.getCodeStr());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleCodeWriter newLine() {
|
||||||
|
addLine();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleCodeWriter addIndent() {
|
||||||
|
add(INDENT_STR);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addLine() {
|
||||||
|
buf.append(NL);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SimpleCodeWriter addLineIndent() {
|
||||||
|
buf.append(indentStr);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateIndent() {
|
||||||
|
int curIndent = indent;
|
||||||
|
if (curIndent < INDENT_CACHE.length) {
|
||||||
|
this.indentStr = INDENT_CACHE[curIndent];
|
||||||
|
} else {
|
||||||
|
this.indentStr = Utils.strRepeat(INDENT_STR, curIndent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void incIndent() {
|
||||||
|
incIndent(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decIndent() {
|
||||||
|
decIndent(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void incIndent(int c) {
|
||||||
|
this.indent += c;
|
||||||
|
updateIndent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decIndent(int c) {
|
||||||
|
this.indent -= c;
|
||||||
|
if (this.indent < 0) {
|
||||||
|
LOG.warn("Indent < 0");
|
||||||
|
this.indent = 0;
|
||||||
|
}
|
||||||
|
updateIndent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIndent() {
|
||||||
|
return indent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIndent(int indent) {
|
||||||
|
this.indent = indent;
|
||||||
|
updateIndent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLine() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLineStartPos() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachDefinition(ICodeNodeRef obj) {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachAnnotation(ICodeAnnotation obj) {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachLineAnnotation(ICodeAnnotation obj) {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachSourceLine(int sourceLine) {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICodeInfo finish() {
|
||||||
|
removeFirstEmptyLine();
|
||||||
|
String code = buf.toString();
|
||||||
|
buf = null;
|
||||||
|
return new SimpleCodeInfo(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeFirstEmptyLine() {
|
||||||
|
int len = NL.length();
|
||||||
|
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
|
||||||
|
buf.delete(0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLength() {
|
||||||
|
return buf.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StringBuilder getRawBuf() {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCodeStr() {
|
||||||
|
removeFirstEmptyLine();
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getCodeStr();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package jadx.api.metadata;
|
||||||
|
|
||||||
|
public interface ICodeAnnotation {
|
||||||
|
|
||||||
|
enum AnnType {
|
||||||
|
CLASS,
|
||||||
|
FIELD,
|
||||||
|
METHOD,
|
||||||
|
VAR,
|
||||||
|
VAR_REF,
|
||||||
|
DECLARATION,
|
||||||
|
OFFSET
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package jadx.api.metadata.annotations;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
|
||||||
|
public class InsnCodeOffset implements ICodeAnnotation {
|
||||||
|
|
||||||
|
public static void attach(ICodeWriter code, InsnNode insn) {
|
||||||
|
if (insn == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (code.isMetadataSupported()) {
|
||||||
|
InsnCodeOffset ann = from(insn);
|
||||||
|
if (ann != null) {
|
||||||
|
code.attachLineAnnotation(ann);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void attach(ICodeWriter code, int offset) {
|
||||||
|
if (offset >= 0 && code.isMetadataSupported()) {
|
||||||
|
code.attachLineAnnotation(new InsnCodeOffset(offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static InsnCodeOffset from(InsnNode insn) {
|
||||||
|
int offset = insn.getOffset();
|
||||||
|
if (offset < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new InsnCodeOffset(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int offset;
|
||||||
|
|
||||||
|
public InsnCodeOffset(int offset) {
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOffset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.OFFSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "offset=" + offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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 String toString() {
|
||||||
|
return "NodeDeclareRef{" + node + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,141 @@
|
|||||||
|
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 java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
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, ICodeAnnotation.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, ICodeAnnotation.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) {
|
||||||
|
return navMap.tailMap(position, true)
|
||||||
|
.values().stream()
|
||||||
|
.flatMap(CodeMetadataStorage::mapEnclosingNode)
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICodeNodeRef getNodeBelow(int position) {
|
||||||
|
return navMap.headMap(position, true).descendingMap()
|
||||||
|
.values().stream()
|
||||||
|
.flatMap(CodeMetadataStorage::mapEnclosingNode)
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<ICodeNodeRef> mapEnclosingNode(ICodeAnnotation ann) {
|
||||||
|
if (ann instanceof NodeDeclareRef) {
|
||||||
|
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
|
||||||
|
if (node instanceof ClassNode || node instanceof MethodNode) {
|
||||||
|
return Stream.of(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NavigableMap<Integer, ICodeAnnotation> getAsMap() {
|
||||||
|
return navMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Integer, Integer> getLineMapping() {
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CodeMetadata{lines=" + lines
|
||||||
|
+ ", annotations=\n" + Utils.listToString(navMap.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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ public class Consts {
|
|||||||
public static final boolean DEBUG_USAGE = false;
|
public static final boolean DEBUG_USAGE = false;
|
||||||
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_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";
|
||||||
@@ -15,12 +17,7 @@ public class Consts {
|
|||||||
public static final String CLASS_ENUM = "java.lang.Enum";
|
public static final String CLASS_ENUM = "java.lang.Enum";
|
||||||
|
|
||||||
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
|
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
|
||||||
|
public static final String OVERRIDE_ANNOTATION = "Ljava/lang/Override;";
|
||||||
public static final String DALVIK_ANNOTATION_PKG = "Ldalvik/annotation/";
|
|
||||||
public static final String DALVIK_SIGNATURE = "Ldalvik/annotation/Signature;";
|
|
||||||
public static final String DALVIK_INNER_CLASS = "Ldalvik/annotation/InnerClass;";
|
|
||||||
public static final String DALVIK_THROWS = "Ldalvik/annotation/Throws;";
|
|
||||||
public static final String DALVIK_ANNOTATION_DEFAULT = "Ldalvik/annotation/AnnotationDefault;";
|
|
||||||
|
|
||||||
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
|
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
|
||||||
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
|
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
|
||||||
|
|||||||
@@ -10,9 +10,14 @@ import java.util.jar.Manifest;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.visitors.AnonymousClassVisitor;
|
||||||
|
import jadx.core.dex.visitors.AttachCommentsVisitor;
|
||||||
import jadx.core.dex.visitors.AttachMethodDetails;
|
import jadx.core.dex.visitors.AttachMethodDetails;
|
||||||
import jadx.core.dex.visitors.AttachTryCatchVisitor;
|
import jadx.core.dex.visitors.AttachTryCatchVisitor;
|
||||||
|
import jadx.core.dex.visitors.CheckCode;
|
||||||
import jadx.core.dex.visitors.ClassModifier;
|
import jadx.core.dex.visitors.ClassModifier;
|
||||||
import jadx.core.dex.visitors.ConstInlineVisitor;
|
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||||
import jadx.core.dex.visitors.ConstructorVisitor;
|
import jadx.core.dex.visitors.ConstructorVisitor;
|
||||||
@@ -26,26 +31,26 @@ import jadx.core.dex.visitors.GenericTypesVisitor;
|
|||||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
import jadx.core.dex.visitors.InitCodeVariables;
|
import jadx.core.dex.visitors.InitCodeVariables;
|
||||||
import jadx.core.dex.visitors.InlineMethods;
|
import jadx.core.dex.visitors.InlineMethods;
|
||||||
import jadx.core.dex.visitors.MarkFinallyVisitor;
|
|
||||||
import jadx.core.dex.visitors.MarkMethodsForInline;
|
import jadx.core.dex.visitors.MarkMethodsForInline;
|
||||||
import jadx.core.dex.visitors.MethodInvokeVisitor;
|
import jadx.core.dex.visitors.MethodInvokeVisitor;
|
||||||
|
import jadx.core.dex.visitors.MethodVisitor;
|
||||||
import jadx.core.dex.visitors.ModVisitor;
|
import jadx.core.dex.visitors.ModVisitor;
|
||||||
import jadx.core.dex.visitors.MoveInlineVisitor;
|
import jadx.core.dex.visitors.MoveInlineVisitor;
|
||||||
import jadx.core.dex.visitors.OverrideMethodVisitor;
|
import jadx.core.dex.visitors.OverrideMethodVisitor;
|
||||||
import jadx.core.dex.visitors.PrepareForCodeGen;
|
import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||||
import jadx.core.dex.visitors.ProcessAnonymous;
|
import jadx.core.dex.visitors.ProcessAnonymous;
|
||||||
import jadx.core.dex.visitors.ProcessInstructionsVisitor;
|
import jadx.core.dex.visitors.ProcessInstructionsVisitor;
|
||||||
|
import jadx.core.dex.visitors.ProcessMethodsForInline;
|
||||||
import jadx.core.dex.visitors.ReSugarCode;
|
import jadx.core.dex.visitors.ReSugarCode;
|
||||||
import jadx.core.dex.visitors.RenameVisitor;
|
|
||||||
import jadx.core.dex.visitors.ShadowFieldVisitor;
|
import jadx.core.dex.visitors.ShadowFieldVisitor;
|
||||||
import jadx.core.dex.visitors.SignatureProcessor;
|
import jadx.core.dex.visitors.SignatureProcessor;
|
||||||
import jadx.core.dex.visitors.SimplifyVisitor;
|
import jadx.core.dex.visitors.SimplifyVisitor;
|
||||||
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
|
import jadx.core.dex.visitors.blocks.BlockProcessor;
|
||||||
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
|
import jadx.core.dex.visitors.blocks.BlockSplitter;
|
||||||
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
|
|
||||||
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
|
|
||||||
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
||||||
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
|
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
|
||||||
|
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
|
||||||
|
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
|
||||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||||
import jadx.core.dex.visitors.regions.CleanRegions;
|
import jadx.core.dex.visitors.regions.CleanRegions;
|
||||||
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||||
@@ -53,10 +58,14 @@ import jadx.core.dex.visitors.regions.LoopRegionVisitor;
|
|||||||
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
|
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
|
||||||
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
||||||
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
||||||
|
import jadx.core.dex.visitors.rename.CodeRenameVisitor;
|
||||||
|
import jadx.core.dex.visitors.rename.RenameVisitor;
|
||||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||||
|
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
|
||||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||||
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public class Jadx {
|
public class Jadx {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||||
@@ -64,66 +73,76 @@ public class Jadx {
|
|||||||
private Jadx() {
|
private Jadx() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
||||||
if (Consts.DEBUG) {
|
switch (args.getDecompilationMode()) {
|
||||||
LOG.info("debug enabled");
|
case AUTO:
|
||||||
|
case RESTRUCTURE:
|
||||||
|
return getRegionsModePasses(args);
|
||||||
|
case SIMPLE:
|
||||||
|
return getSimpleModePasses(args);
|
||||||
|
case FALLBACK:
|
||||||
|
return getFallbackPassesList();
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unknown decompilation mode: " + args.getDecompilationMode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<IDexTreeVisitor> getFallbackPassesList() {
|
|
||||||
List<IDexTreeVisitor> passes = new ArrayList<>(3);
|
|
||||||
passes.add(new AttachTryCatchVisitor());
|
|
||||||
passes.add(new ProcessInstructionsVisitor());
|
|
||||||
passes.add(new FallbackModeVisitor());
|
|
||||||
return passes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
|
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
|
||||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
passes.add(new SignatureProcessor());
|
passes.add(new SignatureProcessor());
|
||||||
|
passes.add(new OverrideMethodVisitor());
|
||||||
passes.add(new RenameVisitor());
|
passes.add(new RenameVisitor());
|
||||||
passes.add(new UsageInfoVisitor());
|
passes.add(new UsageInfoVisitor());
|
||||||
|
passes.add(new ProcessAnonymous());
|
||||||
|
passes.add(new ProcessMethodsForInline());
|
||||||
return passes;
|
return passes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
public static List<IDexTreeVisitor> getRegionsModePasses(JadxArgs args) {
|
||||||
if (args.isFallbackMode()) {
|
|
||||||
return getFallbackPassesList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
|
// instructions IR
|
||||||
|
passes.add(new CheckCode());
|
||||||
if (args.isDebugInfo()) {
|
if (args.isDebugInfo()) {
|
||||||
passes.add(new DebugInfoAttachVisitor());
|
passes.add(new DebugInfoAttachVisitor());
|
||||||
}
|
}
|
||||||
passes.add(new AttachTryCatchVisitor());
|
passes.add(new AttachTryCatchVisitor());
|
||||||
|
if (args.getCommentsLevel() != CommentsLevel.NONE) {
|
||||||
|
passes.add(new AttachCommentsVisitor());
|
||||||
|
}
|
||||||
|
passes.add(new AttachMethodDetails());
|
||||||
passes.add(new ProcessInstructionsVisitor());
|
passes.add(new ProcessInstructionsVisitor());
|
||||||
|
|
||||||
|
// blocks IR
|
||||||
passes.add(new BlockSplitter());
|
passes.add(new BlockSplitter());
|
||||||
|
passes.add(new BlockProcessor());
|
||||||
if (args.isRawCFGOutput()) {
|
if (args.isRawCFGOutput()) {
|
||||||
passes.add(DotGraphVisitor.dumpRaw());
|
passes.add(DotGraphVisitor.dumpRaw());
|
||||||
}
|
}
|
||||||
passes.add(new BlockProcessor());
|
|
||||||
passes.add(new BlockExceptionHandler());
|
|
||||||
passes.add(new BlockFinish());
|
|
||||||
|
|
||||||
passes.add(new AttachMethodDetails());
|
|
||||||
passes.add(new OverrideMethodVisitor());
|
|
||||||
|
|
||||||
passes.add(new SSATransform());
|
passes.add(new SSATransform());
|
||||||
passes.add(new MoveInlineVisitor());
|
passes.add(new MoveInlineVisitor());
|
||||||
passes.add(new ConstructorVisitor());
|
passes.add(new ConstructorVisitor());
|
||||||
passes.add(new InitCodeVariables());
|
passes.add(new InitCodeVariables());
|
||||||
passes.add(new MarkFinallyVisitor());
|
if (args.isExtractFinally()) {
|
||||||
|
passes.add(new MarkFinallyVisitor());
|
||||||
|
}
|
||||||
passes.add(new ConstInlineVisitor());
|
passes.add(new ConstInlineVisitor());
|
||||||
passes.add(new TypeInferenceVisitor());
|
passes.add(new TypeInferenceVisitor());
|
||||||
if (args.isDebugInfo()) {
|
if (args.isDebugInfo()) {
|
||||||
passes.add(new DebugInfoApplyVisitor());
|
passes.add(new DebugInfoApplyVisitor());
|
||||||
}
|
}
|
||||||
|
passes.add(new FinishTypeInference());
|
||||||
passes.add(new InlineMethods());
|
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
|
||||||
|
passes.add(new ProcessKotlinInternals());
|
||||||
|
}
|
||||||
|
passes.add(new CodeRenameVisitor());
|
||||||
|
if (args.isInlineMethods()) {
|
||||||
|
passes.add(new InlineMethods());
|
||||||
|
}
|
||||||
passes.add(new GenericTypesVisitor());
|
passes.add(new GenericTypesVisitor());
|
||||||
passes.add(new ShadowFieldVisitor());
|
passes.add(new ShadowFieldVisitor());
|
||||||
passes.add(new DeboxingVisitor());
|
passes.add(new DeboxingVisitor());
|
||||||
|
passes.add(new AnonymousClassVisitor());
|
||||||
passes.add(new ModVisitor());
|
passes.add(new ModVisitor());
|
||||||
passes.add(new CodeShrinkVisitor());
|
passes.add(new CodeShrinkVisitor());
|
||||||
passes.add(new ReSugarCode());
|
passes.add(new ReSugarCode());
|
||||||
@@ -131,6 +150,7 @@ public class Jadx {
|
|||||||
passes.add(DotGraphVisitor.dump());
|
passes.add(DotGraphVisitor.dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// regions IR
|
||||||
passes.add(new RegionMakerVisitor());
|
passes.add(new RegionMakerVisitor());
|
||||||
passes.add(new IfRegionVisitor());
|
passes.add(new IfRegionVisitor());
|
||||||
passes.add(new ReturnVisitor());
|
passes.add(new ReturnVisitor());
|
||||||
@@ -144,12 +164,12 @@ public class Jadx {
|
|||||||
passes.add(new EnumVisitor());
|
passes.add(new EnumVisitor());
|
||||||
passes.add(new ExtractFieldInit());
|
passes.add(new ExtractFieldInit());
|
||||||
passes.add(new FixAccessModifiers());
|
passes.add(new FixAccessModifiers());
|
||||||
passes.add(new ProcessAnonymous());
|
|
||||||
passes.add(new ClassModifier());
|
passes.add(new ClassModifier());
|
||||||
passes.add(new LoopRegionVisitor());
|
passes.add(new LoopRegionVisitor());
|
||||||
|
|
||||||
passes.add(new MarkMethodsForInline());
|
if (args.isInlineMethods()) {
|
||||||
|
passes.add(new MarkMethodsForInline());
|
||||||
|
}
|
||||||
passes.add(new ProcessVariables());
|
passes.add(new ProcessVariables());
|
||||||
passes.add(new PrepareForCodeGen());
|
passes.add(new PrepareForCodeGen());
|
||||||
if (args.isCfgOutput()) {
|
if (args.isCfgOutput()) {
|
||||||
@@ -158,7 +178,77 @@ public class Jadx {
|
|||||||
return passes;
|
return passes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<IDexTreeVisitor> getSimpleModePasses(JadxArgs args) {
|
||||||
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
|
if (args.isDebugInfo()) {
|
||||||
|
passes.add(new DebugInfoAttachVisitor());
|
||||||
|
}
|
||||||
|
passes.add(new AttachTryCatchVisitor());
|
||||||
|
if (args.getCommentsLevel() != CommentsLevel.NONE) {
|
||||||
|
passes.add(new AttachCommentsVisitor());
|
||||||
|
}
|
||||||
|
passes.add(new AttachMethodDetails());
|
||||||
|
passes.add(new ProcessInstructionsVisitor());
|
||||||
|
|
||||||
|
passes.add(new BlockSplitter());
|
||||||
|
passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK)));
|
||||||
|
if (args.isRawCFGOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dumpRaw());
|
||||||
|
}
|
||||||
|
passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK)));
|
||||||
|
passes.add(new BlockProcessor());
|
||||||
|
passes.add(new SSATransform());
|
||||||
|
passes.add(new MoveInlineVisitor());
|
||||||
|
passes.add(new ConstructorVisitor());
|
||||||
|
passes.add(new InitCodeVariables());
|
||||||
|
passes.add(new ConstInlineVisitor());
|
||||||
|
passes.add(new TypeInferenceVisitor());
|
||||||
|
if (args.isDebugInfo()) {
|
||||||
|
passes.add(new DebugInfoApplyVisitor());
|
||||||
|
}
|
||||||
|
passes.add(new FinishTypeInference());
|
||||||
|
passes.add(new CodeRenameVisitor());
|
||||||
|
passes.add(new DeboxingVisitor());
|
||||||
|
passes.add(new ModVisitor());
|
||||||
|
passes.add(new CodeShrinkVisitor());
|
||||||
|
passes.add(new ReSugarCode());
|
||||||
|
passes.add(new CodeShrinkVisitor());
|
||||||
|
passes.add(new SimplifyVisitor());
|
||||||
|
passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE)));
|
||||||
|
if (args.isRawCFGOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dumpRaw());
|
||||||
|
}
|
||||||
|
if (args.isCfgOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dump());
|
||||||
|
}
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<IDexTreeVisitor> getFallbackPassesList() {
|
||||||
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
|
passes.add(new AttachTryCatchVisitor());
|
||||||
|
passes.add(new AttachCommentsVisitor());
|
||||||
|
passes.add(new ProcessInstructionsVisitor());
|
||||||
|
passes.add(new FallbackModeVisitor());
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String VERSION_DEV = "dev";
|
||||||
|
|
||||||
|
private static String version;
|
||||||
|
|
||||||
public static String getVersion() {
|
public static String getVersion() {
|
||||||
|
if (version == null) {
|
||||||
|
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) {
|
||||||
@@ -176,6 +266,6 @@ public class Jadx {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Can't get manifest file", e);
|
LOG.error("Can't get manifest file", e);
|
||||||
}
|
}
|
||||||
return "dev";
|
return VERSION_DEV;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
package jadx.core;
|
package jadx.core;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
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.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
import jadx.core.codegen.CodeGen;
|
import jadx.core.codegen.CodeGen;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.LoadStage;
|
import jadx.core.dex.nodes.LoadStage;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.visitors.DepthTraversal;
|
import jadx.core.dex.visitors.DepthTraversal;
|
||||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
@@ -18,13 +24,17 @@ import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
|||||||
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
||||||
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
|
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
|
||||||
|
|
||||||
public final class ProcessClass {
|
public class ProcessClass {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
|
||||||
|
|
||||||
private ProcessClass() {
|
private final List<IDexTreeVisitor> passes;
|
||||||
|
|
||||||
|
public ProcessClass(JadxArgs args) {
|
||||||
|
this.passes = Jadx.getPassesList(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static ICodeInfo process(ClassNode cls, boolean codegen) {
|
private ICodeInfo process(ClassNode cls, boolean codegen) {
|
||||||
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
|
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return null;
|
return null;
|
||||||
@@ -33,15 +43,18 @@ public final class ProcessClass {
|
|||||||
try {
|
try {
|
||||||
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
|
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
|
||||||
cls.remove(AFlag.CLASS_DEEP_RELOAD);
|
cls.remove(AFlag.CLASS_DEEP_RELOAD);
|
||||||
cls.unload();
|
|
||||||
cls.deepUnload();
|
cls.deepUnload();
|
||||||
|
cls.add(AFlag.CLASS_UNLOADED);
|
||||||
|
}
|
||||||
|
if (cls.contains(AFlag.CLASS_UNLOADED)) {
|
||||||
cls.root().runPreDecompileStageForClass(cls);
|
cls.root().runPreDecompileStageForClass(cls);
|
||||||
|
cls.remove(AFlag.CLASS_UNLOADED);
|
||||||
|
}
|
||||||
|
if (cls.getState() == GENERATED_AND_UNLOADED) {
|
||||||
|
// force loading code again
|
||||||
|
cls.setState(NOT_LOADED);
|
||||||
}
|
}
|
||||||
if (codegen) {
|
if (codegen) {
|
||||||
if (cls.getState() == GENERATED_AND_UNLOADED) {
|
|
||||||
// allow to run code generation again
|
|
||||||
cls.setState(NOT_LOADED);
|
|
||||||
}
|
|
||||||
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
|
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
|
||||||
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
|
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
|
||||||
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
|
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
|
||||||
@@ -55,7 +68,7 @@ public final class ProcessClass {
|
|||||||
}
|
}
|
||||||
if (cls.getState() == LOADED) {
|
if (cls.getState() == LOADED) {
|
||||||
cls.setState(PROCESS_STARTED);
|
cls.setState(PROCESS_STARTED);
|
||||||
for (IDexTreeVisitor visitor : cls.root().getPasses()) {
|
for (IDexTreeVisitor visitor : passes) {
|
||||||
DepthTraversal.visit(visitor, cls);
|
DepthTraversal.visit(visitor, cls);
|
||||||
}
|
}
|
||||||
cls.setState(PROCESS_COMPLETE);
|
cls.setState(PROCESS_COMPLETE);
|
||||||
@@ -68,23 +81,37 @@ public final class ProcessClass {
|
|||||||
}
|
}
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
if (codegen) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
cls.addError("Class process error: " + e.getClass().getSimpleName(), e);
|
cls.addError("Class process error: " + e.getClass().getSimpleName(), e);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static ICodeInfo generateCode(ClassNode cls) {
|
public ICodeInfo generateCode(ClassNode cls) {
|
||||||
ClassNode topParentClass = cls.getTopParentClass();
|
ClassNode topParentClass = cls.getTopParentClass();
|
||||||
if (topParentClass != cls) {
|
if (topParentClass != cls) {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
if (!cls.getCodegenDeps().isEmpty()) {
|
||||||
|
process(cls, false);
|
||||||
|
for (ClassNode codegenDep : cls.getCodegenDeps()) {
|
||||||
|
process(codegenDep, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
ICodeInfo code = process(cls, true);
|
ICodeInfo code = process(cls, true);
|
||||||
if (code == null) {
|
if (code == null) {
|
||||||
throw new JadxRuntimeException("Codegen failed");
|
throw new JadxRuntimeException("Codegen failed");
|
||||||
@@ -94,4 +121,19 @@ public final class ProcessClass {
|
|||||||
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
|
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void initPasses(RootNode root) {
|
||||||
|
for (IDexTreeVisitor pass : passes) {
|
||||||
|
try {
|
||||||
|
pass.init(root);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make passes list private and not visible
|
||||||
|
public List<IDexTreeVisitor> getPasses() {
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ import jadx.core.utils.exceptions.DecodeException;
|
|||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
import static jadx.core.utils.Utils.notEmpty;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Classes list for import into classpath graph
|
* Classes list for import into classpath graph
|
||||||
*/
|
*/
|
||||||
@@ -49,7 +47,7 @@ public class ClsSet {
|
|||||||
|
|
||||||
private static final String CLST_EXTENSION = ".jcst";
|
private static final String CLST_EXTENSION = ".jcst";
|
||||||
private static final String CLST_FILENAME = "core" + CLST_EXTENSION;
|
private static final String CLST_FILENAME = "core" + CLST_EXTENSION;
|
||||||
private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/');
|
private static final String CLST_PATH = "/clst/" + CLST_FILENAME;
|
||||||
|
|
||||||
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
|
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
|
||||||
private static final int VERSION = 3;
|
private static final int VERSION = 3;
|
||||||
@@ -78,9 +76,9 @@ public class ClsSet {
|
|||||||
|
|
||||||
public void loadFromClstFile() throws IOException, DecodeException {
|
public void loadFromClstFile() throws IOException, DecodeException {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
|
try (InputStream input = ClsSet.class.getResourceAsStream(CLST_PATH)) {
|
||||||
if (input == null) {
|
if (input == null) {
|
||||||
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
|
throw new JadxRuntimeException("Can't load classpath file: " + CLST_PATH);
|
||||||
}
|
}
|
||||||
load(input);
|
load(input);
|
||||||
}
|
}
|
||||||
@@ -131,25 +129,13 @@ public class ClsSet {
|
|||||||
|
|
||||||
private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) {
|
private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) {
|
||||||
AccessInfo accessFlags = mth.getAccessFlags();
|
AccessInfo accessFlags = mth.getAccessFlags();
|
||||||
if (accessFlags.isPrivate()) {
|
if (accessFlags.isPrivate() || accessFlags.isSynthetic() || accessFlags.isBridge()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ArgType genericRetType = mth.getReturnType();
|
ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(), mth.getArgTypes(),
|
||||||
boolean varArgs = accessFlags.isVarArgs();
|
mth.getReturnType(), mth.getTypeParameters(),
|
||||||
List<ArgType> throwList = mth.getThrows();
|
mth.getThrows(), accessFlags.rawValue());
|
||||||
List<ArgType> typeParameters = mth.getTypeParameters();
|
methods.add(clspMethod);
|
||||||
// add only methods with additional info
|
|
||||||
if (varArgs
|
|
||||||
|| notEmpty(throwList)
|
|
||||||
|| notEmpty(typeParameters)
|
|
||||||
|| genericRetType.containsGeneric()
|
|
||||||
|| mth.containsGenericArgs()
|
|
||||||
|| mth.isArgsOverloaded()) {
|
|
||||||
ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(),
|
|
||||||
mth.getArgTypes(), genericRetType,
|
|
||||||
typeParameters, varArgs, throwList);
|
|
||||||
methods.add(clspMethod);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArgType[] makeParentsArray(ClassNode cls) {
|
public static ArgType[] makeParentsArray(ClassNode cls) {
|
||||||
@@ -197,7 +183,7 @@ public class ClsSet {
|
|||||||
|
|
||||||
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
|
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
|
||||||
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
|
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
|
||||||
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME;
|
String clst = CLST_PATH;
|
||||||
boolean clstReplaced = false;
|
boolean clstReplaced = false;
|
||||||
ZipEntry entry = in.getNextEntry();
|
ZipEntry entry = in.getNextEntry();
|
||||||
while (entry != null) {
|
while (entry != null) {
|
||||||
@@ -245,7 +231,7 @@ public class ClsSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
int methodsCount = Stream.of(classes).mapToInt(c -> c.getMethodsMap().size()).sum();
|
int methodsCount = Stream.of(classes).mapToInt(c -> c.getMethodsMap().size()).sum();
|
||||||
LOG.info("Classes: {}, methods: {}, file size: {}B", classes.length, methodsCount, out.size());
|
LOG.info("Classes: {}, methods: {}, file size: {} bytes", classes.length, methodsCount, out.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void writeMethod(DataOutputStream out, ClspMethod method, Map<String, ClspClass> names) throws IOException {
|
private static void writeMethod(DataOutputStream out, ClspMethod method, Map<String, ClspClass> names) throws IOException {
|
||||||
@@ -257,7 +243,7 @@ public class ClsSet {
|
|||||||
writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names);
|
writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names);
|
||||||
writeArgType(out, method.getReturnType(), names);
|
writeArgType(out, method.getReturnType(), names);
|
||||||
writeArgTypesList(out, method.getTypeParameters(), names);
|
writeArgTypesList(out, method.getTypeParameters(), names);
|
||||||
out.writeBoolean(method.isVarArg());
|
out.writeInt(method.getRawAccessFlags());
|
||||||
writeArgTypesList(out, method.getThrows(), names);
|
writeArgTypesList(out, method.getThrows(), names);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,12 +378,12 @@ public class ClsSet {
|
|||||||
genericRetType = retType;
|
genericRetType = retType;
|
||||||
}
|
}
|
||||||
List<ArgType> typeParameters = readArgTypesList(in);
|
List<ArgType> typeParameters = readArgTypesList(in);
|
||||||
boolean varArgs = in.readBoolean();
|
int accFlags = in.readInt();
|
||||||
List<ArgType> throwList = readArgTypesList(in);
|
List<ArgType> throwList = readArgTypesList(in);
|
||||||
MethodInfo methodInfo = MethodInfo.fromDetails(root, clsInfo, name, argTypes, retType);
|
MethodInfo methodInfo = MethodInfo.fromDetails(root, clsInfo, name, argTypes, retType);
|
||||||
return new ClspMethod(methodInfo,
|
return new ClspMethod(methodInfo,
|
||||||
genericArgTypes, genericRetType,
|
genericArgTypes, genericRetType,
|
||||||
typeParameters, varArgs, throwList);
|
typeParameters, throwList, accFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ArgType> readArgTypesList(DataInputStream in) throws IOException {
|
private List<ArgType> readArgTypesList(DataInputStream in) throws IOException {
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.WeakHashMap;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -30,8 +28,9 @@ public class ClspGraph {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
||||||
|
|
||||||
private final RootNode root;
|
private final RootNode root;
|
||||||
private final Map<String, Set<String>> superTypesCache = Collections.synchronizedMap(new WeakHashMap<>());
|
|
||||||
private Map<String, ClspClass> nameMap;
|
private Map<String, ClspClass> nameMap;
|
||||||
|
private Map<String, Set<String>> superTypesCache;
|
||||||
|
private Map<String, List<String>> implementsCache;
|
||||||
|
|
||||||
private final Set<String> missingClasses = new HashSet<>();
|
private final Set<String> missingClasses = new HashSet<>();
|
||||||
|
|
||||||
@@ -63,6 +62,11 @@ public class ClspGraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void initCache() {
|
||||||
|
fillSuperTypesCache();
|
||||||
|
fillImplementsCache();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isClsKnown(String fullName) {
|
public boolean isClsKnown(String fullName) {
|
||||||
return nameMap.containsKey(fullName);
|
return nameMap.containsKey(fullName);
|
||||||
}
|
}
|
||||||
@@ -91,7 +95,7 @@ public class ClspGraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// all other methods in known ClspClass are 'simple'
|
// unknown method
|
||||||
return new SimpleMethodDetails(methodInfo);
|
return new SimpleMethodDetails(methodInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,13 +120,20 @@ public class ClspGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getImplementations(String clsName) {
|
public List<String> getImplementations(String clsName) {
|
||||||
List<String> list = new ArrayList<>();
|
List<String> list = implementsCache.get(clsName);
|
||||||
for (String cls : nameMap.keySet()) {
|
return list == null ? Collections.emptyList() : list;
|
||||||
if (isImplements(cls, clsName)) {
|
}
|
||||||
list.add(cls);
|
|
||||||
|
private void fillImplementsCache() {
|
||||||
|
Map<String, List<String>> map = new HashMap<>(nameMap.size());
|
||||||
|
List<String> classes = new ArrayList<>(nameMap.keySet());
|
||||||
|
Collections.sort(classes);
|
||||||
|
for (String cls : classes) {
|
||||||
|
for (String st : getSuperTypes(cls)) {
|
||||||
|
map.computeIfAbsent(st, v -> new ArrayList<>()).add(cls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list;
|
implementsCache = map;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCommonAncestor(String clsName, String implClsName) {
|
public String getCommonAncestor(String clsName, String implClsName) {
|
||||||
@@ -159,29 +170,26 @@ public class ClspGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> getSuperTypes(String clsName) {
|
public Set<String> getSuperTypes(String clsName) {
|
||||||
Set<String> fromCache = superTypesCache.get(clsName);
|
Set<String> result = superTypesCache.get(clsName);
|
||||||
if (fromCache != null) {
|
return result == null ? Collections.emptySet() : result;
|
||||||
return fromCache;
|
|
||||||
}
|
|
||||||
ClspClass cls = nameMap.get(clsName);
|
|
||||||
if (cls == null) {
|
|
||||||
missingClasses.add(clsName);
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
Set<String> result = new HashSet<>();
|
|
||||||
addSuperTypes(cls, result);
|
|
||||||
return putInSuperTypesCache(clsName, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
private void fillSuperTypesCache() {
|
||||||
private Set<String> putInSuperTypesCache(String clsName, Set<String> result) {
|
Map<String, Set<String>> map = new HashMap<>(nameMap.size());
|
||||||
if (result.isEmpty()) {
|
Set<String> tmpSet = new HashSet<>();
|
||||||
Set<String> empty = Collections.emptySet();
|
for (Map.Entry<String, ClspClass> entry : nameMap.entrySet()) {
|
||||||
superTypesCache.put(clsName, result);
|
ClspClass cls = entry.getValue();
|
||||||
return empty;
|
tmpSet.clear();
|
||||||
|
addSuperTypes(cls, tmpSet);
|
||||||
|
Set<String> result;
|
||||||
|
if (tmpSet.isEmpty()) {
|
||||||
|
result = Collections.emptySet();
|
||||||
|
} else {
|
||||||
|
result = new HashSet<>(tmpSet);
|
||||||
|
}
|
||||||
|
map.put(cls.getName(), result);
|
||||||
}
|
}
|
||||||
superTypesCache.put(clsName, result);
|
superTypesCache = map;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSuperTypes(ClspClass cls, Set<String> result) {
|
private void addSuperTypes(ClspClass cls, Set<String> result) {
|
||||||
@@ -195,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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,9 +214,7 @@ public class ClspGraph {
|
|||||||
private ClspClass getClspClass(ArgType clsType) {
|
private ClspClass getClspClass(ArgType clsType) {
|
||||||
ClspClass clspClass = nameMap.get(clsType.getObject());
|
ClspClass clspClass = nameMap.get(clsType.getObject());
|
||||||
if (clspClass == null) {
|
if (clspClass == null) {
|
||||||
if (LOG.isDebugEnabled()) {
|
missingClasses.add(clsType.getObject());
|
||||||
LOG.debug("External class not found: {}", clsType.getObject());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return clspClass;
|
return clspClass;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.IMethodDetails;
|
import jadx.core.dex.nodes.IMethodDetails;
|
||||||
@@ -20,18 +21,17 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
|
|||||||
private final ArgType returnType;
|
private final ArgType returnType;
|
||||||
private final List<ArgType> typeParameters;
|
private final List<ArgType> typeParameters;
|
||||||
private final List<ArgType> throwList;
|
private final List<ArgType> throwList;
|
||||||
private final boolean varArg;
|
private final int accFlags;
|
||||||
|
|
||||||
public ClspMethod(MethodInfo methodInfo,
|
public ClspMethod(MethodInfo methodInfo,
|
||||||
List<ArgType> argTypes, ArgType returnType,
|
List<ArgType> argTypes, ArgType returnType,
|
||||||
List<ArgType> typeParameters,
|
List<ArgType> typeParameters, List<ArgType> throwList, int accFlags) {
|
||||||
boolean varArgs, List<ArgType> throwList) {
|
|
||||||
this.methodInfo = methodInfo;
|
this.methodInfo = methodInfo;
|
||||||
this.argTypes = argTypes;
|
this.argTypes = argTypes;
|
||||||
this.returnType = returnType;
|
this.returnType = returnType;
|
||||||
this.typeParameters = typeParameters;
|
this.typeParameters = typeParameters;
|
||||||
this.throwList = throwList;
|
this.throwList = throwList;
|
||||||
this.varArg = varArgs;
|
this.accFlags = accFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -69,7 +69,12 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isVarArg() {
|
public boolean isVarArg() {
|
||||||
return varArg;
|
return (accFlags & AccessFlags.VARARGS) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRawAccessFlags() {
|
||||||
|
return accFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -94,6 +99,11 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
|
|||||||
return this.methodInfo.compareTo(other.methodInfo);
|
return this.methodInfo.compareTo(other.methodInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toAttrString() {
|
||||||
|
return IMethodDetails.super.toAttrString() + " (c)";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|||||||
@@ -3,10 +3,15 @@ package jadx.core.clsp;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.IMethodDetails;
|
import jadx.core.dex.nodes.IMethodDetails;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method details build from MethodInfo.
|
||||||
|
* Note: some fields have unknown values.
|
||||||
|
*/
|
||||||
public class SimpleMethodDetails implements IMethodDetails {
|
public class SimpleMethodDetails implements IMethodDetails {
|
||||||
|
|
||||||
private final MethodInfo methodInfo;
|
private final MethodInfo methodInfo;
|
||||||
@@ -45,6 +50,16 @@ public class SimpleMethodDetails implements IMethodDetails {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRawAccessFlags() {
|
||||||
|
return AccessFlags.PUBLIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toAttrString() {
|
||||||
|
return IMethodDetails.super.toAttrString() + " (s)";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "SimpleMethodDetails{" + methodInfo + '}';
|
return "SimpleMethodDetails{" + methodInfo + '}';
|
||||||
|
|||||||
@@ -7,14 +7,16 @@ import java.util.Map.Entry;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.IFieldData;
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.plugins.input.data.IFieldRef;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||||
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
|
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr;
|
||||||
|
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
|
||||||
|
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.attributes.AType;
|
|
||||||
import jadx.core.dex.attributes.IAttributeNode;
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
|
||||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
@@ -34,24 +36,24 @@ public class AnnotationGen {
|
|||||||
this.classGen = classGen;
|
this.classGen = classGen;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addForClass(CodeWriter code) {
|
public void addForClass(ICodeWriter code) {
|
||||||
add(cls, code);
|
add(cls, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addForMethod(CodeWriter code, MethodNode mth) {
|
public void addForMethod(ICodeWriter code, MethodNode mth) {
|
||||||
add(mth, code);
|
add(mth, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addForField(CodeWriter code, FieldNode field) {
|
public void addForField(ICodeWriter code, FieldNode field) {
|
||||||
add(field, code);
|
add(field, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
|
public void addForParameter(ICodeWriter code, AnnotationMethodParamsAttr paramsAnnotations, int n) {
|
||||||
List<AnnotationsList> paramList = paramsAnnotations.getParamList();
|
List<AnnotationsAttr> paramList = paramsAnnotations.getParamList();
|
||||||
if (n >= paramList.size()) {
|
if (n >= paramList.size()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AnnotationsList aList = paramList.get(n);
|
AnnotationsAttr aList = paramList.get(n);
|
||||||
if (aList == null || aList.isEmpty()) {
|
if (aList == null || aList.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -61,21 +63,21 @@ public class AnnotationGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void add(IAttributeNode node, CodeWriter code) {
|
private void add(IAttributeNode node, ICodeWriter code) {
|
||||||
AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
|
AnnotationsAttr aList = node.get(JadxAttrType.ANNOTATION_LIST);
|
||||||
if (aList == null || aList.isEmpty()) {
|
if (aList == null || aList.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (IAnnotation a : aList.getAll()) {
|
for (IAnnotation a : aList.getAll()) {
|
||||||
String aCls = a.getAnnotationClass();
|
String aCls = a.getAnnotationClass();
|
||||||
if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
|
if (!aCls.equals(Consts.OVERRIDE_ANNOTATION)) {
|
||||||
code.startLine();
|
code.startLine();
|
||||||
formatAnnotation(code, a);
|
formatAnnotation(code, a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void formatAnnotation(CodeWriter code, IAnnotation a) {
|
private void formatAnnotation(ICodeWriter code, IAnnotation a) {
|
||||||
code.add('@');
|
code.add('@');
|
||||||
ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass());
|
ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass());
|
||||||
if (annCls != null) {
|
if (annCls != null) {
|
||||||
@@ -116,7 +118,7 @@ public class AnnotationGen {
|
|||||||
return paramName;
|
return paramName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addThrows(MethodNode mth, CodeWriter code) {
|
public void addThrows(MethodNode mth, ICodeWriter code) {
|
||||||
List<ArgType> throwList = mth.getThrows();
|
List<ArgType> throwList = mth.getThrows();
|
||||||
if (!throwList.isEmpty()) {
|
if (!throwList.isEmpty()) {
|
||||||
code.add(" throws ");
|
code.add(" throws ");
|
||||||
@@ -130,20 +132,16 @@ public class AnnotationGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public EncodedValue getAnnotationDefaultValue(String name) {
|
public EncodedValue getAnnotationDefaultValue(MethodNode mth) {
|
||||||
IAnnotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
|
AnnotationDefaultAttr defaultAttr = mth.get(JadxAttrType.ANNOTATION_DEFAULT);
|
||||||
if (an != null) {
|
if (defaultAttr == null) {
|
||||||
EncodedValue defValue = an.getDefaultValue();
|
return null;
|
||||||
if (defValue != null) {
|
|
||||||
IAnnotation defAnnotation = (IAnnotation) defValue.getValue();
|
|
||||||
return defAnnotation.getValues().get(name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return defaultAttr.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refactor this boilerplate code
|
// TODO: refactor this boilerplate code
|
||||||
public void encodeValue(RootNode root, CodeWriter code, EncodedValue encodedValue) {
|
public void encodeValue(RootNode root, ICodeWriter code, EncodedValue encodedValue) {
|
||||||
if (encodedValue == null) {
|
if (encodedValue == null) {
|
||||||
code.add("null");
|
code.add("null");
|
||||||
return;
|
return;
|
||||||
@@ -187,9 +185,9 @@ public class AnnotationGen {
|
|||||||
case ENCODED_ENUM:
|
case ENCODED_ENUM:
|
||||||
case ENCODED_FIELD:
|
case ENCODED_FIELD:
|
||||||
// must be a static field
|
// must be a static field
|
||||||
if (value instanceof IFieldData) {
|
if (value instanceof IFieldRef) {
|
||||||
FieldInfo field = FieldInfo.fromData(root, (IFieldData) value);
|
FieldInfo fieldInfo = FieldInfo.fromRef(root, (IFieldRef) value);
|
||||||
InsnGen.makeStaticFieldAccess(code, field, classGen);
|
InsnGen.makeStaticFieldAccess(code, fieldInfo, classGen);
|
||||||
} else if (value instanceof FieldInfo) {
|
} else if (value instanceof FieldInfo) {
|
||||||
InsnGen.makeStaticFieldAccess(code, (FieldInfo) value, classGen);
|
InsnGen.makeStaticFieldAccess(code, (FieldInfo) value, classGen);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,29 +9,33 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
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;
|
||||||
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
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.AttrNode;
|
import jadx.core.dex.attributes.FieldInitInsnAttr;
|
||||||
import jadx.core.dex.attributes.FieldInitAttr;
|
|
||||||
import jadx.core.dex.attributes.FieldInitAttr.InitType;
|
|
||||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||||
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
||||||
import jadx.core.dex.attributes.nodes.JadxError;
|
|
||||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
@@ -40,8 +44,9 @@ 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.CodeGenUtils;
|
import jadx.core.utils.CodeGenUtils;
|
||||||
import jadx.core.utils.ErrorsCounter;
|
import jadx.core.utils.EncodedValueUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
import jadx.core.utils.android.AndroidResourcesUtils;
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
@@ -55,10 +60,13 @@ public class ClassGen {
|
|||||||
private final boolean showInconsistentCode;
|
private final boolean showInconsistentCode;
|
||||||
|
|
||||||
private final Set<ClassInfo> imports = new HashSet<>();
|
private final Set<ClassInfo> imports = new HashSet<>();
|
||||||
private int clsDeclLine;
|
private int clsDeclOffset;
|
||||||
|
|
||||||
private boolean bodyGenStarted;
|
private boolean bodyGenStarted;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private NameGen outerNameGen;
|
||||||
|
|
||||||
public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
|
public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
|
||||||
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
|
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
|
||||||
}
|
}
|
||||||
@@ -82,10 +90,10 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ICodeInfo makeClass() throws CodegenException {
|
public ICodeInfo makeClass() throws CodegenException {
|
||||||
CodeWriter clsBody = new CodeWriter();
|
ICodeWriter clsBody = cls.root().makeCodeWriter();
|
||||||
addClassCode(clsBody);
|
addClassCode(clsBody);
|
||||||
|
|
||||||
CodeWriter clsCode = new CodeWriter();
|
ICodeWriter clsCode = cls.root().makeCodeWriter();
|
||||||
if (!"".equals(cls.getPackage())) {
|
if (!"".equals(cls.getPackage())) {
|
||||||
clsCode.add("package ").add(cls.getPackage()).add(';');
|
clsCode.add("package ").add(cls.getPackage()).add(';');
|
||||||
clsCode.newLine();
|
clsCode.newLine();
|
||||||
@@ -110,20 +118,20 @@ public class ClassGen {
|
|||||||
return clsCode.finish();
|
return clsCode.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addClassCode(CodeWriter code) throws CodegenException {
|
public void addClassCode(ICodeWriter code) throws CodegenException {
|
||||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Consts.DEBUG_USAGE) {
|
if (Consts.DEBUG_USAGE) {
|
||||||
addClassUsageInfo(code, cls);
|
addClassUsageInfo(code, cls);
|
||||||
}
|
}
|
||||||
CodeGenUtils.addComments(code, cls);
|
CodeGenUtils.addErrorsAndComments(code, cls);
|
||||||
insertDecompilationProblems(code, cls);
|
CodeGenUtils.addSourceFileInfo(code, cls);
|
||||||
addClassDeclaration(code);
|
addClassDeclaration(code);
|
||||||
addClassBody(code);
|
addClassBody(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addClassDeclaration(CodeWriter clsCode) {
|
public void addClassDeclaration(ICodeWriter clsCode) {
|
||||||
AccessInfo af = cls.getAccessFlags();
|
AccessInfo af = cls.getAccessFlags();
|
||||||
if (af.isInterface()) {
|
if (af.isInterface()) {
|
||||||
af = af.remove(AccessFlags.ABSTRACT)
|
af = af.remove(AccessFlags.ABSTRACT)
|
||||||
@@ -141,9 +149,8 @@ public class ClassGen {
|
|||||||
|
|
||||||
annotationGen.addForClass(clsCode);
|
annotationGen.addForClass(clsCode);
|
||||||
insertRenameInfo(clsCode, cls);
|
insertRenameInfo(clsCode, cls);
|
||||||
CodeGenUtils.addSourceFileInfo(clsCode, cls);
|
CodeGenUtils.addInputFileInfo(clsCode, cls);
|
||||||
clsCode.startLineWithNum(cls.getSourceLine());
|
clsCode.startLineWithNum(cls.getSourceLine()).add(af.makeString(cls.checkCommentsLevel(CommentsLevel.INFO)));
|
||||||
clsCode.add(af.makeString());
|
|
||||||
if (af.isInterface()) {
|
if (af.isInterface()) {
|
||||||
if (af.isAnnotation()) {
|
if (af.isAnnotation()) {
|
||||||
clsCode.add('@');
|
clsCode.add('@');
|
||||||
@@ -188,7 +195,7 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addGenericTypeParameters(CodeWriter code, List<ArgType> generics, boolean classDeclaration) {
|
public boolean addGenericTypeParameters(ICodeWriter code, List<ArgType> generics, boolean classDeclaration) {
|
||||||
if (generics == null || generics.isEmpty()) {
|
if (generics == null || generics.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -229,7 +236,7 @@ public class ClassGen {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addClassBody(CodeWriter clsCode) throws CodegenException {
|
public void addClassBody(ICodeWriter clsCode) throws CodegenException {
|
||||||
addClassBody(clsCode, false);
|
addClassBody(clsCode, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,25 +244,24 @@ public class ClassGen {
|
|||||||
* @param printClassName allows to print the original class name as comment (e.g. for inlined
|
* @param printClassName allows to print the original class name as comment (e.g. for inlined
|
||||||
* classes)
|
* classes)
|
||||||
*/
|
*/
|
||||||
public void addClassBody(CodeWriter clsCode, boolean printClassName) throws CodegenException {
|
public void addClassBody(ICodeWriter clsCode, boolean printClassName) throws CodegenException {
|
||||||
clsCode.add('{');
|
clsCode.add('{');
|
||||||
setBodyGenStarted(true);
|
if (printClassName && cls.checkCommentsLevel(CommentsLevel.INFO)) {
|
||||||
clsDeclLine = clsCode.getLine();
|
clsCode.add(" // from class: " + cls.getClassInfo().getFullName());
|
||||||
clsCode.incIndent();
|
|
||||||
if (printClassName) {
|
|
||||||
clsCode.startLine();
|
|
||||||
clsCode.add("/* class " + cls.getFullName() + " */");
|
|
||||||
}
|
}
|
||||||
|
setBodyGenStarted(true);
|
||||||
|
clsDeclOffset = clsCode.getLength();
|
||||||
|
clsCode.incIndent();
|
||||||
addFields(clsCode);
|
addFields(clsCode);
|
||||||
addInnerClsAndMethods(clsCode);
|
addInnerClsAndMethods(clsCode);
|
||||||
clsCode.decIndent();
|
clsCode.decIndent();
|
||||||
clsCode.startLine('}');
|
clsCode.startLine('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addInnerClsAndMethods(CodeWriter clsCode) {
|
private void addInnerClsAndMethods(ICodeWriter clsCode) {
|
||||||
Stream.of(cls.getInnerClasses(), cls.getMethods())
|
Stream.of(cls.getInnerClasses(), cls.getMethods())
|
||||||
.flatMap(Collection::stream)
|
.flatMap(Collection::stream)
|
||||||
.filter(node -> !node.contains(AFlag.DONT_GENERATE))
|
.filter(node -> !node.contains(AFlag.DONT_GENERATE) || fallback)
|
||||||
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
|
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
|
||||||
.forEach(node -> {
|
.forEach(node -> {
|
||||||
if (node instanceof ClassNode) {
|
if (node instanceof ClassNode) {
|
||||||
@@ -266,7 +272,7 @@ public class ClassGen {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addInnerClass(CodeWriter code, ClassNode innerCls) {
|
private void addInnerClass(ICodeWriter code, ClassNode innerCls) {
|
||||||
try {
|
try {
|
||||||
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
|
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
|
||||||
code.newLine();
|
code.newLine();
|
||||||
@@ -279,15 +285,18 @@ public class ClassGen {
|
|||||||
|
|
||||||
private boolean isInnerClassesPresents() {
|
private boolean isInnerClassesPresents() {
|
||||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||||
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
|
if (!innerCls.contains(AType.ANONYMOUS_CLASS)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMethod(CodeWriter code, MethodNode mth) {
|
private void addMethod(ICodeWriter code, MethodNode mth) {
|
||||||
if (code.getLine() != clsDeclLine) {
|
if (skipMethod(mth)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (code.getLength() != clsDeclOffset) {
|
||||||
code.newLine();
|
code.newLine();
|
||||||
}
|
}
|
||||||
int savedIndent = code.getIndent();
|
int savedIndent = code.getIndent();
|
||||||
@@ -297,15 +306,35 @@ public class ClassGen {
|
|||||||
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
|
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
|
||||||
throw new JadxRuntimeException("Method generation error", e);
|
throw new JadxRuntimeException("Method generation error", e);
|
||||||
}
|
}
|
||||||
code.newLine().add("/*");
|
mth.addError("Method generation error", e);
|
||||||
code.newLine().addMultiLine(ErrorsCounter.error(mth, "Method generation error", e));
|
CodeGenUtils.addErrors(code, mth);
|
||||||
Utils.appendStackTrace(code, e);
|
|
||||||
code.newLine().add("*/");
|
|
||||||
code.setIndent(savedIndent);
|
code.setIndent(savedIndent);
|
||||||
mth.addError("Method generation error: " + e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional checks for inlined methods
|
||||||
|
*/
|
||||||
|
private boolean skipMethod(MethodNode mth) {
|
||||||
|
MethodInlineAttr inlineAttr = mth.get(AType.METHOD_INLINE);
|
||||||
|
if (inlineAttr == null || inlineAttr.notNeeded()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (mth.getUseIn().isEmpty()) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isMethodsPresents() {
|
private boolean isMethodsPresents() {
|
||||||
for (MethodNode mth : cls.getMethods()) {
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
if (!mth.contains(AFlag.DONT_GENERATE)) {
|
if (!mth.contains(AFlag.DONT_GENERATE)) {
|
||||||
@@ -315,21 +344,19 @@ public class ClassGen {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addMethodCode(CodeWriter code, MethodNode mth) throws CodegenException {
|
public void addMethodCode(ICodeWriter code, MethodNode mth) throws CodegenException {
|
||||||
CodeGenUtils.addComments(code, mth);
|
CodeGenUtils.addErrorsAndComments(code, mth);
|
||||||
if (mth.isNoCode()) {
|
if (mth.isNoCode()) {
|
||||||
MethodGen mthGen = new MethodGen(this, mth);
|
MethodGen mthGen = new MethodGen(this, mth);
|
||||||
mthGen.addDefinition(code);
|
mthGen.addDefinition(code);
|
||||||
code.add(';');
|
code.add(';');
|
||||||
} else {
|
} else {
|
||||||
insertDecompilationProblems(code, mth);
|
|
||||||
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
|
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
|
||||||
if (badCode && showInconsistentCode) {
|
if (badCode && showInconsistentCode) {
|
||||||
mth.remove(AFlag.INCONSISTENT_CODE);
|
|
||||||
badCode = false;
|
badCode = false;
|
||||||
}
|
}
|
||||||
MethodGen mthGen;
|
MethodGen mthGen;
|
||||||
if (badCode || fallback || mth.contains(AType.JADX_ERROR) || mth.getRegion() == null) {
|
if (badCode || fallback || mth.contains(AType.JADX_ERROR)) {
|
||||||
mthGen = MethodGen.getFallbackMethodGen(mth);
|
mthGen = MethodGen.getFallbackMethodGen(mth);
|
||||||
} else {
|
} else {
|
||||||
mthGen = new MethodGen(this, mth);
|
mthGen = new MethodGen(this, mth);
|
||||||
@@ -345,35 +372,14 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insertDecompilationProblems(CodeWriter code, AttrNode node) {
|
private void addFields(ICodeWriter code) throws CodegenException {
|
||||||
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
errors.stream().distinct().sorted().forEach(err -> {
|
|
||||||
code.startLine("/* JADX ERROR: ").add(err.getError());
|
|
||||||
Throwable cause = err.getCause();
|
|
||||||
if (cause != null) {
|
|
||||||
code.incIndent();
|
|
||||||
Utils.appendStackTrace(code, cause);
|
|
||||||
code.decIndent();
|
|
||||||
}
|
|
||||||
code.add("*/");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
List<String> warns = node.getAll(AType.JADX_WARN);
|
|
||||||
if (!warns.isEmpty()) {
|
|
||||||
warns.stream().distinct().sorted()
|
|
||||||
.forEach(warn -> code.startLine("/* JADX WARNING: ").addMultiLine(warn).add(" */"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addFields(CodeWriter code) throws CodegenException {
|
|
||||||
addEnumFields(code);
|
addEnumFields(code);
|
||||||
for (FieldNode f : cls.getFields()) {
|
for (FieldNode f : cls.getFields()) {
|
||||||
addField(code, f);
|
addField(code, f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addField(CodeWriter code, FieldNode f) {
|
public void addField(ICodeWriter code, FieldNode f) {
|
||||||
if (f.contains(AFlag.DONT_GENERATE)) {
|
if (f.contains(AFlag.DONT_GENERATE)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -383,28 +389,40 @@ public class ClassGen {
|
|||||||
CodeGenUtils.addComments(code, f);
|
CodeGenUtils.addComments(code, f);
|
||||||
annotationGen.addForField(code, f);
|
annotationGen.addForField(code, f);
|
||||||
|
|
||||||
if (f.getFieldInfo().isRenamed()) {
|
boolean addInfoComments = f.checkCommentsLevel(CommentsLevel.INFO);
|
||||||
|
if (f.getFieldInfo().isRenamed() && addInfoComments) {
|
||||||
code.newLine();
|
code.newLine();
|
||||||
CodeGenUtils.addRenamedComment(code, f, f.getName());
|
CodeGenUtils.addRenamedComment(code, f, f.getName());
|
||||||
}
|
}
|
||||||
code.startLine(f.getAccessFlags().makeString());
|
code.startLine(f.getAccessFlags().makeString(addInfoComments));
|
||||||
useType(code, f.getType());
|
useType(code, f.getType());
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
code.attachDefinition(f);
|
code.attachDefinition(f);
|
||||||
code.add(f.getAlias());
|
code.add(f.getAlias());
|
||||||
FieldInitAttr fv = f.get(AType.FIELD_INIT);
|
|
||||||
if (fv != null) {
|
FieldInitInsnAttr initInsnAttr = f.get(AType.FIELD_INIT_INSN);
|
||||||
|
if (initInsnAttr != null) {
|
||||||
|
InsnGen insnGen = makeInsnGen(initInsnAttr.getInsnMth());
|
||||||
code.add(" = ");
|
code.add(" = ");
|
||||||
if (fv.getValueType() == InitType.CONST) {
|
addInsnBody(insnGen, code, initInsnAttr.getInsn());
|
||||||
EncodedValue encodedValue = fv.getEncodedValue();
|
} else {
|
||||||
if (encodedValue.getType() == EncodedType.ENCODED_NULL) {
|
EncodedValue constVal = f.get(JadxAttrType.CONSTANT_VALUE);
|
||||||
|
if (constVal != null) {
|
||||||
|
code.add(" = ");
|
||||||
|
if (constVal.getType() == EncodedType.ENCODED_NULL) {
|
||||||
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
|
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
|
||||||
} else {
|
} else {
|
||||||
annotationGen.encodeValue(cls.root(), code, encodedValue);
|
Object val = EncodedValueUtils.convertToConstValue(constVal);
|
||||||
|
if (val instanceof LiteralArg) {
|
||||||
|
long lit = ((LiteralArg) val).getLiteral();
|
||||||
|
if (!AndroidResourcesUtils.handleResourceFieldValue(cls, code, lit, f.getType())) {
|
||||||
|
// force literal type to be same as field (java bytecode can use different type)
|
||||||
|
code.add(TypeGen.literalToString(lit, f.getType(), cls, fallback));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
annotationGen.encodeValue(cls.root(), code, constVal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (fv.getValueType() == InitType.INSN) {
|
|
||||||
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
|
|
||||||
addInsnBody(insnGen, code, fv.getInsn());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
code.add(';');
|
code.add(';');
|
||||||
@@ -419,7 +437,7 @@ public class ClassGen {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addEnumFields(CodeWriter code) throws CodegenException {
|
private void addEnumFields(ICodeWriter code) throws CodegenException {
|
||||||
EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS);
|
EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS);
|
||||||
if (enumFields == null) {
|
if (enumFields == null) {
|
||||||
return;
|
return;
|
||||||
@@ -427,6 +445,8 @@ public class ClassGen {
|
|||||||
InsnGen igen = null;
|
InsnGen igen = null;
|
||||||
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext();) {
|
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext();) {
|
||||||
EnumField f = it.next();
|
EnumField f = it.next();
|
||||||
|
|
||||||
|
CodeGenUtils.addComments(code, f.getField());
|
||||||
code.startLine(f.getField().getAlias());
|
code.startLine(f.getField().getAlias());
|
||||||
ConstructorInsn constrInsn = f.getConstrInsn();
|
ConstructorInsn constrInsn = f.getConstrInsn();
|
||||||
MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth());
|
MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth());
|
||||||
@@ -439,7 +459,7 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
if (f.getCls() != null) {
|
if (f.getCls() != null) {
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
new ClassGen(f.getCls(), this).addClassBody(code);
|
new ClassGen(f.getCls(), this).addClassBody(code, true);
|
||||||
}
|
}
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
code.add(',');
|
code.add(',');
|
||||||
@@ -471,7 +491,7 @@ public class ClassGen {
|
|||||||
return new InsnGen(mthGen, false);
|
return new InsnGen(mthGen, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addInsnBody(InsnGen insnGen, CodeWriter code, InsnNode insn) {
|
private void addInsnBody(InsnGen insnGen, ICodeWriter code, InsnNode insn) {
|
||||||
try {
|
try {
|
||||||
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
|
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -479,7 +499,7 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useType(CodeWriter code, ArgType type) {
|
public void useType(ICodeWriter code, ArgType type) {
|
||||||
PrimitiveType stype = type.getPrimitiveType();
|
PrimitiveType stype = type.getPrimitiveType();
|
||||||
if (stype == null) {
|
if (stype == null) {
|
||||||
code.add(type.toString());
|
code.add(type.toString());
|
||||||
@@ -497,21 +517,51 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useClass(CodeWriter code, String rawCls) {
|
public void useClass(ICodeWriter code, String rawCls) {
|
||||||
useClass(code, ArgType.object(rawCls));
|
useClass(code, ArgType.object(rawCls));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useClass(CodeWriter code, ArgType type) {
|
public void useClass(ICodeWriter code, ArgType type) {
|
||||||
ArgType outerType = type.getOuterType();
|
ArgType outerType = type.getOuterType();
|
||||||
if (outerType != null) {
|
if (outerType != null) {
|
||||||
useClass(code, outerType);
|
useClass(code, outerType);
|
||||||
code.add('.');
|
code.add('.');
|
||||||
// import not needed, force use short name
|
addInnerType(code, type);
|
||||||
useClassShortName(code, type.getObject());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
useClass(code, ClassInfo.fromType(cls.root(), type));
|
useClass(code, ClassInfo.fromType(cls.root(), type));
|
||||||
|
addGenerics(code, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addInnerType(ICodeWriter code, ArgType baseType) {
|
||||||
|
ArgType innerType = baseType.getInnerType();
|
||||||
|
ArgType outerType = innerType.getOuterType();
|
||||||
|
if (outerType != null) {
|
||||||
|
useClassWithShortName(code, baseType, outerType);
|
||||||
|
code.add('.');
|
||||||
|
addInnerType(code, innerType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
useClassWithShortName(code, baseType, innerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void useClassWithShortName(ICodeWriter code, ArgType baseType, ArgType type) {
|
||||||
|
String fullNameObj;
|
||||||
|
if (type.getObject().contains(".")) {
|
||||||
|
fullNameObj = type.getObject();
|
||||||
|
} else {
|
||||||
|
fullNameObj = baseType.getObject();
|
||||||
|
}
|
||||||
|
ClassInfo classInfo = ClassInfo.fromName(cls.root(), fullNameObj);
|
||||||
|
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||||
|
if (classNode != null) {
|
||||||
|
code.attachAnnotation(classNode);
|
||||||
|
}
|
||||||
|
code.add(classInfo.getAliasShortName());
|
||||||
|
addGenerics(code, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addGenerics(ICodeWriter code, ArgType type) {
|
||||||
List<ArgType> generics = type.getGenericTypes();
|
List<ArgType> generics = type.getGenericTypes();
|
||||||
if (generics != null) {
|
if (generics != null) {
|
||||||
code.add('<');
|
code.add('<');
|
||||||
@@ -536,16 +586,7 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void useClassShortName(CodeWriter code, String object) {
|
public void useClass(ICodeWriter code, ClassInfo classInfo) {
|
||||||
ClassInfo classInfo = ClassInfo.fromName(cls.root(), object);
|
|
||||||
ClassNode classNode = cls.root().resolveClass(classInfo);
|
|
||||||
if (classNode != null) {
|
|
||||||
code.attachAnnotation(classNode);
|
|
||||||
}
|
|
||||||
code.add(classInfo.getAliasShortName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void useClass(CodeWriter code, ClassInfo classInfo) {
|
|
||||||
ClassNode classNode = cls.root().resolveClass(classInfo);
|
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||||
if (classNode != null) {
|
if (classNode != null) {
|
||||||
useClass(code, classNode);
|
useClass(code, classNode);
|
||||||
@@ -554,12 +595,12 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useClass(CodeWriter code, ClassNode classNode) {
|
public void useClass(ICodeWriter code, ClassNode classNode) {
|
||||||
code.attachAnnotation(classNode);
|
code.attachAnnotation(classNode);
|
||||||
addClsName(code, classNode.getClassInfo());
|
addClsName(code, classNode.getClassInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addClsName(CodeWriter code, ClassInfo classInfo) {
|
public void addClsName(ICodeWriter code, ClassInfo classInfo) {
|
||||||
String clsName = useClassInternal(cls.getClassInfo(), classInfo);
|
String clsName = useClassInternal(cls.getClassInfo(), classInfo);
|
||||||
code.add(clsName);
|
code.add(clsName);
|
||||||
}
|
}
|
||||||
@@ -570,7 +611,7 @@ public class ClassGen {
|
|||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
String shortName = extClsInfo.getAliasShortName();
|
String shortName = extClsInfo.getAliasShortName();
|
||||||
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
if (useCls.equals(extClsInfo)) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
if (isClassInnerFor(useCls, extClsInfo)) {
|
if (isClassInnerFor(useCls, extClsInfo)) {
|
||||||
@@ -579,16 +620,21 @@ public class ClassGen {
|
|||||||
if (extClsInfo.isInner()) {
|
if (extClsInfo.isInner()) {
|
||||||
return expandInnerClassName(useCls, extClsInfo);
|
return expandInnerClassName(useCls, extClsInfo);
|
||||||
}
|
}
|
||||||
|
if (checkInnerCollision(cls.root(), useCls, extClsInfo)
|
||||||
|
|| checkInPackageCollision(cls.root(), useCls, extClsInfo)) {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
if (searchCollision(cls.root(), useCls, extClsInfo)) {
|
|
||||||
return fullName;
|
|
||||||
}
|
|
||||||
// ignore classes from default package
|
// ignore classes from default package
|
||||||
if (extClsInfo.isDefaultPackage()) {
|
if (extClsInfo.isDefaultPackage()) {
|
||||||
return shortName;
|
return shortName;
|
||||||
@@ -665,7 +711,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;
|
||||||
}
|
}
|
||||||
@@ -682,17 +728,30 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return searchCollision(root, useCls.getParentClass(), searchCls);
|
return checkInnerCollision(root, useCls.getParentClass(), searchCls);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
|
/**
|
||||||
|
* 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) {
|
||||||
ClassInfo classInfo = cls.getClassInfo();
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
if (classInfo.hasAlias()) {
|
if (classInfo.hasAlias() && cls.checkCommentsLevel(CommentsLevel.INFO)) {
|
||||||
CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject());
|
CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addClassUsageInfo(CodeWriter code, ClassNode cls) {
|
private static void addClassUsageInfo(ICodeWriter code, ClassNode cls) {
|
||||||
List<ClassNode> deps = cls.getDependencies();
|
List<ClassNode> deps = cls.getDependencies();
|
||||||
code.startLine("// deps - ").add(Integer.toString(deps.size()));
|
code.startLine("// deps - ").add(Integer.toString(deps.size()));
|
||||||
for (ClassNode depCls : deps) {
|
for (ClassNode depCls : deps) {
|
||||||
@@ -710,7 +769,7 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void addMthUsageInfo(CodeWriter code, MethodNode mth) {
|
static void addMthUsageInfo(ICodeWriter code, MethodNode mth) {
|
||||||
List<MethodNode> useInMths = mth.getUseIn();
|
List<MethodNode> useInMths = mth.getUseIn();
|
||||||
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
||||||
for (MethodNode useMth : useInMths) {
|
for (MethodNode useMth : useInMths) {
|
||||||
@@ -718,7 +777,7 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addFieldUsageInfo(CodeWriter code, FieldNode fieldNode) {
|
private static void addFieldUsageInfo(ICodeWriter code, FieldNode fieldNode) {
|
||||||
List<MethodNode> useInMths = fieldNode.getUseIn();
|
List<MethodNode> useInMths = fieldNode.getUseIn();
|
||||||
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
||||||
for (MethodNode useMth : useInMths) {
|
for (MethodNode useMth : useInMths) {
|
||||||
@@ -745,4 +804,13 @@ public class ClassGen {
|
|||||||
public void setBodyGenStarted(boolean bodyGenStarted) {
|
public void setBodyGenStarted(boolean bodyGenStarted) {
|
||||||
this.bodyGenStarted = bodyGenStarted;
|
this.bodyGenStarted = bodyGenStarted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public NameGen getOuterNameGen() {
|
||||||
|
return outerNameGen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOuterNameGen(@NotNull NameGen outerNameGen) {
|
||||||
|
this.outerNameGen = outerNameGen;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,291 +0,0 @@
|
|||||||
package jadx.core.codegen;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.ICodeInfo;
|
|
||||||
import jadx.api.impl.SimpleCodeInfo;
|
|
||||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
|
||||||
import jadx.core.utils.StringUtils;
|
|
||||||
import jadx.core.utils.Utils;
|
|
||||||
|
|
||||||
public class CodeWriter {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
|
|
||||||
|
|
||||||
public static final String NL = System.getProperty("line.separator");
|
|
||||||
public static final String INDENT_STR = " ";
|
|
||||||
|
|
||||||
private static final boolean ADD_LINE_NUMBERS = false;
|
|
||||||
|
|
||||||
private static final String[] INDENT_CACHE = {
|
|
||||||
"",
|
|
||||||
INDENT_STR,
|
|
||||||
INDENT_STR + INDENT_STR,
|
|
||||||
INDENT_STR + INDENT_STR + INDENT_STR,
|
|
||||||
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
|
||||||
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
|
||||||
};
|
|
||||||
|
|
||||||
private StringBuilder buf;
|
|
||||||
@Nullable
|
|
||||||
private String code;
|
|
||||||
private String indentStr;
|
|
||||||
private int indent;
|
|
||||||
|
|
||||||
private int line = 1;
|
|
||||||
private int offset = 0;
|
|
||||||
private Map<CodePosition, Object> annotations = Collections.emptyMap();
|
|
||||||
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
|
||||||
|
|
||||||
public CodeWriter() {
|
|
||||||
this.buf = new StringBuilder();
|
|
||||||
this.indent = 0;
|
|
||||||
this.indentStr = "";
|
|
||||||
if (ADD_LINE_NUMBERS) {
|
|
||||||
incIndent(3);
|
|
||||||
add(indentStr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CodeWriter startLine() {
|
|
||||||
addLine();
|
|
||||||
addLineIndent();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CodeWriter startLine(char c) {
|
|
||||||
addLine();
|
|
||||||
addLineIndent();
|
|
||||||
add(c);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CodeWriter startLine(String str) {
|
|
||||||
addLine();
|
|
||||||
addLineIndent();
|
|
||||||
add(str);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CodeWriter startLineWithNum(int sourceLine) {
|
|
||||||
if (sourceLine == 0) {
|
|
||||||
startLine();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
if (ADD_LINE_NUMBERS) {
|
|
||||||
newLine();
|
|
||||||
attachSourceLine(sourceLine);
|
|
||||||
String ln = "/* " + sourceLine + " */ ";
|
|
||||||
add(ln);
|
|
||||||
if (indentStr.length() > ln.length()) {
|
|
||||||
add(indentStr.substring(ln.length()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
startLine();
|
|
||||||
attachSourceLine(sourceLine);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CodeWriter addMultiLine(String str) {
|
|
||||||
if (str.contains(NL)) {
|
|
||||||
buf.append(str.replace(NL, NL + indentStr));
|
|
||||||
line += StringUtils.countMatches(str, NL);
|
|
||||||
offset = 0;
|
|
||||||
} else {
|
|
||||||
buf.append(str);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CodeWriter add(String str) {
|
|
||||||
buf.append(str);
|
|
||||||
offset += str.length();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CodeWriter add(char c) {
|
|
||||||
buf.append(c);
|
|
||||||
offset++;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeWriter add(CodeWriter code) {
|
|
||||||
line--;
|
|
||||||
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
|
|
||||||
CodePosition pos = entry.getKey();
|
|
||||||
attachAnnotation(entry.getValue(), new CodePosition(line + pos.getLine(), pos.getOffset()));
|
|
||||||
}
|
|
||||||
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
|
|
||||||
attachSourceLine(line + entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
line += code.line;
|
|
||||||
offset = code.offset;
|
|
||||||
buf.append(code.buf);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CodeWriter newLine() {
|
|
||||||
addLine();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CodeWriter addIndent() {
|
|
||||||
add(INDENT_STR);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addLine() {
|
|
||||||
buf.append(NL);
|
|
||||||
line++;
|
|
||||||
offset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CodeWriter addLineIndent() {
|
|
||||||
buf.append(indentStr);
|
|
||||||
offset += indentStr.length();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateIndent() {
|
|
||||||
int curIndent = indent;
|
|
||||||
if (curIndent < INDENT_CACHE.length) {
|
|
||||||
this.indentStr = INDENT_CACHE[curIndent];
|
|
||||||
} else {
|
|
||||||
this.indentStr = Utils.strRepeat(INDENT_STR, curIndent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void incIndent() {
|
|
||||||
incIndent(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decIndent() {
|
|
||||||
decIndent(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void incIndent(int c) {
|
|
||||||
this.indent += c;
|
|
||||||
updateIndent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decIndent(int c) {
|
|
||||||
this.indent -= c;
|
|
||||||
if (this.indent < 0) {
|
|
||||||
LOG.warn("Indent < 0");
|
|
||||||
this.indent = 0;
|
|
||||||
}
|
|
||||||
updateIndent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getIndent() {
|
|
||||||
return indent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIndent(int indent) {
|
|
||||||
this.indent = indent;
|
|
||||||
updateIndent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLine() {
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class DefinitionWrapper {
|
|
||||||
private final LineAttrNode node;
|
|
||||||
|
|
||||||
private DefinitionWrapper(LineAttrNode node) {
|
|
||||||
this.node = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LineAttrNode getNode() {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void attachDefinition(LineAttrNode obj) {
|
|
||||||
attachAnnotation(obj);
|
|
||||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void attachAnnotation(Object obj) {
|
|
||||||
attachAnnotation(obj, new CodePosition(line, offset + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void attachLineAnnotation(Object obj) {
|
|
||||||
attachAnnotation(obj, new CodePosition(line, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object attachAnnotation(Object obj, CodePosition pos) {
|
|
||||||
if (annotations.isEmpty()) {
|
|
||||||
annotations = new HashMap<>();
|
|
||||||
}
|
|
||||||
return annotations.put(pos, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void attachSourceLine(int sourceLine) {
|
|
||||||
if (sourceLine == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
attachSourceLine(line, sourceLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void attachSourceLine(int decompiledLine, int sourceLine) {
|
|
||||||
if (lineMap.isEmpty()) {
|
|
||||||
lineMap = new TreeMap<>();
|
|
||||||
}
|
|
||||||
lineMap.put(decompiledLine, sourceLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICodeInfo finish() {
|
|
||||||
removeFirstEmptyLine();
|
|
||||||
processDefinitionAnnotations();
|
|
||||||
code = buf.toString();
|
|
||||||
buf = null;
|
|
||||||
return new SimpleCodeInfo(code, lineMap, annotations);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeFirstEmptyLine() {
|
|
||||||
int len = NL.length();
|
|
||||||
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
|
|
||||||
buf.delete(0, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processDefinitionAnnotations() {
|
|
||||||
if (!annotations.isEmpty()) {
|
|
||||||
annotations.entrySet().removeIf(entry -> {
|
|
||||||
Object v = entry.getValue();
|
|
||||||
if (v instanceof DefinitionWrapper) {
|
|
||||||
LineAttrNode l = ((DefinitionWrapper) v).getNode();
|
|
||||||
l.setDecompiledLine(entry.getKey().getLine());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int bufLength() {
|
|
||||||
return buf.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCodeStr() {
|
|
||||||
if (code == null) {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return code != null ? code : buf.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ import java.util.Iterator;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.instructions.ArithNode;
|
import jadx.core.dex.instructions.ArithNode;
|
||||||
import jadx.core.dex.instructions.IfOp;
|
import jadx.core.dex.instructions.IfOp;
|
||||||
@@ -41,15 +42,15 @@ public class ConditionGen extends InsnGen {
|
|||||||
super(insnGen.mgen, insnGen.fallback);
|
super(insnGen.mgen, insnGen.fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void add(CodeWriter code, IfCondition condition) throws CodegenException {
|
public void add(ICodeWriter code, IfCondition condition) throws CodegenException {
|
||||||
add(code, new CondStack(), condition);
|
add(code, new CondStack(), condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
void wrap(CodeWriter code, IfCondition condition) throws CodegenException {
|
void wrap(ICodeWriter code, IfCondition condition) throws CodegenException {
|
||||||
wrap(code, new CondStack(), condition);
|
wrap(code, new CondStack(), condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void add(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
private void add(ICodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||||
stack.push(condition);
|
stack.push(condition);
|
||||||
switch (condition.getMode()) {
|
switch (condition.getMode()) {
|
||||||
case COMPARE:
|
case COMPARE:
|
||||||
@@ -75,7 +76,7 @@ public class ConditionGen extends InsnGen {
|
|||||||
stack.pop();
|
stack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void wrap(CodeWriter code, CondStack stack, IfCondition cond) throws CodegenException {
|
private void wrap(ICodeWriter code, CondStack stack, IfCondition cond) throws CodegenException {
|
||||||
boolean wrap = isWrapNeeded(cond);
|
boolean wrap = isWrapNeeded(cond);
|
||||||
if (wrap) {
|
if (wrap) {
|
||||||
code.add('(');
|
code.add('(');
|
||||||
@@ -86,7 +87,7 @@ public class ConditionGen extends InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void wrap(CodeWriter code, InsnArg firstArg) throws CodegenException {
|
private void wrap(ICodeWriter code, InsnArg firstArg) throws CodegenException {
|
||||||
boolean wrap = isArgWrapNeeded(firstArg);
|
boolean wrap = isArgWrapNeeded(firstArg);
|
||||||
if (wrap) {
|
if (wrap) {
|
||||||
code.add('(');
|
code.add('(');
|
||||||
@@ -97,7 +98,7 @@ public class ConditionGen extends InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCompare(CodeWriter code, CondStack stack, Compare compare) throws CodegenException {
|
private void addCompare(ICodeWriter code, CondStack stack, Compare compare) throws CodegenException {
|
||||||
IfOp op = compare.getOp();
|
IfOp op = compare.getOp();
|
||||||
InsnArg firstArg = compare.getA();
|
InsnArg firstArg = compare.getA();
|
||||||
InsnArg secondArg = compare.getB();
|
InsnArg secondArg = compare.getB();
|
||||||
@@ -130,7 +131,7 @@ public class ConditionGen extends InsnGen {
|
|||||||
addArg(code, secondArg, isArgWrapNeeded(secondArg));
|
addArg(code, secondArg, isArgWrapNeeded(secondArg));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTernary(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
private void addTernary(ICodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||||
add(code, stack, condition.first());
|
add(code, stack, condition.first());
|
||||||
code.add(" ? ");
|
code.add(" ? ");
|
||||||
add(code, stack, condition.second());
|
add(code, stack, condition.second());
|
||||||
@@ -138,12 +139,12 @@ public class ConditionGen extends InsnGen {
|
|||||||
add(code, stack, condition.third());
|
add(code, stack, condition.third());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addNot(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
private void addNot(ICodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||||
code.add('!');
|
code.add('!');
|
||||||
wrap(code, stack, condition.getArgs().get(0));
|
wrap(code, stack, condition.getArgs().get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAndOr(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
private void addAndOr(ICodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||||
String mode = condition.getMode() == Mode.AND ? " && " : " || ";
|
String mode = condition.getMode() == Mode.AND ? " && " : " || ";
|
||||||
Iterator<IfCondition> it = condition.getArgs().iterator();
|
Iterator<IfCondition> it = condition.getArgs().iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
|
|||||||
@@ -9,12 +9,17 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.core.deobf.NameMapper;
|
import jadx.api.CommentsLevel;
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
|
import jadx.api.plugins.input.data.MethodHandleType;
|
||||||
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.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;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
@@ -30,6 +35,7 @@ import jadx.core.dex.instructions.GotoNode;
|
|||||||
import jadx.core.dex.instructions.IfNode;
|
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.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;
|
||||||
@@ -44,11 +50,13 @@ import jadx.core.dex.instructions.args.RegisterArg;
|
|||||||
import jadx.core.dex.instructions.args.SSAVar;
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||||
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
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.CodeGenUtils;
|
||||||
import jadx.core.utils.RegionUtils;
|
import jadx.core.utils.RegionUtils;
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
@@ -62,7 +70,6 @@ public class InsnGen {
|
|||||||
protected final MethodNode mth;
|
protected final MethodNode mth;
|
||||||
protected final RootNode root;
|
protected final RootNode root;
|
||||||
protected final boolean fallback;
|
protected final boolean fallback;
|
||||||
protected final boolean attachInsns;
|
|
||||||
|
|
||||||
protected enum Flags {
|
protected enum Flags {
|
||||||
BODY_ONLY,
|
BODY_ONLY,
|
||||||
@@ -75,32 +82,39 @@ public class InsnGen {
|
|||||||
this.mth = mgen.getMethodNode();
|
this.mth = mgen.getMethodNode();
|
||||||
this.root = mth.root();
|
this.root = mth.root();
|
||||||
this.fallback = fallback;
|
this.fallback = fallback;
|
||||||
this.attachInsns = root.getArgs().isJsonOutput();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isFallback() {
|
private boolean isFallback() {
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException {
|
public void addArgDot(ICodeWriter code, InsnArg arg) throws CodegenException {
|
||||||
int len = code.bufLength();
|
int len = code.getLength();
|
||||||
addArg(code, arg, true);
|
addArg(code, arg, true);
|
||||||
if (len != code.bufLength()) {
|
if (len != code.getLength()) {
|
||||||
code.add('.');
|
code.add('.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addArg(CodeWriter code, InsnArg arg) throws CodegenException {
|
public void addArg(ICodeWriter code, InsnArg arg) throws CodegenException {
|
||||||
addArg(code, arg, true);
|
addArg(code, arg, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addArg(CodeWriter code, InsnArg arg, boolean wrap) throws CodegenException {
|
public void addArg(ICodeWriter code, InsnArg arg, boolean wrap) throws CodegenException {
|
||||||
|
addArg(code, arg, wrap ? BODY_ONLY_FLAG : BODY_ONLY_NOWRAP_FLAGS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addArg(ICodeWriter code, InsnArg arg, Set<Flags> flags) throws CodegenException {
|
||||||
if (arg.isRegister()) {
|
if (arg.isRegister()) {
|
||||||
code.add(mgen.getNameGen().useArg((RegisterArg) arg));
|
RegisterArg reg = (RegisterArg) arg;
|
||||||
|
if (code.isMetadataSupported()) {
|
||||||
|
code.attachAnnotation(VarNode.getRef(mth, reg));
|
||||||
|
}
|
||||||
|
code.add(mgen.getNameGen().useArg(reg));
|
||||||
} else if (arg.isLiteral()) {
|
} else if (arg.isLiteral()) {
|
||||||
code.add(lit((LiteralArg) arg));
|
addLiteralArg(code, (LiteralArg) arg, flags);
|
||||||
} else if (arg.isInsnWrap()) {
|
} else if (arg.isInsnWrap()) {
|
||||||
addWrappedArg(code, (InsnWrapArg) arg, wrap);
|
addWrappedArg(code, (InsnWrapArg) arg, flags);
|
||||||
} else if (arg.isNamed()) {
|
} else if (arg.isNamed()) {
|
||||||
code.add(((Named) arg).getName());
|
code.add(((Named) arg).getName());
|
||||||
} else {
|
} else {
|
||||||
@@ -108,19 +122,27 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addWrappedArg(CodeWriter code, InsnWrapArg arg, boolean wrap) throws CodegenException {
|
private void addLiteralArg(ICodeWriter code, LiteralArg litArg, Set<Flags> flags) {
|
||||||
|
String literalStr = lit(litArg);
|
||||||
|
if (!flags.contains(Flags.BODY_ONLY_NOWRAP) && literalStr.startsWith("-")) {
|
||||||
|
code.add('(').add(literalStr).add(')');
|
||||||
|
} else {
|
||||||
|
code.add(literalStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addWrappedArg(ICodeWriter code, InsnWrapArg arg, Set<Flags> flags) throws CodegenException {
|
||||||
InsnNode wrapInsn = arg.getWrapInsn();
|
InsnNode wrapInsn = arg.getWrapInsn();
|
||||||
if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
|
if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
|
||||||
code.add('(');
|
code.add('(');
|
||||||
makeInsn(wrapInsn, code, Flags.INLINE);
|
makeInsn(wrapInsn, code, Flags.INLINE);
|
||||||
code.add(')');
|
code.add(')');
|
||||||
} else {
|
} else {
|
||||||
Flags flags = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
|
makeInsnBody(code, wrapInsn, flags);
|
||||||
makeInsn(wrapInsn, code, flags);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
|
public void assignVar(ICodeWriter code, InsnNode insn) throws CodegenException {
|
||||||
RegisterArg arg = insn.getResult();
|
RegisterArg arg = insn.getResult();
|
||||||
if (insn.contains(AFlag.DECLARE_VAR)) {
|
if (insn.contains(AFlag.DECLARE_VAR)) {
|
||||||
declareVar(code, arg);
|
declareVar(code, arg);
|
||||||
@@ -129,26 +151,37 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void declareVar(CodeWriter code, RegisterArg arg) {
|
public void declareVar(ICodeWriter code, RegisterArg arg) {
|
||||||
declareVar(code, arg.getSVar().getCodeVar());
|
declareVar(code, arg.getSVar().getCodeVar());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void declareVar(CodeWriter code, CodeVar codeVar) {
|
public void declareVar(ICodeWriter code, CodeVar codeVar) {
|
||||||
if (codeVar.isFinal()) {
|
if (codeVar.isFinal()) {
|
||||||
code.add("final ");
|
code.add("final ");
|
||||||
}
|
}
|
||||||
useType(code, codeVar.getType());
|
useType(code, codeVar.getType());
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
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()) {
|
||||||
|
code.attachDefinition(VarNode.get(mth, codeVar));
|
||||||
|
}
|
||||||
|
code.add(varName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String lit(LiteralArg arg) {
|
private String lit(LiteralArg arg) {
|
||||||
return TypeGen.literalToString(arg, mth, fallback);
|
return TypeGen.literalToString(arg, mth, fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
private void instanceField(ICodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||||
ClassNode pCls = mth.getParentClass();
|
ClassNode pCls = mth.getParentClass();
|
||||||
FieldNode fieldNode = pCls.root().deepResolveField(field);
|
FieldNode fieldNode = pCls.root().resolveField(field);
|
||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
||||||
if (replace != null) {
|
if (replace != null) {
|
||||||
@@ -175,7 +208,7 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
|
public static void makeStaticFieldAccess(ICodeWriter code, FieldInfo field, 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);
|
||||||
@@ -186,7 +219,7 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
code.add('.');
|
code.add('.');
|
||||||
}
|
}
|
||||||
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field);
|
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
|
||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
code.attachAnnotation(fieldNode);
|
code.attachAnnotation(fieldNode);
|
||||||
}
|
}
|
||||||
@@ -197,23 +230,23 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void staticField(CodeWriter code, FieldInfo field) {
|
protected void staticField(ICodeWriter code, FieldInfo field) {
|
||||||
makeStaticFieldAccess(code, field, mgen.getClassGen());
|
makeStaticFieldAccess(code, field, mgen.getClassGen());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useClass(CodeWriter code, ArgType type) {
|
public void useClass(ICodeWriter code, ArgType type) {
|
||||||
mgen.getClassGen().useClass(code, type);
|
mgen.getClassGen().useClass(code, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useClass(CodeWriter code, ClassInfo cls) {
|
public void useClass(ICodeWriter code, ClassInfo cls) {
|
||||||
mgen.getClassGen().useClass(code, cls);
|
mgen.getClassGen().useClass(code, cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void useType(CodeWriter code, ArgType type) {
|
protected void useType(ICodeWriter code, ArgType type) {
|
||||||
mgen.getClassGen().useType(code, type);
|
mgen.getClassGen().useType(code, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
|
public void makeInsn(InsnNode insn, ICodeWriter code) throws CodegenException {
|
||||||
makeInsn(insn, code, null);
|
makeInsn(insn, code, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +254,7 @@ public class InsnGen {
|
|||||||
private static final Set<Flags> BODY_ONLY_FLAG = EnumSet.of(Flags.BODY_ONLY);
|
private static final Set<Flags> BODY_ONLY_FLAG = EnumSet.of(Flags.BODY_ONLY);
|
||||||
private static final Set<Flags> BODY_ONLY_NOWRAP_FLAGS = EnumSet.of(Flags.BODY_ONLY_NOWRAP);
|
private static final Set<Flags> BODY_ONLY_NOWRAP_FLAGS = EnumSet.of(Flags.BODY_ONLY_NOWRAP);
|
||||||
|
|
||||||
protected void makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
protected void makeInsn(InsnNode insn, ICodeWriter code, Flags flag) throws CodegenException {
|
||||||
if (insn.getType() == InsnType.REGION_ARG) {
|
if (insn.getType() == InsnType.REGION_ARG) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -231,9 +264,7 @@ public class InsnGen {
|
|||||||
} else {
|
} else {
|
||||||
if (flag != Flags.INLINE) {
|
if (flag != Flags.INLINE) {
|
||||||
code.startLineWithNum(insn.getSourceLine());
|
code.startLineWithNum(insn.getSourceLine());
|
||||||
if (attachInsns) {
|
InsnCodeOffset.attach(code, insn);
|
||||||
code.attachLineAnnotation(insn);
|
|
||||||
}
|
|
||||||
if (insn.contains(AFlag.COMMENT_OUT)) {
|
if (insn.contains(AFlag.COMMENT_OUT)) {
|
||||||
code.add("// ");
|
code.add("// ");
|
||||||
}
|
}
|
||||||
@@ -249,6 +280,7 @@ public class InsnGen {
|
|||||||
makeInsnBody(code, insn, EMPTY_FLAGS);
|
makeInsnBody(code, insn, EMPTY_FLAGS);
|
||||||
if (flag != Flags.INLINE) {
|
if (flag != Flags.INLINE) {
|
||||||
code.add(';');
|
code.add(';');
|
||||||
|
CodeGenUtils.addCodeComments(code, mth, insn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -256,7 +288,7 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
|
private void makeInsnBody(ICodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
|
||||||
switch (insn.getType()) {
|
switch (insn.getType()) {
|
||||||
case CONST_STR:
|
case CONST_STR:
|
||||||
String str = ((ConstStringNode) insn).getString();
|
String str = ((ConstStringNode) insn).getString();
|
||||||
@@ -371,11 +403,15 @@ public class InsnGen {
|
|||||||
ArgType arrayType = ((NewArrayNode) insn).getArrayType();
|
ArgType arrayType = ((NewArrayNode) insn).getArrayType();
|
||||||
code.add("new ");
|
code.add("new ");
|
||||||
useType(code, arrayType.getArrayRootElement());
|
useType(code, arrayType.getArrayRootElement());
|
||||||
code.add('[');
|
int k = 0;
|
||||||
addArg(code, insn.getArg(0));
|
int argsCount = insn.getArgsCount();
|
||||||
code.add(']');
|
for (; k < argsCount; k++) {
|
||||||
|
code.add('[');
|
||||||
|
addArg(code, insn.getArg(k), false);
|
||||||
|
code.add(']');
|
||||||
|
}
|
||||||
int dim = arrayType.getArrayDimension();
|
int dim = arrayType.getArrayDimension();
|
||||||
for (int i = 0; i < dim - 1; i++) {
|
for (; k < dim - 1; k++) {
|
||||||
code.add("[]");
|
code.add("[]");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -478,7 +514,7 @@ public class InsnGen {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ONE_ARG:
|
case ONE_ARG:
|
||||||
addArg(code, insn.getArg(0));
|
addArg(code, insn.getArg(0), state);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* fallback mode instructions */
|
/* fallback mode instructions */
|
||||||
@@ -490,7 +526,7 @@ public class InsnGen {
|
|||||||
code.add(' ');
|
code.add(' ');
|
||||||
code.add(ifInsn.getOp().getSymbol()).add(' ');
|
code.add(ifInsn.getOp().getSymbol()).add(' ');
|
||||||
addArg(code, insn.getArg(1));
|
addArg(code, insn.getArg(1));
|
||||||
code.add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget()));
|
code.add(") goto ").add(MethodGen.getLabelName(ifInsn));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GOTO:
|
case GOTO:
|
||||||
@@ -511,13 +547,24 @@ public class InsnGen {
|
|||||||
code.add(") {");
|
code.add(") {");
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
int[] keys = sw.getKeys();
|
int[] keys = sw.getKeys();
|
||||||
int[] targets = sw.getTargets();
|
int size = keys.length;
|
||||||
for (int i = 0; i < keys.length; i++) {
|
BlockNode[] targetBlocks = sw.getTargetBlocks();
|
||||||
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
|
if (targetBlocks != null) {
|
||||||
code.add(MethodGen.getLabelName(targets[i])).add(';');
|
for (int i = 0; i < size; i++) {
|
||||||
|
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
|
||||||
|
code.add(MethodGen.getLabelName(targetBlocks[i])).add(';');
|
||||||
|
}
|
||||||
|
code.startLine("default: goto ");
|
||||||
|
code.add(MethodGen.getLabelName(sw.getDefTargetBlock())).add(';');
|
||||||
|
} else {
|
||||||
|
int[] targets = sw.getTargets();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
|
||||||
|
code.add(MethodGen.getLabelName(targets[i])).add(';');
|
||||||
|
}
|
||||||
|
code.startLine("default: goto ");
|
||||||
|
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
|
||||||
}
|
}
|
||||||
code.startLine("default: goto ");
|
|
||||||
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
|
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
break;
|
break;
|
||||||
@@ -545,7 +592,7 @@ public class InsnGen {
|
|||||||
|
|
||||||
case FILL_ARRAY_DATA:
|
case FILL_ARRAY_DATA:
|
||||||
fallbackOnlyInsn(insn);
|
fallbackOnlyInsn(insn);
|
||||||
code.add("fill-array " + insn.toString());
|
code.add("fill-array " + insn);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SWITCH_DATA:
|
case SWITCH_DATA:
|
||||||
@@ -553,6 +600,17 @@ public class InsnGen {
|
|||||||
code.add(insn.toString());
|
code.add(insn.toString());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MOVE_MULTI:
|
||||||
|
fallbackOnlyInsn(insn);
|
||||||
|
int len = insn.getArgsCount();
|
||||||
|
for (int i = 0; i < len - 1; i += 2) {
|
||||||
|
addArg(code, insn.getArg(i));
|
||||||
|
code.add(" = ");
|
||||||
|
addArg(code, insn.getArg(i + 1));
|
||||||
|
code.add("; ");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new CodegenException(mth, "Unknown instruction: " + insn.getType());
|
throw new CodegenException(mth, "Unknown instruction: " + insn.getType());
|
||||||
}
|
}
|
||||||
@@ -562,8 +620,10 @@ public class InsnGen {
|
|||||||
* In most cases must be combined with new array instructions.
|
* In most cases must be combined with new array instructions.
|
||||||
* Use one by one array fill (can be replaced with System.arrayCopy)
|
* Use one by one array fill (can be replaced with System.arrayCopy)
|
||||||
*/
|
*/
|
||||||
private void fillArray(CodeWriter code, FillArrayInsn arrayNode) throws CodegenException {
|
private void fillArray(ICodeWriter code, FillArrayInsn arrayNode) throws CodegenException {
|
||||||
code.add("// fill-array-data instruction");
|
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
|
||||||
|
code.add("// fill-array-data instruction");
|
||||||
|
}
|
||||||
code.startLine();
|
code.startLine();
|
||||||
InsnArg arrArg = arrayNode.getArg(0);
|
InsnArg arrArg = arrayNode.getArg(0);
|
||||||
ArgType arrayType = arrArg.getType();
|
ArgType arrayType = arrArg.getType();
|
||||||
@@ -586,7 +646,7 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void oneArgInsn(CodeWriter code, InsnNode insn, Set<Flags> state, char op) throws CodegenException {
|
private void oneArgInsn(ICodeWriter code, InsnNode insn, Set<Flags> state, char op) throws CodegenException {
|
||||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||||
if (wrap) {
|
if (wrap) {
|
||||||
code.add('(');
|
code.add('(');
|
||||||
@@ -608,23 +668,29 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void filledNewArray(FilledNewArrayNode insn, CodeWriter code) throws CodegenException {
|
private void filledNewArray(FilledNewArrayNode insn, ICodeWriter code) throws CodegenException {
|
||||||
if (!insn.contains(AFlag.DECLARE_VAR)) {
|
if (!insn.contains(AFlag.DECLARE_VAR)) {
|
||||||
code.add("new ");
|
code.add("new ");
|
||||||
useType(code, insn.getArrayType());
|
useType(code, insn.getArrayType());
|
||||||
}
|
}
|
||||||
code.add('{');
|
code.add('{');
|
||||||
int c = insn.getArgsCount();
|
int c = insn.getArgsCount();
|
||||||
|
int wrap = 0;
|
||||||
for (int i = 0; i < c; i++) {
|
for (int i = 0; i < c; i++) {
|
||||||
addArg(code, insn.getArg(i), false);
|
addArg(code, insn.getArg(i), false);
|
||||||
if (i + 1 < c) {
|
if (i + 1 < c) {
|
||||||
code.add(", ");
|
code.add(", ");
|
||||||
}
|
}
|
||||||
|
wrap++;
|
||||||
|
if (wrap == 1000) {
|
||||||
|
code.startLine();
|
||||||
|
wrap = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
code.add('}');
|
code.add('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeConstructor(ConstructorInsn insn, CodeWriter 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();
|
cls.ensureProcessed();
|
||||||
@@ -635,13 +701,30 @@ public class InsnGen {
|
|||||||
if (insn.isSelf()) {
|
if (insn.isSelf()) {
|
||||||
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
|
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
|
||||||
}
|
}
|
||||||
|
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
||||||
|
MethodNode refMth = callMth;
|
||||||
|
if (callMth != null) {
|
||||||
|
MethodReplaceAttr replaceAttr = callMth.get(AType.METHOD_REPLACE);
|
||||||
|
if (replaceAttr != null) {
|
||||||
|
refMth = replaceAttr.getReplaceMth();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (insn.isSuper()) {
|
if (insn.isSuper()) {
|
||||||
|
code.attachAnnotation(refMth);
|
||||||
code.add("super");
|
code.add("super");
|
||||||
} else if (insn.isThis()) {
|
} else if (insn.isThis()) {
|
||||||
|
code.attachAnnotation(refMth);
|
||||||
code.add("this");
|
code.add("this");
|
||||||
} else {
|
} else {
|
||||||
code.add("new ");
|
code.add("new ");
|
||||||
useClass(code, insn.getClassType());
|
if (refMth == null || refMth.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
// use class reference if constructor method is missing (default constructor)
|
||||||
|
code.attachAnnotation(mth.root().resolveClass(insn.getCallMth().getDeclClass()));
|
||||||
|
} else {
|
||||||
|
code.attachAnnotation(refMth);
|
||||||
|
}
|
||||||
|
mgen.getClassGen().addClsName(code, insn.getClassType());
|
||||||
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
|
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
|
||||||
if (genericInfoAttr != null) {
|
if (genericInfoAttr != null) {
|
||||||
code.add('<');
|
code.add('<');
|
||||||
@@ -659,26 +742,18 @@ public class InsnGen {
|
|||||||
code.add('>');
|
code.add('>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
|
||||||
generateMethodArguments(code, insn, 0, callMth);
|
generateMethodArguments(code, insn, 0, callMth);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void inlineAnonymousConstructor(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||||
if (this.mth.getParentClass() == cls) {
|
if (this.mth.getParentClass() == cls) {
|
||||||
cls.remove(AFlag.ANONYMOUS_CLASS);
|
cls.remove(AType.ANONYMOUS_CLASS);
|
||||||
cls.remove(AFlag.DONT_GENERATE);
|
cls.remove(AFlag.DONT_GENERATE);
|
||||||
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
|
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
|
||||||
throw new CodegenException("Anonymous inner class unlimited recursion detected."
|
throw new CodegenException("Anonymous inner class unlimited recursion detected."
|
||||||
+ " Convert class to inner: " + cls.getClassInfo().getFullName());
|
+ " Convert class to inner: " + cls.getClassInfo().getFullName());
|
||||||
}
|
}
|
||||||
|
ArgType parent = cls.get(AType.ANONYMOUS_CLASS).getBaseType();
|
||||||
cls.add(AFlag.DONT_GENERATE);
|
|
||||||
ArgType parent;
|
|
||||||
if (cls.getInterfaces().size() == 1) {
|
|
||||||
parent = cls.getInterfaces().get(0);
|
|
||||||
} else {
|
|
||||||
parent = cls.getSuperClass();
|
|
||||||
}
|
|
||||||
// hide empty anonymous constructors
|
// hide empty anonymous constructors
|
||||||
for (MethodNode ctor : cls.getMethods()) {
|
for (MethodNode ctor : cls.getMethods()) {
|
||||||
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
|
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
|
||||||
@@ -686,46 +761,55 @@ public class InsnGen {
|
|||||||
ctor.add(AFlag.DONT_GENERATE);
|
ctor.add(AFlag.DONT_GENERATE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
code.add("new ");
|
code.add("new ");
|
||||||
if (parent == null) {
|
useClass(code, parent);
|
||||||
code.add("Object");
|
|
||||||
} else {
|
|
||||||
useClass(code, parent);
|
|
||||||
}
|
|
||||||
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
||||||
|
if (callMth != null) {
|
||||||
|
// copy var names
|
||||||
|
List<RegisterArg> mthArgs = callMth.getArgRegs();
|
||||||
|
int argsCount = Math.min(insn.getArgsCount(), mthArgs.size());
|
||||||
|
for (int i = 0; i < argsCount; i++) {
|
||||||
|
InsnArg arg = insn.getArg(i);
|
||||||
|
if (arg.isRegister()) {
|
||||||
|
RegisterArg mthArg = mthArgs.get(i);
|
||||||
|
RegisterArg insnArg = (RegisterArg) arg;
|
||||||
|
mthArg.getSVar().setCodeVar(insnArg.getSVar().getCodeVar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
generateMethodArguments(code, insn, 0, callMth);
|
generateMethodArguments(code, insn, 0, callMth);
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code, true);
|
|
||||||
|
ClassGen classGen = new ClassGen(cls, mgen.getClassGen().getParentGen());
|
||||||
|
classGen.setOuterNameGen(mgen.getNameGen());
|
||||||
|
classGen.addClassBody(code, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
|
private void makeInvoke(InvokeNode insn, ICodeWriter code) throws CodegenException {
|
||||||
|
InvokeType type = insn.getInvokeType();
|
||||||
|
if (type == InvokeType.CUSTOM) {
|
||||||
|
makeInvokeLambda(code, (InvokeCustomNode) insn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
MethodInfo callMth = insn.getCallMth();
|
MethodInfo callMth = insn.getCallMth();
|
||||||
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
|
MethodNode callMthNode = mth.root().resolveMethod(callMth);
|
||||||
|
|
||||||
int k = 0;
|
int k = 0;
|
||||||
InvokeType type = insn.getInvokeType();
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case DIRECT:
|
case DIRECT:
|
||||||
case VIRTUAL:
|
case VIRTUAL:
|
||||||
case INTERFACE:
|
case INTERFACE:
|
||||||
InsnArg arg = insn.getArg(0);
|
InsnArg arg = insn.getArg(0);
|
||||||
// FIXME: add 'this' for equals methods in scope
|
if (needInvokeArg(arg)) {
|
||||||
if (!arg.isThis()) {
|
|
||||||
addArgDot(code, arg);
|
addArgDot(code, arg);
|
||||||
}
|
}
|
||||||
k++;
|
k++;
|
||||||
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:
|
||||||
@@ -746,37 +830,176 @@ public class InsnGen {
|
|||||||
generateMethodArguments(code, insn, k, callMthNode);
|
generateMethodArguments(code, insn, k, callMthNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
// FIXME: add 'this' for equals methods in scope
|
||||||
private ClassInfo getClassForSuperCall(CodeWriter code, MethodInfo callMth) {
|
private boolean needInvokeArg(InsnArg arg) {
|
||||||
ClassNode useCls = mth.getParentClass();
|
if (arg.isAnyThis()) {
|
||||||
ClassInfo insnCls = useCls.getClassInfo();
|
if (arg.isThis()) {
|
||||||
ClassInfo declClass = callMth.getDeclClass();
|
return false;
|
||||||
if (insnCls.equals(declClass)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
ClassNode topClass = useCls.getTopParentClass();
|
|
||||||
if (topClass.getClassInfo().equals(declClass)) {
|
|
||||||
return declClass;
|
|
||||||
}
|
|
||||||
// search call class
|
|
||||||
ClassNode nextParent = useCls;
|
|
||||||
do {
|
|
||||||
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();
|
ClassNode clsNode = mth.root().resolveClass(arg.getType());
|
||||||
} while (nextParent != null && nextParent != topClass);
|
if (clsNode != null && clsNode.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
return false;
|
||||||
// search failed, just return parent class
|
}
|
||||||
return useCls.getParentClass().getClassInfo();
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void generateMethodArguments(CodeWriter code, BaseInvokeNode insn, int startArgNum,
|
private void makeInvokeLambda(ICodeWriter code, InvokeCustomNode customNode) throws CodegenException {
|
||||||
|
if (customNode.isUseRef()) {
|
||||||
|
makeRefLambda(code, customNode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fallback || !customNode.isInlineInsn()) {
|
||||||
|
makeSimpleLambda(code, customNode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MethodNode callMth = (MethodNode) customNode.getCallInsn().get(AType.METHOD_DETAILS);
|
||||||
|
makeInlinedLambdaMethod(code, customNode, callMth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeRefLambda(ICodeWriter code, InvokeCustomNode customNode) {
|
||||||
|
InsnNode callInsn = customNode.getCallInsn();
|
||||||
|
if (callInsn instanceof ConstructorInsn) {
|
||||||
|
MethodInfo callMth = ((ConstructorInsn) callInsn).getCallMth();
|
||||||
|
useClass(code, callMth.getDeclClass());
|
||||||
|
code.add("::new");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (callInsn instanceof InvokeNode) {
|
||||||
|
InvokeNode invokeInsn = (InvokeNode) callInsn;
|
||||||
|
MethodInfo callMth = invokeInsn.getCallMth();
|
||||||
|
if (customNode.getHandleType() == MethodHandleType.INVOKE_STATIC) {
|
||||||
|
useClass(code, callMth.getDeclClass());
|
||||||
|
} else {
|
||||||
|
code.add("this");
|
||||||
|
}
|
||||||
|
code.add("::").add(callMth.getAlias());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeSimpleLambda(ICodeWriter code, InvokeCustomNode customNode) {
|
||||||
|
try {
|
||||||
|
InsnNode callInsn = customNode.getCallInsn();
|
||||||
|
MethodInfo implMthInfo = customNode.getImplMthInfo();
|
||||||
|
int implArgsCount = implMthInfo.getArgsCount();
|
||||||
|
if (implArgsCount == 0) {
|
||||||
|
code.add("()");
|
||||||
|
} else {
|
||||||
|
code.add('(');
|
||||||
|
int callArgsCount = callInsn.getArgsCount();
|
||||||
|
int startArg = callArgsCount - implArgsCount;
|
||||||
|
if (customNode.getHandleType() != MethodHandleType.INVOKE_STATIC
|
||||||
|
&& customNode.getArgsCount() > 0
|
||||||
|
&& customNode.getArg(0).isThis()) {
|
||||||
|
callInsn.getArg(0).add(AFlag.THIS);
|
||||||
|
}
|
||||||
|
if (startArg >= 0) {
|
||||||
|
for (int i = startArg; i < callArgsCount; i++) {
|
||||||
|
if (i != startArg) {
|
||||||
|
code.add(", ");
|
||||||
|
}
|
||||||
|
addArg(code, callInsn.getArg(i));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
code.add("/* ERROR: " + startArg + " */");
|
||||||
|
}
|
||||||
|
code.add(')');
|
||||||
|
}
|
||||||
|
code.add(" -> {");
|
||||||
|
if (fallback) {
|
||||||
|
code.add(" // ").add(implMthInfo.toString());
|
||||||
|
}
|
||||||
|
code.incIndent();
|
||||||
|
code.startLine();
|
||||||
|
if (!implMthInfo.getReturnType().isVoid()) {
|
||||||
|
code.add("return ");
|
||||||
|
}
|
||||||
|
makeInsn(callInsn, code, Flags.INLINE);
|
||||||
|
code.add(";");
|
||||||
|
|
||||||
|
code.decIndent();
|
||||||
|
code.startLine('}');
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Failed to generate 'invoke-custom' instruction: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeInlinedLambdaMethod(ICodeWriter code, InvokeCustomNode customNode, MethodNode callMth) throws CodegenException {
|
||||||
|
MethodGen callMthGen = new MethodGen(mgen.getClassGen(), callMth);
|
||||||
|
NameGen nameGen = callMthGen.getNameGen();
|
||||||
|
nameGen.inheritUsedNames(this.mgen.getNameGen());
|
||||||
|
|
||||||
|
List<ArgType> implArgs = customNode.getImplMthInfo().getArgumentsTypes();
|
||||||
|
List<RegisterArg> callArgs = callMth.getArgRegs();
|
||||||
|
if (implArgs.isEmpty()) {
|
||||||
|
code.add("()");
|
||||||
|
} else {
|
||||||
|
int callArgsCount = callArgs.size();
|
||||||
|
int startArg = callArgsCount - implArgs.size();
|
||||||
|
for (int i = startArg; i < callArgsCount; i++) {
|
||||||
|
if (i != startArg) {
|
||||||
|
code.add(", ");
|
||||||
|
}
|
||||||
|
CodeVar argCodeVar = callArgs.get(i).getSVar().getCodeVar();
|
||||||
|
defVar(code, argCodeVar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// force set external arg names into call method args
|
||||||
|
int extArgsCount = customNode.getArgsCount();
|
||||||
|
int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg
|
||||||
|
for (int i = startArg; i < extArgsCount; i++) {
|
||||||
|
RegisterArg extArg = (RegisterArg) customNode.getArg(i);
|
||||||
|
RegisterArg callRegArg = callArgs.get(i);
|
||||||
|
callRegArg.getSVar().setCodeVar(extArg.getSVar().getCodeVar());
|
||||||
|
}
|
||||||
|
code.add(" -> {");
|
||||||
|
code.incIndent();
|
||||||
|
callMthGen.addInstructions(code);
|
||||||
|
|
||||||
|
code.decIndent();
|
||||||
|
code.startLine('}');
|
||||||
|
}
|
||||||
|
|
||||||
|
private void callSuper(ICodeWriter code, MethodInfo callMth) {
|
||||||
|
ClassInfo superCallCls = getClassForSuperCall(callMth);
|
||||||
|
if (superCallCls == null) {
|
||||||
|
// unknown class, add comment to keep that info
|
||||||
|
code.add("super/*").add(callMth.getDeclClass().getFullName()).add("*/");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ClassInfo curClass = mth.getParentClass().getClassInfo();
|
||||||
|
if (superCallCls.equals(curClass)) {
|
||||||
|
code.add("super");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// use custom class
|
||||||
|
useClass(code, superCallCls);
|
||||||
|
code.add(".super");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
@Nullable MethodNode mthNode) throws CodegenException {
|
@Nullable MethodNode mthNode) throws CodegenException {
|
||||||
int k = startArgNum;
|
int k = startArgNum;
|
||||||
if (mthNode != null && mthNode.contains(AFlag.SKIP_FIRST_ARG)) {
|
if (mthNode != null && mthNode.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||||
@@ -813,7 +1036,7 @@ public class InsnGen {
|
|||||||
/**
|
/**
|
||||||
* Expand varArgs from filled array.
|
* Expand varArgs from filled array.
|
||||||
*/
|
*/
|
||||||
private boolean processVarArg(CodeWriter code, BaseInvokeNode invokeInsn, InsnArg lastArg) throws CodegenException {
|
private boolean processVarArg(ICodeWriter code, BaseInvokeNode invokeInsn, InsnArg lastArg) throws CodegenException {
|
||||||
if (!invokeInsn.contains(AFlag.VARARG_CALL)) {
|
if (!invokeInsn.contains(AFlag.VARARG_CALL)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -835,7 +1058,7 @@ public class InsnGen {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
private void makeTernary(TernaryInsn insn, ICodeWriter code, Set<Flags> state) throws CodegenException {
|
||||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||||
if (wrap) {
|
if (wrap) {
|
||||||
code.add('(');
|
code.add('(');
|
||||||
@@ -848,7 +1071,6 @@ public class InsnGen {
|
|||||||
} else {
|
} else {
|
||||||
condGen.wrap(code, insn.getCondition());
|
condGen.wrap(code, insn.getCondition());
|
||||||
code.add(" ? ");
|
code.add(" ? ");
|
||||||
addCastIfNeeded(code, first, second);
|
|
||||||
addArg(code, first, false);
|
addArg(code, first, false);
|
||||||
code.add(" : ");
|
code.add(" : ");
|
||||||
addArg(code, second, false);
|
addArg(code, second, false);
|
||||||
@@ -858,34 +1080,7 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCastIfNeeded(CodeWriter code, InsnArg first, InsnArg second) {
|
private void makeArith(ArithNode insn, ICodeWriter code, Set<Flags> state) throws CodegenException {
|
||||||
if (first.isLiteral() && second.isLiteral()) {
|
|
||||||
if (first.getType() == ArgType.BYTE) {
|
|
||||||
long lit1 = ((LiteralArg) first).getLiteral();
|
|
||||||
long lit2 = ((LiteralArg) second).getLiteral();
|
|
||||||
if (lit1 != Byte.MAX_VALUE && lit1 != Byte.MIN_VALUE
|
|
||||||
&& lit2 != Byte.MAX_VALUE && lit2 != Byte.MIN_VALUE) {
|
|
||||||
code.add("(byte) ");
|
|
||||||
}
|
|
||||||
} else if (first.getType() == ArgType.SHORT) {
|
|
||||||
long lit1 = ((LiteralArg) first).getLiteral();
|
|
||||||
long lit2 = ((LiteralArg) second).getLiteral();
|
|
||||||
if (lit1 != Short.MAX_VALUE && lit1 != Short.MIN_VALUE
|
|
||||||
&& lit2 != Short.MAX_VALUE && lit2 != Short.MIN_VALUE) {
|
|
||||||
code.add("(short) ");
|
|
||||||
}
|
|
||||||
} else if (first.getType() == ArgType.CHAR) {
|
|
||||||
long lit1 = ((LiteralArg) first).getLiteral();
|
|
||||||
long lit2 = ((LiteralArg) second).getLiteral();
|
|
||||||
if (!NameMapper.isPrintableChar((char) (lit1))
|
|
||||||
&& !NameMapper.isPrintableChar((char) (lit2))) {
|
|
||||||
code.add("(char) ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void makeArith(ArithNode insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
|
||||||
if (insn.contains(AFlag.ARITH_ONEARG)) {
|
if (insn.contains(AFlag.ARITH_ONEARG)) {
|
||||||
makeArithOneArg(insn, code);
|
makeArithOneArg(insn, code);
|
||||||
return;
|
return;
|
||||||
@@ -905,7 +1100,7 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
|
private void makeArithOneArg(ArithNode insn, ICodeWriter code) throws CodegenException {
|
||||||
ArithOp op = insn.getOp();
|
ArithOp op = insn.getOp();
|
||||||
InsnArg resArg = insn.getArg(0);
|
InsnArg resArg = insn.getArg(0);
|
||||||
InsnArg arg = insn.getArg(1);
|
InsnArg arg = insn.getArg(1);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user