Compare commits
512 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 381afa2741 | |||
| 82d4099541 | |||
| 5f659c8de7 | |||
| e054ea6683 | |||
| 0deafb768b | |||
| cd612b452c | |||
| 009939f866 | |||
| cd006ce78e | |||
| 71bf2aa59f | |||
| 714b935474 | |||
| 2a2b83a695 | |||
| acdaa95854 | |||
| 278c5f6142 | |||
| 8ca3cd3155 | |||
| 2b7d7ce2cf | |||
| a22efc2eb6 | |||
| 804c8eff91 | |||
| aec8ebe237 | |||
| 7353790ed1 | |||
| e09e8e5823 | |||
| 92773417b3 | |||
| 12dc4fde8a | |||
| d1e5186d4a | |||
| d06ba95374 | |||
| f0e6c8ea8e | |||
| c94c204da2 | |||
| 71617a1c70 | |||
| 9f684937c6 | |||
| ff6665c716 | |||
| aa8fd3c861 | |||
| e2b42804d5 | |||
| 0f6e942c5b | |||
| c0a81978bf | |||
| b76c882210 | |||
| 14cbfbc5a4 | |||
| 9b1761f71f | |||
| 73ca2e0fa4 | |||
| 4e4c7f7d7b | |||
| 33f2c3f220 | |||
| dcca0133fb | |||
| 408201b69b | |||
| e024628d46 | |||
| 6428f29373 | |||
| cfaa6ab6df | |||
| 91ee7565ac | |||
| 1bbcac2ab3 | |||
| 60b2353afe | |||
| 50cfa4c971 | |||
| 691bf8faca | |||
| 89b4ae6a6f | |||
| 605a67932f | |||
| 1774dc74e3 | |||
| 2d641bf049 | |||
| 94a06d9b6f | |||
| a485942731 | |||
| 2c1b3b2480 | |||
| f1f7c70aee | |||
| 718caf8cb1 | |||
| 545cd4ec12 | |||
| 444ea9ec7e | |||
| 13609a5c44 | |||
| d6ad21f6f9 | |||
| 593a32a689 | |||
| 7fed5534eb | |||
| 1560284831 | |||
| 558a86739f | |||
| bfd60b733a | |||
| ae26512601 | |||
| 498c2f5256 | |||
| 459f12d61f | |||
| bcd6e537e0 | |||
| 867c3413e9 | |||
| 0f808d5c60 | |||
| f5767dd865 | |||
| 631a855bac | |||
| c616b5b03b | |||
| 3e9f4a5060 | |||
| 31434186ab | |||
| e81ba1c431 | |||
| b219ab607f | |||
| cd8307f432 | |||
| a720105208 | |||
| 34692c41f2 | |||
| 8a8b945eb8 | |||
| 99569c52ac | |||
| f696dc715b | |||
| 21b8552386 | |||
| 4b1886700d | |||
| a83ca1f85b | |||
| 65553c156c | |||
| 440357d2e8 | |||
| 5e62b9077a | |||
| 6192ced214 | |||
| ae31fee8dd | |||
| e7b00cc76e | |||
| 7d29c5d766 | |||
| 15776c4ce3 | |||
| d720179deb | |||
| 0d69e0ac97 | |||
| 09e267f8bc | |||
| 705ceca42a | |||
| 58722d372e | |||
| f482b8b95c | |||
| 21e4dee0e2 | |||
| c7a12ad75b | |||
| 7cd77ae379 | |||
| d59c99ddfe | |||
| 85760cc844 | |||
| 0692464b85 | |||
| 3968222744 | |||
| c05368d92e | |||
| 404136cd72 | |||
| b1d5ed0066 | |||
| e22474e0a7 | |||
| 45b7304630 | |||
| 692536c584 | |||
| 4e3d85887c | |||
| 87852328da | |||
| 2207cd7b52 | |||
| f3cd4e38d7 | |||
| 2dce1c0ad9 | |||
| 258ecad277 | |||
| 7f5092c0d5 | |||
| a7f315f596 | |||
| 4dc4aa122b | |||
| e3f388af11 | |||
| 83196628c9 | |||
| 315c07d4f6 | |||
| 47dadf0a43 | |||
| c62039f327 | |||
| a5ea560edc | |||
| e09e933f9c | |||
| dbd00d5a8b | |||
| 2da772df8e | |||
| 4cdad0e83e | |||
| 2f780da305 | |||
| 9d8066f4b8 | |||
| 2cc49256a9 | |||
| 79ab2e11f8 | |||
| c1f4302e62 | |||
| f66ec9168c | |||
| 37aecf72cb | |||
| 3c7be5e9be | |||
| 89dbae8f8e | |||
| 5eec8f754d | |||
| 49a82c8388 | |||
| 26bad4a1cd | |||
| fa0a38d3aa | |||
| b56fd4d29a | |||
| 4520747167 | |||
| e444ecb2c7 | |||
| 1336c47d18 | |||
| 519a74e8d2 | |||
| dea7714ef3 | |||
| 74b88b407e | |||
| 57c28c61e0 | |||
| 87320348dd | |||
| fcb70e69c1 | |||
| 4859629850 | |||
| bd0d248fd0 | |||
| c24a3edb44 | |||
| d0f197ea3d | |||
| 5502d93cd5 | |||
| 073fd76aa2 | |||
| 492a3f6928 | |||
| 1ce8fa8bdd | |||
| 1bb90233b9 | |||
| 49ce92f540 | |||
| 2107da2e1a | |||
| d98321026d | |||
| 0b6fabbc71 | |||
| bb0fad2834 | |||
| 08f9722e33 | |||
| 62ca30bbc6 | |||
| 467403362d | |||
| 265a78cd23 | |||
| a0e13d0481 | |||
| 77fc6435a0 | |||
| cd7e5bf020 | |||
| 5e7388f686 | |||
| 1047e751e6 | |||
| c598871764 | |||
| 2921c66834 | |||
| 7bbb083c36 | |||
| 531650c9f2 | |||
| f3098741c3 | |||
| 9dbffef140 | |||
| c97e504686 | |||
| 0c4b807caa | |||
| 1eca2b6cb0 | |||
| 17cbb3eab0 | |||
| c72f2a2c96 | |||
| 610f531653 | |||
| 1e9b28b369 | |||
| 6d4caca6cc | |||
| 15953f832f | |||
| d346ed0570 | |||
| df520a1134 | |||
| f90fc1d5ec | |||
| 797904afb5 | |||
| 489fbb5e42 | |||
| 9dd5a9ef89 | |||
| 02213802c5 | |||
| 8365855475 | |||
| 55eb86d2d5 | |||
| 0a9b944431 | |||
| f1e229193c | |||
| 04e309aeff | |||
| 99eb31b312 | |||
| 287275d886 | |||
| af6f8b5391 | |||
| 3b9b103c3f | |||
| 0c55ab9001 | |||
| 9c88f70740 | |||
| 9ab003df4c | |||
| 7f8d03d192 | |||
| c64ffde11f | |||
| 1568008c67 | |||
| 84211576e4 | |||
| 553f5b063f | |||
| f5d1f288d0 | |||
| a2df92dd68 | |||
| 1c6e51f8b2 | |||
| ef5da49bc0 | |||
| 7545625af4 | |||
| e3055b95f6 | |||
| 78eed8629c | |||
| cc29da8e81 | |||
| d1a6841c20 | |||
| 600842a1a6 | |||
| 8ba3e935a5 | |||
| 87504dd2cc | |||
| e4e6f37949 | |||
| 4b314e9d99 | |||
| a48ce296b8 | |||
| cf3e17c4b8 | |||
| bae36f9720 | |||
| 11db454b84 | |||
| 1b60c1d1a8 | |||
| 8321d5e380 | |||
| 64c9ce2ab0 | |||
| 08f9a90c95 | |||
| 9f06d6744e | |||
| f228a72118 | |||
| 3249a5e0bc | |||
| d1ac43de33 | |||
| 00f5e83506 | |||
| d3ecc1f640 | |||
| 902247fcdb | |||
| bd9e1096cc | |||
| db892adf34 | |||
| 1cbaad3ec9 | |||
| 401d08ea49 | |||
| ba17f7bc00 | |||
| db2b537380 | |||
| 06f26ef8f5 | |||
| a71bb7a532 | |||
| 99934b5100 | |||
| ff5f6fca3c | |||
| 3578f7d68f | |||
| 7bc01dcfa8 | |||
| bc7a748420 | |||
| c0194d025d | |||
| 19ca8a096b | |||
| cf5bfc297b | |||
| a17f9136dd | |||
| 7d07fb0b77 | |||
| 99935bada6 | |||
| be9dae57b9 | |||
| 4629043721 | |||
| 068234f0ca | |||
| ccb8ed1394 | |||
| 8d68d409eb | |||
| e842e022ba | |||
| 1e6b30343c | |||
| ddedb8d8a0 | |||
| 472aa52706 | |||
| ab97084058 | |||
| 0911b2dc2f | |||
| fd7d08cb10 | |||
| 3ae8359408 | |||
| 6b76a3c787 | |||
| 9fbf9ef667 | |||
| c8de7b97dd | |||
| b32dc17dd7 | |||
| 7c53b985cd | |||
| c8df26f227 | |||
| 3bc9671905 | |||
| 7fd959e6e3 | |||
| 24dc68652e | |||
| aad2d24c58 | |||
| 15d56abeb6 | |||
| d89ec67888 | |||
| f9f840fb9d | |||
| 8e8a2faa10 | |||
| 0c2784bb42 | |||
| c555cd0825 | |||
| 92e28326a4 | |||
| 2dbdd1f079 | |||
| fc58022d56 | |||
| ed9fe8a573 | |||
| 49e234d9f8 | |||
| a587ce88ea | |||
| a530371b6f | |||
| 0c5a83c021 | |||
| 12bb632371 | |||
| e4fc6774b1 | |||
| f57dfb3f2e | |||
| c3f7a049d8 | |||
| 3eee83c2f2 | |||
| ed8c662631 | |||
| 850df18d7c | |||
| 7f4da306c9 | |||
| 424a8ffaf4 | |||
| 8410e62531 | |||
| 533b686e0b | |||
| c6c54f90dc | |||
| 0f5fd4e48a | |||
| a7247e8a88 | |||
| c10a30346b | |||
| 436e86fdf2 | |||
| 29a137bde3 |
@@ -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,16 +8,12 @@ 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
|
||||||
- [ ] search existing issues by exception message
|
- search existing issues by exception message
|
||||||
(for example `JadxRuntimeException: Can't find immediate dominator for block`)
|
|
||||||
- [ ] check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
|
|
||||||
|
|
||||||
**Describe error**
|
**Describe error**
|
||||||
- provide full name of method or class with error
|
- full name of method or class with error
|
||||||
- provide full java stacktrace
|
- full java stacktrace (no need to copy method fallback code (commented pseudocode))
|
||||||
|
- **IMPORTANT!** attach or provide link to apk file (double check apk version)
|
||||||
**Note**: no need to copy method fallback code (commented pseudocode)
|
|
||||||
- attach or provide link to apk file (double check apk version)
|
|
||||||
|
|
||||||
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
|
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
name: Feature Request
|
||||||
|
about: Suggest an idea for jadx
|
||||||
|
title: "[feature]"
|
||||||
|
labels: new feature
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Describe your idea:*
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
:exclamation: Please review the [guidelines for contributing](https://github.com/skylot/jadx/blob/master/CONTRIBUTING.md#Pull-Request-Process)
|
||||||
|
|
||||||
|
### Description
|
||||||
|
Please describe your pull request.
|
||||||
|
Reference issue it fixes.
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 8
|
||||||
|
|
||||||
|
- name: Set jadx version
|
||||||
|
run: |
|
||||||
|
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||||
|
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
||||||
|
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: burrunan/gradle-cache-action@v1
|
||||||
|
name: Build with Gradle
|
||||||
|
env:
|
||||||
|
TERM: dumb
|
||||||
|
TEST_INPUT_PLUGIN: dx
|
||||||
|
with:
|
||||||
|
arguments: clean build dist copyExe --warning-mode=all
|
||||||
|
|
||||||
|
- name: Save bundle artifact
|
||||||
|
if: success() && github.event_name == 'push'
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
|
||||||
|
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
|
||||||
|
# Upload unpacked files for now
|
||||||
|
path: build/jadx/**/*
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Save exe artifact
|
||||||
|
if: success() && github.event_name == 'push'
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
|
||||||
|
path: build/*.exe
|
||||||
|
if-no-files-found: error
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
name: "Validate Gradle Wrapper"
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validation:
|
||||||
|
name: "Validation"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: gradle/wrapper-validation-action@v1
|
||||||
+4
-1
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
# IntelliJ Idea files
|
# IntelliJ Idea files
|
||||||
.idea/
|
.idea/
|
||||||
|
.run/
|
||||||
out/
|
out/
|
||||||
*.iml
|
*.iml
|
||||||
*.ipr
|
*.ipr
|
||||||
@@ -24,10 +25,12 @@ node_modules/
|
|||||||
|
|
||||||
jadx-output/
|
jadx-output/
|
||||||
*-tmp/
|
*-tmp/
|
||||||
|
**/tmp/
|
||||||
|
*.jobf
|
||||||
|
|
||||||
*.dex
|
|
||||||
*.class
|
*.class
|
||||||
*.dump
|
*.dump
|
||||||
*.log
|
*.log
|
||||||
*.cfg
|
*.cfg
|
||||||
*.orig
|
*.orig
|
||||||
|
quark.json
|
||||||
|
|||||||
+6
-14
@@ -7,26 +7,18 @@ before_script:
|
|||||||
|
|
||||||
stages:
|
stages:
|
||||||
- test
|
- test
|
||||||
- check
|
|
||||||
|
|
||||||
java-8:
|
java-8:
|
||||||
stage: test
|
stage: test
|
||||||
image: openjdk:8
|
image: openjdk:8
|
||||||
script: ./gradlew clean build
|
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
|
script: ./gradlew clean build dist copyExe --warning-mode=all
|
||||||
|
|
||||||
check:
|
java-latest:
|
||||||
stage: check
|
stage: test
|
||||||
image: openjdk:8
|
image: openjdk:latest
|
||||||
script:
|
script: java -version && ./gradlew clean build dist --warning-mode=all
|
||||||
- export JADX_LAST_TAG="$(git describe --abbrev=0 --tags)"
|
|
||||||
- export JADX_VERSION="${JADX_LAST_TAG:1}-dev-$(git rev-parse --short HEAD)"
|
|
||||||
- ./gradlew clean sonarqube -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN -Dsonar.branch.name=dev
|
|
||||||
- ./gradlew clean dist
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- build/jadx*.zip
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
[submodule "jadx-test-app/test-app"]
|
|
||||||
path = jadx-test-app/test-app
|
|
||||||
url = git://github.com/skylot/jadx-test-app.git
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
branch: 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
|
|
||||||
-40
@@ -1,40 +0,0 @@
|
|||||||
language: java
|
|
||||||
sudo: false
|
|
||||||
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
|
|
||||||
- oraclejdk8
|
|
||||||
- openjdk11
|
|
||||||
|
|
||||||
script: ./gradlew clean build
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- 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
|
|
||||||
jdk: openjdk8
|
|
||||||
if: branch = release AND repo = env(MAIN_REPO) AND type = push
|
|
||||||
script: bash scripts/travis-release.sh
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||||
|
level of experience, education, socio-economic status, nationality, personal
|
||||||
|
appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at skylot@gmail.com. All
|
||||||
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
|
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see
|
||||||
|
https://www.contributor-covenant.org/faq
|
||||||
+31
-6
@@ -1,7 +1,32 @@
|
|||||||
# Contribution
|
# Contributing
|
||||||
|
|
||||||
To support this project you can:
|
Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions with the project.
|
||||||
- Post thoughts about new features/optimizations that important to you
|
|
||||||
- Submit bug using one of following patterns:
|
## Open Issue
|
||||||
* Java code examples which decompiles incorrectly
|
|
||||||
* Error log and link to _public available_ apk file or app page on Google play
|
1. Before proceed please do:
|
||||||
|
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
|
||||||
|
- search existing issues by exception message
|
||||||
|
|
||||||
|
2. Describe error
|
||||||
|
**Describe error**
|
||||||
|
- full name of method or class with error
|
||||||
|
- full java stacktrace (no need to copy method fallback code (commented pseudocode))
|
||||||
|
- **IMPORTANT!:** attach or provide link to apk file (double check apk version)
|
||||||
|
|
||||||
|
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
|
||||||
|
|
||||||
|
|
||||||
|
## Pull Request Process
|
||||||
|
|
||||||
|
1. Please don't submit any code style fixes, dependencies updates or other changes which are not fixing any issues.
|
||||||
|
|
||||||
|
1. Before start working on PR please discuss the change you wish to make via issue. PR without corresponding issue will be rejected.
|
||||||
|
|
||||||
|
1. Use only features and API from Java 8 or below.
|
||||||
|
|
||||||
|
1. If possible don't add additional dependencies especially if they are big.
|
||||||
|
|
||||||
|
1. Make sure your code is correctly formatted, see description here: [Code Formatting](https://github.com/skylot/jadx/wiki/Code-Formatting).
|
||||||
|
|
||||||
|
1. Make sure your changes is passing build: `./gradlew clean build dist`
|
||||||
|
|||||||
@@ -1,111 +1,128 @@
|
|||||||
|
<img src="https://raw.githubusercontent.com/skylot/jadx/master/jadx-gui/src/main/resources/logos/jadx-logo.png" width="64" align="left" />
|
||||||
|
|
||||||
## JADX
|
## JADX
|
||||||
|
|
||||||
[](https://travis-ci.org/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)
|
|
||||||
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
|
||||||
[](https://github.com/semantic-release/semantic-release)
|
[](https://github.com/semantic-release/semantic-release)
|
||||||
|
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||||
|
|
||||||
**jadx** - Dex to Java decompiler
|
**jadx** - Dex to Java decompiler
|
||||||
|
|
||||||
Command line and GUI tools for produce 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
|
||||||
|
|
||||||

|
**Main features:**
|
||||||
|
- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
|
||||||
|
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
|
||||||
|
- deobfuscator included
|
||||||
|
|
||||||
|
**jadx-gui features:**
|
||||||
|
- view decompiled code with highlighted syntax
|
||||||
|
- jump to declaration
|
||||||
|
- find usage
|
||||||
|
- full text search
|
||||||
|
- smali debugger (thanks to [@LBJ-the-GOAT](https://github.com/LBJ-the-GOAT)), check [wiki page](https://github.com/skylot/jadx/wiki/Smali-debugger) for setup and usage
|
||||||
|
|
||||||
### Downloads
|
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
|
||||||
- latest [unstable build:  ](https://bintray.com/skylot/jadx/unstable/_latestVersion#files)
|
|
||||||
|
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
|
||||||
|
|
||||||
|
### Download
|
||||||
- release from [github: ](https://github.com/skylot/jadx/releases/latest)
|
- release 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/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` - graphical 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 64-bit version
|
**Note:** ensure you have installed Java 11 or later 64-bit version.
|
||||||
|
For windows you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
|
||||||
|
|
||||||
|
### Install
|
||||||
|
1. Arch linux
|
||||||
|
```bash
|
||||||
|
sudo pacman -S jadx
|
||||||
|
```
|
||||||
|
2. macOS
|
||||||
|
```bash
|
||||||
|
brew install jadx
|
||||||
|
```
|
||||||
|
|
||||||
### Related projects:
|
### Build from source
|
||||||
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
|
|
||||||
|
|
||||||
|
|
||||||
### Building jadx from source
|
|
||||||
JDK 8 or higher must be installed:
|
JDK 8 or higher must be installed:
|
||||||
|
```
|
||||||
git clone https://github.com/skylot/jadx.git
|
git clone https://github.com/skylot/jadx.git
|
||||||
cd jadx
|
cd jadx
|
||||||
./gradlew dist
|
./gradlew dist
|
||||||
|
```
|
||||||
|
|
||||||
(on Windows, use `gradlew.bat` instead of `./gradlew`)
|
(on Windows, use `gradlew.bat` instead of `./gradlew`)
|
||||||
|
|
||||||
Scripts for run jadx will be placed in `build/jadx/bin`
|
Scripts for run jadx will be placed in `build/jadx/bin`
|
||||||
and also packed to `build/jadx-<version>.zip`
|
and also packed to `build/jadx-<version>.zip`
|
||||||
|
|
||||||
### macOS
|
|
||||||
You can install using brew:
|
|
||||||
|
|
||||||
brew install jadx
|
|
||||||
|
|
||||||
### Run
|
|
||||||
Run **jadx** on itself:
|
|
||||||
|
|
||||||
cd build/jadx/
|
|
||||||
bin/jadx -d out lib/jadx-core-*.jar
|
|
||||||
# or
|
|
||||||
bin/jadx-gui lib/jadx-core-*.jar
|
|
||||||
|
|
||||||
|
|
||||||
### 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
|
||||||
-j, --threads-count - processing threads count
|
|
||||||
-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
|
||||||
--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
|
||||||
--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-rewrite-cfg - force to ignore and overwrite deobfuscation map file
|
||||||
--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'
|
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
||||||
|
--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 - make simple dump (using goto instead of 'if', 'for', etc)
|
||||||
-v, --verbose - verbose output
|
--comments-level - set code comments level, values: none, user_only, error, warn, info, debug, default: info
|
||||||
|
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
|
||||||
|
-v, --verbose - verbose output (set --log-level to DEBUG)
|
||||||
|
-q, --quiet - turn off output (set --log-level to QUIET)
|
||||||
--version - print jadx version
|
--version - print jadx version
|
||||||
-h, --help - print this help
|
-h, --help - print this help
|
||||||
Example:
|
Examples:
|
||||||
jadx -d out classes.dex
|
jadx -d out classes.dex
|
||||||
jadx --rename-flags "none" classes.dex
|
jadx --rename-flags "none" classes.dex
|
||||||
jadx --rename-flags "valid,printable" classes.dex
|
jadx --rename-flags "valid, printable" classes.dex
|
||||||
|
jadx --log-level ERROR 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
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
##### Out of memory error:
|
Please check wiki page [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A)
|
||||||
- Reduce processing threads count (`-j` option)
|
|
||||||
- Increase maximum java heap size:
|
### Contributing
|
||||||
* command line (example for linux):
|
To support this project you can:
|
||||||
`JAVA_OPTS="-Xmx4G" jadx -j 1 some.apk`
|
- Post thoughts about new features/optimizations that important to you
|
||||||
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
|
- Submit decompilation issues, please read before proceed: [Open issue](CONTRIBUTING.md#Open-Issue)
|
||||||
`DEFAULT_JVM_OPTS="-Xmx2500M"`
|
- Open pull request, please follow these rules: [Pull Request Process](CONTRIBUTING.md#Pull-Request-Process)
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
*Licensed under the Apache 2.0 License*
|
*Licensed under the Apache 2.0 License*
|
||||||
|
|
||||||
*Copyright 2018 by Skylot*
|
|
||||||
|
|||||||
+83
-87
@@ -1,9 +1,6 @@
|
|||||||
import com.diffplug.spotless.LineEnding
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'org.sonarqube' version '2.7'
|
id 'com.github.ben-manes.versions' version '0.39.0'
|
||||||
id 'com.github.ben-manes.versions' version '0.21.0'
|
id 'com.diffplug.spotless' version '6.0.0'
|
||||||
id "com.diffplug.gradle.spotless" version "3.21.1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||||
@@ -12,42 +9,46 @@ println("jadx version: ${jadxVersion}")
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'jacoco'
|
|
||||||
apply plugin: 'checkstyle'
|
apply plugin: 'checkstyle'
|
||||||
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
version = jadxVersion
|
version = jadxVersion
|
||||||
|
|
||||||
tasks.withType(JavaCompile) {
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
|
|
||||||
if (!"$it".contains(':jadx-samples:')) {
|
|
||||||
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileJava {
|
compileJava {
|
||||||
options.encoding = "UTF-8"
|
options.encoding = "UTF-8"
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
version = jadxVersion
|
|
||||||
manifest {
|
manifest {
|
||||||
mainAttributes('jadx-version': jadxVersion)
|
mainAttributes('jadx-version': jadxVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
mavenJava(MavenPublication) {
|
||||||
|
from components.java
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'org.slf4j:slf4j-api:1.7.26'
|
implementation 'org.slf4j:slf4j-api:1.7.32'
|
||||||
|
compileOnly 'org.jetbrains:annotations:23.0.0'
|
||||||
|
|
||||||
testCompile 'ch.qos.logback:logback-classic:1.2.3'
|
testImplementation 'ch.qos.logback:logback-classic:1.2.7'
|
||||||
testCompile 'org.hamcrest:hamcrest-library:2.1'
|
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||||
testCompile 'org.mockito:mockito-core:2.25.1'
|
testImplementation 'org.mockito:mockito-core:4.1.0'
|
||||||
|
testImplementation 'org.assertj:assertj-core:3.21.0'
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.1'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.1'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||||
|
|
||||||
testCompile 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
|
testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
|
||||||
|
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@@ -57,31 +58,8 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
|
||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
|
|
||||||
jacoco {
|
|
||||||
toolVersion = "0.8.2"
|
|
||||||
}
|
|
||||||
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/**/*'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
spotless {
|
spotless {
|
||||||
@@ -90,73 +68,93 @@ spotless {
|
|||||||
include 'jadx-cli/src/**/java/**/*.java'
|
include 'jadx-cli/src/**/java/**/*.java'
|
||||||
include 'jadx-core/src/**/java/**/*.java'
|
include 'jadx-core/src/**/java/**/*.java'
|
||||||
include 'jadx-gui/src/**/java/**/*.java'
|
include 'jadx-gui/src/**/java/**/*.java'
|
||||||
|
include 'jadx-plugins/**/java/**/*.java'
|
||||||
}
|
}
|
||||||
|
|
||||||
importOrderFile 'config/code-formatter/eclipse.importorder'
|
importOrderFile 'config/code-formatter/eclipse.importorder'
|
||||||
eclipse().configFile 'config/code-formatter/eclipse.xml'
|
eclipse().configFile 'config/code-formatter/eclipse.xml'
|
||||||
removeUnusedImports()
|
if (JavaVersion.current() < JavaVersion.VERSION_16) {
|
||||||
|
removeUnusedImports()
|
||||||
|
} else {
|
||||||
|
// google-format broken on java 16 (https://github.com/diffplug/spotless/issues/834)
|
||||||
|
println('Warning! Unused imports remove is disabled for Java 16')
|
||||||
|
}
|
||||||
|
|
||||||
lineEndings(LineEnding.UNIX)
|
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
||||||
encoding("UTF-8")
|
encoding("UTF-8")
|
||||||
trimTrailingWhitespace()
|
trimTrailingWhitespace()
|
||||||
endWithNewline()
|
endWithNewline()
|
||||||
}
|
}
|
||||||
format 'misc', {
|
format 'misc', {
|
||||||
target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties'
|
target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties'
|
||||||
targetExclude "jadx-test-app/test-app/**", ".gradle/**", ".idea/**"
|
targetExclude ".gradle/**", ".idea/**", "*/build/**"
|
||||||
|
|
||||||
lineEndings(LineEnding.UNIX)
|
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
||||||
encoding("UTF-8")
|
encoding("UTF-8")
|
||||||
trimTrailingWhitespace()
|
trimTrailingWhitespace()
|
||||||
endWithNewline()
|
endWithNewline()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyUpdates.resolutionStrategy = {
|
dependencyUpdates {
|
||||||
componentSelection { rules ->
|
resolutionStrategy {
|
||||||
rules.all { ComponentSelection selection ->
|
componentSelection { rules ->
|
||||||
boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
|
rules.all { ComponentSelection selection ->
|
||||||
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
|
boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
|
||||||
}
|
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
|
||||||
if (rejected) {
|
}
|
||||||
selection.reject('Release candidate')
|
if (rejected) {
|
||||||
|
selection.reject('Release candidate')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 dist {
|
||||||
|
group 'jadx'
|
||||||
|
description = 'Build jadx distribution zip'
|
||||||
|
|
||||||
|
dependsOn(pack)
|
||||||
|
|
||||||
|
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
|
||||||
|
if (os.isWindows()) {
|
||||||
|
dependsOn('copyExe')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task pack(type: Zip, dependsOn: copyArtifacts) {
|
task distWin(type: Copy, dependsOn: 'jadx-gui:distWinWithJre') {
|
||||||
|
group 'jadx'
|
||||||
|
description = 'Copy bundle to build dir'
|
||||||
destinationDir buildDir
|
destinationDir buildDir
|
||||||
archiveName "jadx-${jadxVersion}.zip"
|
from(tasks.getByPath('jadx-gui:distWinWithJre').outputs) {
|
||||||
from copyArtifacts.destinationDir
|
include '*.zip'
|
||||||
}
|
}
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
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'
|
|
||||||
}
|
|
||||||
|
|
||||||
task dist(dependsOn: [pack, copyExe]) {
|
|
||||||
group 'jadx'
|
|
||||||
description = 'Build jadx distribution zip'
|
|
||||||
}
|
|
||||||
|
|
||||||
task samples(dependsOn: 'jadx-samples:samples') {
|
|
||||||
group 'jadx'
|
|
||||||
}
|
|
||||||
|
|
||||||
task testAppCheck(dependsOn: 'jadx-test-app:testAppCheck') {
|
|
||||||
group 'jadx'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task cleanBuildDir(type: Delete) {
|
task cleanBuildDir(type: Delete) {
|
||||||
@@ -164,6 +162,4 @@ task cleanBuildDir(type: Delete) {
|
|||||||
delete buildDir
|
delete buildDir
|
||||||
}
|
}
|
||||||
|
|
||||||
test.dependsOn(samples)
|
|
||||||
|
|
||||||
clean.dependsOn(cleanBuildDir)
|
clean.dependsOn(cleanBuildDir)
|
||||||
|
|||||||
@@ -78,7 +78,9 @@
|
|||||||
|
|
||||||
<!-- annotations -->
|
<!-- annotations -->
|
||||||
<module name="AnnotationLocation"/>
|
<module name="AnnotationLocation"/>
|
||||||
<module name="AnnotationUseStyle"/>
|
<module name="AnnotationUseStyle">
|
||||||
|
<property name="elementStyle" value="compact"/>
|
||||||
|
</module>
|
||||||
<module name="MissingOverride"/>
|
<module name="MissingOverride"/>
|
||||||
<!-- <module name="MissingDeprecated"/> -->
|
<!-- <module name="MissingDeprecated"/> -->
|
||||||
|
|
||||||
@@ -117,6 +119,11 @@
|
|||||||
<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>
|
</module>
|
||||||
|
|
||||||
<module name="NewlineAtEndOfFile"/>
|
<module name="NewlineAtEndOfFile"/>
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
org.gradle.daemon=false
|
org.gradle.daemon=false
|
||||||
|
org.gradle.warning.mode=all
|
||||||
|
|||||||
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-5.2.1-bin.zip
|
distributionSha256Sum=de8f52ad49bdc759164f72439a3bf56ddb1589c4cde802d3cec7d6ad0e0ee410
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -1,78 +1,129 @@
|
|||||||
#!/usr/bin/env sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
#
|
||||||
## 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=""
|
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
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
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
|
||||||
@@ -81,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
|
||||||
@@ -89,84 +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, switch paths to Windows format before running java
|
|
||||||
if $cygwin ; 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=$((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" )
|
||||||
|
|
||||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
|
||||||
cd "$(dirname "$0")"
|
# 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
|
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" "$@"
|
||||||
|
|||||||
Vendored
+24
-19
@@ -1,3 +1,19 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
|||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS=
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -35,7 +54,7 @@ goto fail
|
|||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
@@ -45,28 +64,14 @@ echo location of your Java installation.
|
|||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:init
|
|
||||||
@rem Get command-line arguments, handling Windows variants
|
|
||||||
|
|
||||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
|
||||||
|
|
||||||
:win9xME_args
|
|
||||||
@rem Slurp the command line arguments.
|
|
||||||
set CMD_LINE_ARGS=
|
|
||||||
set _SKIP=2
|
|
||||||
|
|
||||||
:win9xME_args_slurp
|
|
||||||
if "x%~1" == "x" goto execute
|
|
||||||
|
|
||||||
set CMD_LINE_ARGS=%*
|
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|||||||
+23
-22
@@ -1,28 +1,29 @@
|
|||||||
apply plugin: 'application'
|
plugins {
|
||||||
|
id 'application'
|
||||||
mainClassName = 'jadx.cli.JadxCLI'
|
}
|
||||||
applicationName = 'jadx'
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile(project(':jadx-core'))
|
implementation(project(':jadx-core'))
|
||||||
compile 'com.beust:jcommander:1.74'
|
|
||||||
compile 'ch.qos.logback:logback-classic:1.2.3'
|
runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
||||||
|
runtimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||||
|
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||||
|
|
||||||
|
implementation 'com.beust:jcommander:1.81'
|
||||||
|
implementation 'ch.qos.logback:logback-classic:1.2.7'
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
applicationName = 'jadx'
|
||||||
|
mainClass.set('jadx.cli.JadxCLI')
|
||||||
|
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationDistribution.with {
|
applicationDistribution.with {
|
||||||
into('') {
|
into('') {
|
||||||
from '../.'
|
from '../.'
|
||||||
include 'README.md'
|
include 'README.md'
|
||||||
include 'NOTICE'
|
include 'NOTICE'
|
||||||
include 'LICENSE'
|
include 'LICENSE'
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
startScripts {
|
|
||||||
defaultJvmOpts = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
|
|
||||||
doLast {
|
|
||||||
def str = windowsScript.text
|
|
||||||
str = str.replaceAll('set JAVA_EXE=%JAVA_HOME%/bin/java.exe', 'set JAVA_EXE="%JAVA_HOME%/bin/java.exe"')
|
|
||||||
windowsScript.text = str
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ 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 org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import com.beust.jcommander.JCommander;
|
import com.beust.jcommander.JCommander;
|
||||||
import com.beust.jcommander.ParameterDescription;
|
import com.beust.jcommander.ParameterDescription;
|
||||||
import com.beust.jcommander.ParameterException;
|
import com.beust.jcommander.ParameterException;
|
||||||
@@ -78,23 +80,37 @@ public class JCommanderWrapper<T> {
|
|||||||
}
|
}
|
||||||
StringBuilder opt = new StringBuilder();
|
StringBuilder opt = new StringBuilder();
|
||||||
opt.append(" ").append(p.getNames());
|
opt.append(" ").append(p.getNames());
|
||||||
|
String description = p.getDescription();
|
||||||
addSpaces(opt, maxNamesLen - opt.length() + 3);
|
addSpaces(opt, maxNamesLen - opt.length() + 3);
|
||||||
opt.append("- ").append(p.getDescription());
|
if (description.contains("\n")) {
|
||||||
addDefaultValue(args, f, opt);
|
String[] lines = description.split("\n");
|
||||||
|
opt.append("- ").append(lines[0]);
|
||||||
|
for (int i = 1; i < lines.length; i++) {
|
||||||
|
opt.append('\n');
|
||||||
|
addSpaces(opt, maxNamesLen + 5);
|
||||||
|
opt.append(lines[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opt.append("- ").append(description);
|
||||||
|
}
|
||||||
|
String defaultValue = getDefaultValue(args, f, opt);
|
||||||
|
if (defaultValue != null) {
|
||||||
|
opt.append(", default: ").append(defaultValue);
|
||||||
|
}
|
||||||
out.println(opt);
|
out.println(opt);
|
||||||
}
|
}
|
||||||
out.println("Example:");
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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();
|
||||||
@@ -102,26 +118,26 @@ public class JCommanderWrapper<T> {
|
|||||||
return fieldList;
|
return fieldList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
|
@Nullable
|
||||||
Class<?> fieldType = f.getType();
|
private String getDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
|
||||||
if (fieldType == int.class) {
|
try {
|
||||||
try {
|
Class<?> fieldType = f.getType();
|
||||||
int val = f.getInt(args);
|
if (fieldType == int.class) {
|
||||||
opt.append(" (default: ").append(val).append(')');
|
return Integer.toString(f.getInt(args));
|
||||||
} catch (Exception e) {
|
|
||||||
// ignore
|
|
||||||
}
|
}
|
||||||
}
|
if (fieldType == String.class) {
|
||||||
if (fieldType == String.class) {
|
return (String) f.get(args);
|
||||||
try {
|
}
|
||||||
String val = (String) f.get(args);
|
if (Enum.class.isAssignableFrom(fieldType)) {
|
||||||
|
Enum<?> val = (Enum<?>) f.get(args);
|
||||||
if (val != null) {
|
if (val != null) {
|
||||||
opt.append(" (default: ").append(val).append(')');
|
return val.name().toLowerCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
// ignore
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addSpaces(StringBuilder str, int count) {
|
private static void addSpaces(StringBuilder str, int count) {
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ 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.SimpleCodeWriter;
|
||||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
public class JadxCLI {
|
public class JadxCLI {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
||||||
@@ -13,35 +16,48 @@ public class JadxCLI {
|
|||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
try {
|
try {
|
||||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
result = execute(args);
|
||||||
if (jadxArgs.processArgs(args)) {
|
} catch (JadxArgsValidateException e) {
|
||||||
result = processAndSave(jadxArgs);
|
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||||
}
|
result = 1;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("jadx error: {}", e.getMessage(), e);
|
LOG.error("jadx error: {}", e.getMessage(), e);
|
||||||
result = 1;
|
result = 1;
|
||||||
} finally {
|
} finally {
|
||||||
|
FileUtils.deleteTempRootDir();
|
||||||
System.exit(result);
|
System.exit(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int processAndSave(JadxCLIArgs inputArgs) {
|
public static int execute(String[] args) {
|
||||||
JadxArgs args = inputArgs.toJadxArgs();
|
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||||
JadxDecompiler jadx = new JadxDecompiler(args);
|
if (jadxArgs.processArgs(args)) {
|
||||||
try {
|
return processAndSave(jadxArgs.toJadxArgs());
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int processAndSave(JadxArgs jadxArgs) {
|
||||||
|
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||||
|
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
|
||||||
|
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
||||||
jadx.load();
|
jadx.load();
|
||||||
} catch (JadxArgsValidateException e) {
|
if (LogHelper.getLogLevel() == LogHelper.LogLevelEnum.QUIET) {
|
||||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
jadx.save();
|
||||||
return 1;
|
} 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
int errorsCount = jadx.getErrorsCount();
|
||||||
|
if (errorsCount != 0) {
|
||||||
|
jadx.printErrorsReport();
|
||||||
|
LOG.error("finished with errors, count: {}", errorsCount);
|
||||||
|
} else {
|
||||||
|
LOG.info("done");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
jadx.save();
|
return 0;
|
||||||
int errorsCount = jadx.getErrorsCount();
|
|
||||||
if (errorsCount != 0) {
|
|
||||||
jadx.printErrorsReport();
|
|
||||||
LOG.error("finished with errors");
|
|
||||||
} else {
|
|
||||||
LOG.info("done");
|
|
||||||
}
|
|
||||||
return errorsCount;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
package jadx.cli;
|
package jadx.cli;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.beust.jcommander.IStringConverter;
|
import com.beust.jcommander.IStringConverter;
|
||||||
import com.beust.jcommander.Parameter;
|
import com.beust.jcommander.Parameter;
|
||||||
|
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
import jadx.api.CommentsLevel;
|
||||||
import ch.qos.logback.core.Appender;
|
|
||||||
|
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.JadxArgs.RenameEnum;
|
import jadx.api.JadxArgs.RenameEnum;
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
@@ -25,7 +20,7 @@ import jadx.core.utils.files.FileUtils;
|
|||||||
|
|
||||||
public class JadxCLIArgs {
|
public class JadxCLIArgs {
|
||||||
|
|
||||||
@Parameter(description = "<input file> (.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")
|
||||||
@@ -64,9 +59,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;
|
||||||
|
|
||||||
@@ -85,19 +86,29 @@ 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(
|
||||||
|
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-rewrite-cfg" }, description = "force to ignore and overwrite deobfuscation map file")
|
||||||
protected boolean deobfuscationForceSave = false;
|
protected boolean deobfuscationForceSave = false;
|
||||||
|
|
||||||
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
||||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
||||||
|
|
||||||
|
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
|
||||||
|
protected boolean deobfuscationParseKotlinMetadata = false;
|
||||||
|
|
||||||
@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);
|
||||||
@@ -114,9 +125,26 @@ public class JadxCLIArgs {
|
|||||||
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
|
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
|
||||||
protected boolean fallbackMode = false;
|
protected boolean fallbackMode = false;
|
||||||
|
|
||||||
@Parameter(names = { "-v", "--verbose" }, description = "verbose output")
|
@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)")
|
||||||
protected boolean verbose = false;
|
protected boolean verbose = false;
|
||||||
|
|
||||||
|
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
|
||||||
|
protected boolean quiet = false;
|
||||||
|
|
||||||
@Parameter(names = { "--version" }, description = "print jadx version")
|
@Parameter(names = { "--version" }, description = "print jadx version")
|
||||||
protected boolean printVersion = false;
|
protected boolean printVersion = false;
|
||||||
|
|
||||||
@@ -158,15 +186,7 @@ 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);
|
||||||
}
|
}
|
||||||
if (verbose) {
|
LogHelper.setLogLevelFromArgs(this);
|
||||||
ch.qos.logback.classic.Logger rootLogger =
|
|
||||||
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
|
||||||
// remove INFO ThresholdFilter
|
|
||||||
Appender<ILoggingEvent> appender = rootLogger.getAppender("STDOUT");
|
|
||||||
if (appender != null) {
|
|
||||||
appender.clearAllFilters();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (JadxException e) {
|
} catch (JadxException e) {
|
||||||
System.err.println("ERROR: " + e.getMessage());
|
System.err.println("ERROR: " + e.getMessage());
|
||||||
jcw.printUsage();
|
jcw.printUsage();
|
||||||
@@ -194,20 +214,23 @@ public class JadxCLIArgs {
|
|||||||
args.setRawCFGOutput(rawCfgOutput);
|
args.setRawCFGOutput(rawCfgOutput);
|
||||||
args.setReplaceConsts(replaceConsts);
|
args.setReplaceConsts(replaceConsts);
|
||||||
args.setDeobfuscationOn(deobfuscationOn);
|
args.setDeobfuscationOn(deobfuscationOn);
|
||||||
|
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile));
|
||||||
args.setDeobfuscationForceSave(deobfuscationForceSave);
|
args.setDeobfuscationForceSave(deobfuscationForceSave);
|
||||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||||
|
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
||||||
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);
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,10 +278,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,6 +302,10 @@ public class JadxCLIArgs {
|
|||||||
return deobfuscationMaxLength;
|
return deobfuscationMaxLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDeobfuscationMapFile() {
|
||||||
|
return deobfuscationMapFile;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDeobfuscationForceSave() {
|
public boolean isDeobfuscationForceSave() {
|
||||||
return deobfuscationForceSave;
|
return deobfuscationForceSave;
|
||||||
}
|
}
|
||||||
@@ -279,6 +314,10 @@ public class JadxCLIArgs {
|
|||||||
return deobfuscationUseSourceNameAsAlias;
|
return deobfuscationUseSourceNameAsAlias;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDeobfuscationParseKotlinMetadata() {
|
||||||
|
return deobfuscationParseKotlinMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEscapeUnicode() {
|
public boolean isEscapeUnicode() {
|
||||||
return escapeUnicode;
|
return escapeUnicode;
|
||||||
}
|
}
|
||||||
@@ -319,6 +358,10 @@ public class JadxCLIArgs {
|
|||||||
return fsCaseSensitive;
|
return fsCaseSensitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommentsLevel getCommentsLevel() {
|
||||||
|
return commentsLevel;
|
||||||
|
}
|
||||||
|
|
||||||
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
||||||
private final String paramName;
|
private final String paramName;
|
||||||
|
|
||||||
@@ -337,17 +380,33 @@ 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) {
|
||||||
String values = Arrays.stream(RenameEnum.values())
|
|
||||||
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
|
|
||||||
.collect(Collectors.joining(", "));
|
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
'\'' + s + "' is unknown for parameter " + paramName
|
'\'' + s + "' is unknown for parameter " + paramName
|
||||||
+ ", possible values are " + values);
|
+ ", possible values are " + enumValuesString(RenameEnum.values()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 String enumValuesString(Enum<?>[] values) {
|
||||||
|
return Stream.of(values)
|
||||||
|
.map(v -> v.name().toLowerCase(Locale.ROOT))
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package jadx.cli;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.beust.jcommander.IStringConverter;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
|
import ch.qos.logback.classic.Logger;
|
||||||
|
|
||||||
|
import jadx.api.JadxDecompiler;
|
||||||
|
|
||||||
|
public class LogHelper {
|
||||||
|
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(LogHelper.class);
|
||||||
|
|
||||||
|
public enum LogLevelEnum {
|
||||||
|
QUIET(Level.OFF),
|
||||||
|
PROGRESS(Level.OFF),
|
||||||
|
ERROR(Level.ERROR),
|
||||||
|
WARN(Level.WARN),
|
||||||
|
INFO(Level.INFO),
|
||||||
|
DEBUG(Level.DEBUG);
|
||||||
|
|
||||||
|
private final Level level;
|
||||||
|
|
||||||
|
LogLevelEnum(Level level) {
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Level getLevel() {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LogLevelEnum logLevelValue;
|
||||||
|
|
||||||
|
public static void setLogLevelFromArgs(JadxCLIArgs args) {
|
||||||
|
if (isCustomLogConfig()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogLevelEnum logLevel = args.logLevel;
|
||||||
|
if (args.quiet) {
|
||||||
|
logLevel = LogLevelEnum.QUIET;
|
||||||
|
} else if (args.verbose) {
|
||||||
|
logLevel = LogLevelEnum.DEBUG;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyLogLevel(logLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void applyLogLevel(LogLevelEnum logLevel) {
|
||||||
|
logLevelValue = logLevel;
|
||||||
|
|
||||||
|
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static LogLevelEnum getLogLevel() {
|
||||||
|
return logLevelValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLevelForClass(Class<?> cls, Level 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=
|
||||||
|
*/
|
||||||
|
private static boolean isCustomLogConfig() {
|
||||||
|
try {
|
||||||
|
String logbackConfig = System.getProperty("logback.configurationFile");
|
||||||
|
if (logbackConfig == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG.debug("Use custom log config: {}", logbackConfig);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Failed to detect custom log config", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LogLevelConverter implements IStringConverter<LogLevelEnum> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LogLevelEnum convert(String value) {
|
||||||
|
try {
|
||||||
|
return LogLevelEnum.valueOf(value.toUpperCase());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
'\'' + value + "' is unknown log level, possible values are "
|
||||||
|
+ JadxCLIArgs.enumValuesString(LogLevelEnum.values()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package jadx.cli.clst;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.plugins.JadxPluginManager;
|
||||||
|
import jadx.api.plugins.input.JadxInputPlugin;
|
||||||
|
import jadx.api.plugins.input.data.ILoadResult;
|
||||||
|
import jadx.core.clsp.ClsSet;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.dex.visitors.SignatureProcessor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for convert dex or jar to jadx classes set (.jcst)
|
||||||
|
*/
|
||||||
|
public class ConvertToClsSet {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
|
||||||
|
|
||||||
|
public static void usage() {
|
||||||
|
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
if (args.length < 2) {
|
||||||
|
usage();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
|
||||||
|
Path output = inputPaths.remove(0);
|
||||||
|
|
||||||
|
JadxPluginManager pluginManager = new JadxPluginManager();
|
||||||
|
List<ILoadResult> loadedInputs = new ArrayList<>();
|
||||||
|
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||||
|
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
|
||||||
|
}
|
||||||
|
|
||||||
|
JadxArgs jadxArgs = new JadxArgs();
|
||||||
|
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
|
||||||
|
RootNode root = new RootNode(jadxArgs);
|
||||||
|
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);
|
||||||
|
set.loadFrom(root);
|
||||||
|
set.save(output);
|
||||||
|
|
||||||
|
LOG.info("Output: {}", output);
|
||||||
|
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,14 +1,11 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
|
||||||
<level>INFO</level>
|
|
||||||
</filter>
|
|
||||||
<encoder>
|
<encoder>
|
||||||
<pattern>%-5level - %msg%n</pattern>
|
<pattern>%-5level - %msg%n</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<root level="DEBUG">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
</root>
|
</root>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package jadx.cli;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.LinkOption;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.PathMatcher;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class TestInput {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(TestInput.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDexInput() throws Exception {
|
||||||
|
decompile("dex", "samples/hello.dex");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmaliInput() throws Exception {
|
||||||
|
decompile("smali", "samples/HelloWorld.smali");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClassInput() throws Exception {
|
||||||
|
decompile("class", "samples/HelloWorld.class");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleInput() throws Exception {
|
||||||
|
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
|
||||||
|
List<String> args = new ArrayList<>();
|
||||||
|
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
||||||
|
args.add("-v");
|
||||||
|
args.add("-d");
|
||||||
|
args.add(tempDir.toAbsolutePath().toString());
|
||||||
|
|
||||||
|
for (String inputSample : inputSamples) {
|
||||||
|
URL resource = getClass().getClassLoader().getResource(inputSample);
|
||||||
|
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> resultJavaFiles = collectJavaFilesInDir(tempDir);
|
||||||
|
assertThat(resultJavaFiles).isNotEmpty();
|
||||||
|
|
||||||
|
// do not copy input files as resources
|
||||||
|
PathMatcher logAllFiles = path -> {
|
||||||
|
LOG.debug("File in result dir: {}", path);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
for (Path path : collectFilesInDir(tempDir, logAllFiles)) {
|
||||||
|
for (String inputSample : inputSamples) {
|
||||||
|
assertThat(path.toAbsolutePath().toString()).doesNotContain(inputSample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Path> collectJavaFilesInDir(Path dir) throws IOException {
|
||||||
|
PathMatcher javaMatcher = dir.getFileSystem().getPathMatcher("glob:**.java");
|
||||||
|
return collectFilesInDir(dir, javaMatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Path> collectFilesInDir(Path dir, PathMatcher matcher) throws IOException {
|
||||||
|
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||||
|
return pathStream
|
||||||
|
.filter(p -> Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS))
|
||||||
|
.filter(matcher::matches)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void cleanup() {
|
||||||
|
FileUtils.clearTempRootDir();
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,26 @@
|
|||||||
|
.class Lsmali/HelloWorld;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.source "HelloWorld.java"
|
||||||
|
|
||||||
|
.method constructor <init>()V
|
||||||
|
.registers 1
|
||||||
|
|
||||||
|
.line 1
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static main([Ljava/lang/String;)V
|
||||||
|
.registers 2
|
||||||
|
|
||||||
|
.line 3
|
||||||
|
sget-object p0, Ljava/lang/System;->out:Ljava/io/PrintStream;
|
||||||
|
|
||||||
|
const-string v0, "Hello, World"
|
||||||
|
|
||||||
|
invoke-virtual {p0, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
.line 4
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
Binary file not shown.
+21
-13
@@ -1,20 +1,28 @@
|
|||||||
ext.jadxClasspath = 'clsp-data/android-5.1.jar'
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
runtime files(jadxClasspath)
|
api(project(':jadx-plugins:jadx-plugins-api'))
|
||||||
|
|
||||||
compile files('lib/dx-1.16.jar')
|
implementation 'com.google.code.gson:gson:2.8.9'
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
|
||||||
compile 'org.ow2.asm:asm:7.1'
|
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||||
compile 'org.jetbrains:annotations:17.0.0'
|
|
||||||
compile 'uk.com.robust-it:cloning:1.9.12'
|
|
||||||
compile 'com.google.code.gson:gson:2.8.5'
|
|
||||||
|
|
||||||
compile 'org.smali:baksmali:2.2.7'
|
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
||||||
compile('org.smali:smali:2.2.7') {
|
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||||
exclude group: 'com.google.guava'
|
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||||
}
|
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||||
compile 'com.google.guava:guava:27.1-jre'
|
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
|
||||||
|
|
||||||
testCompile 'org.apache.commons:commons-lang3:3.8.1'
|
testImplementation('tools.profiler:async-profiler:1.8.3')
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
exclude '**/tmp/*'
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -2,32 +2,27 @@ package jadx.api;
|
|||||||
|
|
||||||
public final class CodePosition {
|
public final class CodePosition {
|
||||||
|
|
||||||
private final JavaNode node;
|
|
||||||
private final int line;
|
private final int line;
|
||||||
private final int offset;
|
private final int offset;
|
||||||
|
private final int pos;
|
||||||
|
|
||||||
public CodePosition(JavaNode node, int line, int offset) {
|
public CodePosition(int line, int offset, int pos) {
|
||||||
this.node = node;
|
|
||||||
this.line = line;
|
this.line = line;
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
|
this.pos = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CodePosition(int line) {
|
||||||
|
this(line, 0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public CodePosition(int line, int offset) {
|
public CodePosition(int line, int offset) {
|
||||||
this.node = null;
|
this(line, offset, -1);
|
||||||
this.line = line;
|
|
||||||
this.offset = offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public JavaNode getNode() {
|
public int getPos() {
|
||||||
return node;
|
return pos;
|
||||||
}
|
|
||||||
|
|
||||||
public JavaClass getJavaClass() {
|
|
||||||
JavaClass parent = node.getDeclaringClass();
|
|
||||||
if (parent == null && node instanceof JavaClass) {
|
|
||||||
return (JavaClass) node;
|
|
||||||
}
|
|
||||||
return parent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLine() {
|
public int getLine() {
|
||||||
@@ -62,8 +57,8 @@ public final class CodePosition {
|
|||||||
if (offset != 0) {
|
if (offset != 0) {
|
||||||
sb.append(':').append(offset);
|
sb.append(':').append(offset);
|
||||||
}
|
}
|
||||||
if (node != null) {
|
if (pos > 0) {
|
||||||
sb.append(' ').append(node);
|
sb.append('@').append(pos);
|
||||||
}
|
}
|
||||||
return sb.toString();
|
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,13 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public interface ICodeCache {
|
||||||
|
|
||||||
|
void add(String clsFullName, ICodeInfo codeInfo);
|
||||||
|
|
||||||
|
void remove(String clsFullName);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
ICodeInfo get(String clsFullName);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import jadx.api.impl.SimpleCodeInfo;
|
||||||
|
|
||||||
|
public interface ICodeInfo {
|
||||||
|
|
||||||
|
ICodeInfo EMPTY = new SimpleCodeInfo("");
|
||||||
|
|
||||||
|
String getCodeStr();
|
||||||
|
|
||||||
|
Map<Integer, Integer> getLineMapping();
|
||||||
|
|
||||||
|
Map<CodePosition, Object> getAnnotations();
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
int getLine();
|
||||||
|
|
||||||
|
void attachDefinition(ILineAttributeNode obj);
|
||||||
|
|
||||||
|
void attachAnnotation(Object obj);
|
||||||
|
|
||||||
|
void attachLineAnnotation(Object obj);
|
||||||
|
|
||||||
|
void attachSourceLine(int sourceLine);
|
||||||
|
|
||||||
|
ICodeInfo finish();
|
||||||
|
|
||||||
|
String getCodeStr();
|
||||||
|
|
||||||
|
int getLength();
|
||||||
|
|
||||||
|
StringBuilder getRawBuf();
|
||||||
|
|
||||||
|
Map<CodePosition, Object> getRawAnnotations();
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IDecompileScheduler {
|
||||||
|
List<List<JavaClass>> buildBatches(List<JavaClass> classes);
|
||||||
|
}
|
||||||
@@ -6,8 +6,13 @@ import java.util.Collections;
|
|||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import jadx.api.data.ICodeData;
|
||||||
|
import jadx.api.impl.AnnotatedCodeWriter;
|
||||||
|
import jadx.api.impl.InMemoryCodeCache;
|
||||||
|
|
||||||
public class JadxArgs {
|
public class JadxArgs {
|
||||||
|
|
||||||
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);
|
||||||
@@ -22,6 +27,9 @@ public class JadxArgs {
|
|||||||
private File outDirSrc;
|
private File outDirSrc;
|
||||||
private File outDirRes;
|
private File outDirRes;
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -32,7 +40,10 @@ public class JadxArgs {
|
|||||||
|
|
||||||
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;
|
||||||
@@ -45,6 +56,8 @@ public class JadxArgs {
|
|||||||
private boolean deobfuscationOn = false;
|
private boolean deobfuscationOn = false;
|
||||||
private boolean deobfuscationForceSave = false;
|
private boolean deobfuscationForceSave = false;
|
||||||
private boolean useSourceNameAsClassAlias = false;
|
private boolean useSourceNameAsClassAlias = false;
|
||||||
|
private boolean parseKotlinMetadata = false;
|
||||||
|
private File deobfuscationMapFile = null;
|
||||||
|
|
||||||
private int deobfuscationMinLength = 0;
|
private int deobfuscationMinLength = 0;
|
||||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||||
@@ -68,6 +81,10 @@ public class JadxArgs {
|
|||||||
|
|
||||||
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
|
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
|
||||||
|
|
||||||
|
private ICodeData codeData;
|
||||||
|
|
||||||
|
private CommentsLevel commentsLevel = CommentsLevel.INFO;
|
||||||
|
|
||||||
public JadxArgs() {
|
public JadxArgs() {
|
||||||
// use default options
|
// use default options
|
||||||
}
|
}
|
||||||
@@ -170,6 +187,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;
|
||||||
}
|
}
|
||||||
@@ -178,6 +203,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;
|
||||||
}
|
}
|
||||||
@@ -226,6 +267,14 @@ public class JadxArgs {
|
|||||||
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
|
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isParseKotlinMetadata() {
|
||||||
|
return parseKotlinMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParseKotlinMetadata(boolean parseKotlinMetadata) {
|
||||||
|
this.parseKotlinMetadata = parseKotlinMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
public int getDeobfuscationMinLength() {
|
public int getDeobfuscationMinLength() {
|
||||||
return deobfuscationMinLength;
|
return deobfuscationMinLength;
|
||||||
}
|
}
|
||||||
@@ -242,6 +291,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;
|
||||||
}
|
}
|
||||||
@@ -314,6 +371,14 @@ public class JadxArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRenameFlags(Set<RenameEnum> renameFlags) {
|
||||||
|
this.renameFlags = renameFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<RenameEnum> getRenameFlags() {
|
||||||
|
return renameFlags;
|
||||||
|
}
|
||||||
|
|
||||||
public OutputFormatEnum getOutputFormat() {
|
public OutputFormatEnum getOutputFormat() {
|
||||||
return outputFormat;
|
return outputFormat;
|
||||||
}
|
}
|
||||||
@@ -326,6 +391,38 @@ public class JadxArgs {
|
|||||||
this.outputFormat = outputFormat;
|
this.outputFormat = outputFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ICodeCache getCodeCache() {
|
||||||
|
return codeCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCodeCache(ICodeCache 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;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "JadxArgs{" + "inputFiles=" + inputFiles
|
return "JadxArgs{" + "inputFiles=" + inputFiles
|
||||||
@@ -341,8 +438,10 @@ public class JadxArgs {
|
|||||||
+ ", skipResources=" + skipResources
|
+ ", skipResources=" + skipResources
|
||||||
+ ", skipSources=" + skipSources
|
+ ", skipSources=" + skipSources
|
||||||
+ ", deobfuscationOn=" + deobfuscationOn
|
+ ", deobfuscationOn=" + deobfuscationOn
|
||||||
|
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
||||||
+ ", deobfuscationForceSave=" + deobfuscationForceSave
|
+ ", deobfuscationForceSave=" + deobfuscationForceSave
|
||||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||||
|
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||||
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
||||||
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
||||||
+ ", escapeUnicode=" + escapeUnicode
|
+ ", escapeUnicode=" + escapeUnicode
|
||||||
@@ -352,6 +451,9 @@ public class JadxArgs {
|
|||||||
+ ", fsCaseSensitive=" + fsCaseSensitive
|
+ ", fsCaseSensitive=" + fsCaseSensitive
|
||||||
+ ", renameFlags=" + renameFlags
|
+ ", renameFlags=" + renameFlags
|
||||||
+ ", outputFormat=" + outputFormat
|
+ ", outputFormat=" + outputFormat
|
||||||
|
+ ", commentsLevel=" + commentsLevel
|
||||||
|
+ ", codeCache=" + codeCache
|
||||||
|
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,14 +27,11 @@ public class JadxArgsValidator {
|
|||||||
if (inputFiles.isEmpty()) {
|
if (inputFiles.isEmpty()) {
|
||||||
throw new JadxArgsValidateException("Please specify input file");
|
throw new JadxArgsValidateException("Please specify input file");
|
||||||
}
|
}
|
||||||
if (inputFiles.size() > 1) {
|
for (File inputFile : inputFiles) {
|
||||||
for (File inputFile : inputFiles) {
|
String fileName = inputFile.getName();
|
||||||
String fileName = inputFile.getName();
|
if (fileName.startsWith("--")) {
|
||||||
if (fileName.startsWith("--")) {
|
throw new JadxArgsValidateException("Unknown argument: " + fileName);
|
||||||
throw new JadxArgsValidateException("Unknown argument: " + fileName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
throw new JadxArgsValidateException("Only one input file supported");
|
|
||||||
}
|
}
|
||||||
for (File file : inputFiles) {
|
for (File file : inputFiles) {
|
||||||
checkFile(file);
|
checkFile(file);
|
||||||
@@ -88,9 +85,6 @@ public class JadxArgsValidator {
|
|||||||
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) {
|
||||||
|
|||||||
@@ -1,44 +1,51 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
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 org.jf.baksmali.Adaptors.ClassDefinition;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jf.baksmali.BaksmaliOptions;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jf.dexlib2.DexFileFactory;
|
|
||||||
import org.jf.dexlib2.Opcodes;
|
|
||||||
import org.jf.dexlib2.dexbacked.DexBackedClassDef;
|
|
||||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
|
||||||
import org.jf.util.IndentingWriter;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.data.annotations.VarRef;
|
||||||
|
import jadx.api.plugins.JadxPlugin;
|
||||||
|
import jadx.api.plugins.JadxPluginManager;
|
||||||
|
import jadx.api.plugins.input.JadxInputPlugin;
|
||||||
|
import jadx.api.plugins.input.data.ILoadResult;
|
||||||
import jadx.core.Jadx;
|
import jadx.core.Jadx;
|
||||||
import jadx.core.ProcessClass;
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
|
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
|
||||||
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.InputFile;
|
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,13 +53,14 @@ 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"));
|
||||||
*
|
* try (JadxDecompiler jadx = new JadxDecompiler(args)) {
|
||||||
* JadxDecompiler jadx = new JadxDecompiler(args);
|
* jadx.load();
|
||||||
* jadx.load();
|
* jadx.save();
|
||||||
* jadx.save();
|
* }
|
||||||
* </code>
|
* </code>
|
||||||
* </pre>
|
* </pre>
|
||||||
* <p>
|
* <p>
|
||||||
@@ -60,30 +68,32 @@ 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());
|
||||||
* }
|
* }
|
||||||
* </code>
|
* </code>
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public final class JadxDecompiler {
|
public final class JadxDecompiler implements Closeable {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
||||||
|
|
||||||
private JadxArgs args;
|
private final JadxArgs args;
|
||||||
|
private final JadxPluginManager pluginManager = new JadxPluginManager();
|
||||||
private final List<InputFile> inputFiles = new ArrayList<>();
|
private final List<ILoadResult> loadedInputs = new ArrayList<>();
|
||||||
|
|
||||||
private RootNode root;
|
private RootNode root;
|
||||||
private List<IDexTreeVisitor> passes;
|
|
||||||
|
|
||||||
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 Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
|
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
|
||||||
private Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
||||||
private Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
|
||||||
|
|
||||||
public JadxDecompiler() {
|
public JadxDecompiler() {
|
||||||
this(new JadxArgs());
|
this(new JadxArgs());
|
||||||
@@ -96,54 +106,92 @@ public final class JadxDecompiler {
|
|||||||
public void load() {
|
public void load() {
|
||||||
reset();
|
reset();
|
||||||
JadxArgsValidator.validate(args);
|
JadxArgsValidator.validate(args);
|
||||||
init();
|
|
||||||
LOG.info("loading ...");
|
LOG.info("loading ...");
|
||||||
|
loadInputFiles();
|
||||||
loadFiles(args.getInputFiles());
|
|
||||||
|
|
||||||
root = new RootNode(args);
|
root = new RootNode(args);
|
||||||
root.load(inputFiles);
|
root.loadClasses(loadedInputs);
|
||||||
|
|
||||||
root.initClassPath();
|
root.initClassPath();
|
||||||
root.loadResources(getResources());
|
root.loadResources(getResources());
|
||||||
|
root.runPreDecompileStage();
|
||||||
initVisitors();
|
root.initPasses();
|
||||||
}
|
}
|
||||||
|
|
||||||
void init() {
|
private void loadInputFiles() {
|
||||||
this.passes = Jadx.getPassesList(args);
|
loadedInputs.clear();
|
||||||
|
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
||||||
|
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
|
||||||
|
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||||
|
ILoadResult loadResult = inputPlugin.loadFiles(inputFiles);
|
||||||
|
if (loadResult != null && !loadResult.isEmpty()) {
|
||||||
|
loadedInputs.add(loadResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
private void reset() {
|
||||||
|
root = null;
|
||||||
classes = null;
|
classes = null;
|
||||||
resources = null;
|
resources = null;
|
||||||
xmlParser = null;
|
binaryXmlParser = null;
|
||||||
root = null;
|
protoXmlParser = null;
|
||||||
passes = null;
|
|
||||||
|
classesMap.clear();
|
||||||
|
methodsMap.clear();
|
||||||
|
fieldsMap.clear();
|
||||||
|
|
||||||
|
closeInputs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeInputs() {
|
||||||
|
loadedInputs.forEach(load -> {
|
||||||
|
try {
|
||||||
|
load.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Failed to close input", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
loadedInputs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPlugin(JadxPlugin plugin) {
|
||||||
|
pluginManager.register(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getVersion() {
|
public static String getVersion() {
|
||||||
return Jadx.getVersion();
|
return Jadx.getVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadFiles(List<File> files) {
|
|
||||||
if (files.isEmpty()) {
|
|
||||||
throw new JadxRuntimeException("Empty file list");
|
|
||||||
}
|
|
||||||
inputFiles.clear();
|
|
||||||
for (File file : files) {
|
|
||||||
try {
|
|
||||||
InputFile.addFilesFrom(file, inputFiles, args.isSkipSources());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new JadxRuntimeException("Error load file: " + file, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save() {
|
public void save() {
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -167,20 +215,44 @@ public final class JadxDecompiler {
|
|||||||
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();
|
||||||
@@ -188,36 +260,51 @@ public final class JadxDecompiler {
|
|||||||
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) {
|
||||||
|
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
|
||||||
for (ResourceFile resourceFile : getResources()) {
|
for (ResourceFile resourceFile : getResources()) {
|
||||||
executor.execute(new ResourcesSaver(outDir, resourceFile));
|
if (resourceFile.getType() != ResourceType.ARSC
|
||||||
|
&& inputFileNames.contains(resourceFile.getOriginalName())) {
|
||||||
|
// ignore resource made from input file
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tasks.add(new ResourcesSaver(outDir, resourceFile));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void appendSourcesSave(ExecutorService executor, File outDir) {
|
private void appendSourcesSave(List<Runnable> tasks, File outDir) {
|
||||||
final Predicate<String> classFilter = args.getClassFilter();
|
Predicate<String> classFilter = args.getClassFilter();
|
||||||
for (JavaClass cls : getClasses()) {
|
List<JavaClass> classes = getClasses();
|
||||||
|
List<JavaClass> processQueue = new ArrayList<>(classes.size());
|
||||||
|
for (JavaClass cls : classes) {
|
||||||
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
|
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (classFilter != null && !classFilter.test(cls.getFullName())) {
|
if (classFilter != null && !classFilter.test(cls.getFullName())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
executor.execute(() -> {
|
processQueue.add(cls);
|
||||||
try {
|
}
|
||||||
cls.decompile();
|
for (List<JavaClass> decompileBatch : decompileScheduler.buildBatches(processQueue)) {
|
||||||
SaveCode.save(outDir, cls.getClassNode());
|
tasks.add(() -> {
|
||||||
} catch (Exception e) {
|
for (JavaClass cls : decompileBatch) {
|
||||||
LOG.error("Error saving class: {}", cls.getFullName(), e);
|
try {
|
||||||
|
ICodeInfo code = cls.getCodeInfo();
|
||||||
|
SaveCode.save(outDir, cls.getClassNode(), code);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Error saving class: {}", cls, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -248,7 +335,7 @@ public final class JadxDecompiler {
|
|||||||
if (root == null) {
|
if (root == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
resources = new ResourcesLoader(this).load(inputFiles);
|
resources = new ResourcesLoader(this).load();
|
||||||
}
|
}
|
||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
@@ -297,103 +384,268 @@ public final class JadxDecompiler {
|
|||||||
root.getErrorsCounter().printReport();
|
root.getErrorsCounter().printReport();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initVisitors() {
|
/**
|
||||||
for (IDexTreeVisitor pass : passes) {
|
* Internal API. Not Stable!
|
||||||
try {
|
*/
|
||||||
pass.init(root);
|
public RootNode getRoot() {
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void processClass(ClassNode cls) {
|
|
||||||
ProcessClass.process(cls, passes, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void generateSmali(ClassNode cls) {
|
|
||||||
Path path = cls.dex().getDexFile().getPath();
|
|
||||||
String className = Utils.makeQualifiedObjectName(cls.getClassInfo().getType().getObject());
|
|
||||||
try {
|
|
||||||
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(path.toFile(), Opcodes.getDefault());
|
|
||||||
boolean decompiled = false;
|
|
||||||
for (DexBackedClassDef classDef : dexFile.getClasses()) {
|
|
||||||
if (classDef.getType().equals(className)) {
|
|
||||||
ClassDefinition classDefinition = new ClassDefinition(new BaksmaliOptions(), classDef);
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
classDefinition.writeTo(new IndentingWriter(sw));
|
|
||||||
cls.setSmali(sw.toString());
|
|
||||||
decompiled = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!decompiled) {
|
|
||||||
LOG.error("Failed to find smali class {}", className);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Error generating smali", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RootNode getRoot() {
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<IDexTreeVisitor> getPasses() {
|
synchronized BinaryXMLParser getBinaryXmlParser() {
|
||||||
return passes;
|
if (binaryXmlParser == null) {
|
||||||
}
|
binaryXmlParser = new BinaryXMLParser(root);
|
||||||
|
|
||||||
synchronized BinaryXMLParser getXmlParser() {
|
|
||||||
if (xmlParser == null) {
|
|
||||||
xmlParser = new BinaryXMLParser(root);
|
|
||||||
}
|
}
|
||||||
return xmlParser;
|
return binaryXmlParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<ClassNode, JavaClass> getClassesMap() {
|
synchronized ProtoXMLParser getProtoXmlParser() {
|
||||||
return classesMap;
|
if (protoXmlParser == null) {
|
||||||
|
protoXmlParser = new ProtoXMLParser(root);
|
||||||
|
}
|
||||||
|
return protoXmlParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<MethodNode, JavaMethod> getMethodsMap() {
|
private void loadJavaClass(JavaClass javaClass) {
|
||||||
return methodsMap;
|
javaClass.getMethods().forEach(mth -> methodsMap.put(mth.getMethodNode(), mth));
|
||||||
|
javaClass.getFields().forEach(fld -> fieldsMap.put(fld.getFieldNode(), fld));
|
||||||
|
|
||||||
|
for (JavaClass innerCls : javaClass.getInnerClasses()) {
|
||||||
|
classesMap.put(innerCls.getClassNode(), innerCls);
|
||||||
|
loadJavaClass(innerCls);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JavaMethod getJavaMethodByNode(MethodNode mth) {
|
/**
|
||||||
|
* Get JavaClass by ClassNode without loading and decompilation
|
||||||
|
*/
|
||||||
|
JavaClass convertClassNode(ClassNode cls) {
|
||||||
|
return classesMap.compute(cls, (node, prevJavaCls) -> {
|
||||||
|
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) {
|
||||||
|
// keep previous variable
|
||||||
|
return prevJavaCls;
|
||||||
|
}
|
||||||
|
if (cls.isInner()) {
|
||||||
|
return new JavaClass(cls, convertClassNode(cls.getParentClass()));
|
||||||
|
}
|
||||||
|
return new JavaClass(cls, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable("For not generated classes")
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public JavaClass getJavaClassByNode(ClassNode cls) {
|
||||||
|
JavaClass javaClass = classesMap.get(cls);
|
||||||
|
if (javaClass != null && javaClass.getClassNode() == cls) {
|
||||||
|
return javaClass;
|
||||||
|
}
|
||||||
|
// load parent class if inner
|
||||||
|
ClassNode parentClass = cls.getTopParentClass();
|
||||||
|
if (parentClass.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (parentClass != cls) {
|
||||||
|
JavaClass parentJavaClass = classesMap.get(parentClass);
|
||||||
|
if (parentJavaClass == null) {
|
||||||
|
getClasses();
|
||||||
|
parentJavaClass = classesMap.get(parentClass);
|
||||||
|
}
|
||||||
|
loadJavaClass(parentJavaClass);
|
||||||
|
javaClass = classesMap.get(cls);
|
||||||
|
if (javaClass != null) {
|
||||||
|
return javaClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// class or parent classes can be excluded from generation
|
||||||
|
if (cls.hasNotGeneratedParent()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw new JadxRuntimeException("JavaClass not found by ClassNode: " + cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private JavaMethod getJavaMethodByNode(MethodNode mth) {
|
||||||
JavaMethod javaMethod = methodsMap.get(mth);
|
JavaMethod javaMethod = methodsMap.get(mth);
|
||||||
|
if (javaMethod != null && javaMethod.getMethodNode() == mth) {
|
||||||
|
return javaMethod;
|
||||||
|
}
|
||||||
|
if (mth.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// parent class not loaded yet
|
||||||
|
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
|
||||||
|
if (javaClass == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
loadJavaClass(javaClass);
|
||||||
|
javaMethod = methodsMap.get(mth);
|
||||||
if (javaMethod != null) {
|
if (javaMethod != null) {
|
||||||
return javaMethod;
|
return javaMethod;
|
||||||
}
|
}
|
||||||
// parent class not loaded yet
|
if (mth.getParentClass().hasNotGeneratedParent()) {
|
||||||
JavaClass javaClass = classesMap.get(mth.getParentClass());
|
return null;
|
||||||
if (javaClass != null) {
|
|
||||||
javaClass.decompile();
|
|
||||||
return methodsMap.get(mth);
|
|
||||||
}
|
}
|
||||||
return null;
|
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<FieldNode, JavaField> getFieldsMap() {
|
@Nullable
|
||||||
return fieldsMap;
|
private JavaField getJavaFieldByNode(FieldNode fld) {
|
||||||
}
|
|
||||||
|
|
||||||
JavaField getJavaFieldByNode(FieldNode fld) {
|
|
||||||
JavaField javaField = fieldsMap.get(fld);
|
JavaField javaField = fieldsMap.get(fld);
|
||||||
if (javaField != null) {
|
if (javaField != null && javaField.getFieldNode() == fld) {
|
||||||
return javaField;
|
return javaField;
|
||||||
}
|
}
|
||||||
// parent class not loaded yet
|
// parent class not loaded yet
|
||||||
JavaClass javaClass = classesMap.get(fld.getParentClass());
|
JavaClass javaClass = getJavaClassByNode(fld.getParentClass().getTopParentClass());
|
||||||
if (javaClass != null) {
|
if (javaClass == null) {
|
||||||
javaClass.decompile();
|
return null;
|
||||||
return fieldsMap.get(fld);
|
}
|
||||||
|
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
|
||||||
|
public JavaClass searchJavaClassByOrigFullName(String fullName) {
|
||||||
|
return getRoot().getClasses().stream()
|
||||||
|
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
|
||||||
|
.findFirst()
|
||||||
|
.map(this::getJavaClassByNode)
|
||||||
|
.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 getJavaClassByNode(node.getTopParentClass());
|
||||||
|
} else {
|
||||||
|
return getJavaClassByNode(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public JavaClass searchJavaClassByAliasFullName(String fullName) {
|
||||||
|
return getRoot().getClasses().stream()
|
||||||
|
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
|
||||||
|
.findFirst()
|
||||||
|
.map(this::getJavaClassByNode)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
JavaNode convertNode(Object obj) {
|
||||||
|
if (obj instanceof VarRef) {
|
||||||
|
VarRef varRef = (VarRef) obj;
|
||||||
|
MethodNode mthNode = varRef.getMth();
|
||||||
|
JavaMethod mth = getJavaMethodByNode(mthNode);
|
||||||
|
if (mth == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new JavaVariable(mth, varRef);
|
||||||
|
}
|
||||||
|
if (!(obj instanceof LineAttrNode)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LineAttrNode node = (LineAttrNode) obj;
|
||||||
|
if (node.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (obj instanceof ClassNode) {
|
||||||
|
return convertClassNode((ClassNode) obj);
|
||||||
|
}
|
||||||
|
if (obj instanceof MethodNode) {
|
||||||
|
return getJavaMethodByNode(((MethodNode) obj));
|
||||||
|
}
|
||||||
|
if (obj instanceof FieldNode) {
|
||||||
|
return getJavaFieldByNode((FieldNode) obj);
|
||||||
|
}
|
||||||
|
throw new JadxRuntimeException("Unexpected node type: " + obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make interface for all nodes in code annotations and add common method instead this
|
||||||
|
Object getInternalNode(JavaNode javaNode) {
|
||||||
|
if (javaNode instanceof JavaClass) {
|
||||||
|
return ((JavaClass) javaNode).getClassNode();
|
||||||
|
}
|
||||||
|
if (javaNode instanceof JavaMethod) {
|
||||||
|
return ((JavaMethod) javaNode).getMethodNode();
|
||||||
|
}
|
||||||
|
if (javaNode instanceof JavaField) {
|
||||||
|
return ((JavaField) javaNode).getFieldNode();
|
||||||
|
}
|
||||||
|
if (javaNode instanceof JavaVariable) {
|
||||||
|
return ((JavaVariable) javaNode).getVarRef();
|
||||||
|
}
|
||||||
|
throw new JadxRuntimeException("Unexpected node type: " + javaNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<JavaNode> convertNodes(Collection<?> nodesList) {
|
||||||
|
return nodesList.stream()
|
||||||
|
.map(this::convertNode)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
|
||||||
|
Map<CodePosition, Object> map = codeInfo.getAnnotations();
|
||||||
|
if (map.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object obj = map.get(new CodePosition(line, offset));
|
||||||
|
if (obj == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return convertNode(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public CodePosition getDefinitionPosition(JavaNode javaNode) {
|
||||||
|
JavaClass jCls = javaNode.getTopParentClass();
|
||||||
|
jCls.decompile();
|
||||||
|
int defLine = javaNode.getDecompiledLine();
|
||||||
|
if (defLine == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new CodePosition(defLine, 0, javaNode.getDefPos());
|
||||||
|
}
|
||||||
|
|
||||||
|
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,11 +7,10 @@ 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.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.core.codegen.CodeWriter;
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
|
||||||
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;
|
||||||
@@ -26,6 +25,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
private List<JavaClass> innerClasses = Collections.emptyList();
|
private List<JavaClass> innerClasses = 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;
|
||||||
|
|
||||||
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
|
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
|
||||||
this.decompiler = decompiler;
|
this.decompiler = decompiler;
|
||||||
@@ -43,56 +43,59 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getCode() {
|
public String getCode() {
|
||||||
CodeWriter code = cls.getCode();
|
ICodeInfo code = getCodeInfo();
|
||||||
if (code == null) {
|
if (code == null) {
|
||||||
decompile();
|
return "";
|
||||||
code = cls.getCode();
|
|
||||||
if (code == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return code.getCodeStr();
|
return code.getCodeStr();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void decompile() {
|
public ICodeInfo getCodeInfo() {
|
||||||
if (decompiler == null) {
|
return cls.decompile();
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
if (cls.getCode() == null) {
|
public void decompile() {
|
||||||
decompiler.processClass(cls);
|
cls.decompile();
|
||||||
load();
|
}
|
||||||
}
|
|
||||||
|
public synchronized void reload() {
|
||||||
|
listsLoaded = false;
|
||||||
|
cls.reloadCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unload() {
|
||||||
|
listsLoaded = false;
|
||||||
|
cls.unloadCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized String getSmali() {
|
public synchronized String getSmali() {
|
||||||
if (decompiler == null) {
|
return cls.getDisassembledCode();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (cls.getSmali() == null) {
|
|
||||||
decompiler.generateSmali(cls);
|
|
||||||
}
|
|
||||||
return cls.getSmali();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void unload() {
|
|
||||||
cls.unload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal API. Not Stable!
|
||||||
|
*/
|
||||||
|
@ApiStatus.Internal
|
||||||
public ClassNode getClassNode() {
|
public ClassNode getClassNode() {
|
||||||
return cls;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void load() {
|
private synchronized void loadLists() {
|
||||||
|
if (listsLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listsLoaded = true;
|
||||||
|
decompile();
|
||||||
JadxDecompiler rootDecompiler = getRootDecompiler();
|
JadxDecompiler rootDecompiler = getRootDecompiler();
|
||||||
|
|
||||||
int inClsCount = cls.getInnerClasses().size();
|
int inClsCount = cls.getInnerClasses().size();
|
||||||
if (inClsCount != 0) {
|
if (inClsCount != 0) {
|
||||||
List<JavaClass> list = new ArrayList<>(inClsCount);
|
List<JavaClass> list = new ArrayList<>(inClsCount);
|
||||||
for (ClassNode inner : cls.getInnerClasses()) {
|
for (ClassNode inner : cls.getInnerClasses()) {
|
||||||
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaClass javaClass = new JavaClass(inner, this);
|
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
||||||
javaClass.load();
|
javaClass.loadLists();
|
||||||
list.add(javaClass);
|
list.add(javaClass);
|
||||||
rootDecompiler.getClassesMap().put(inner, javaClass);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.innerClasses = Collections.unmodifiableList(list);
|
this.innerClasses = Collections.unmodifiableList(list);
|
||||||
@@ -105,7 +108,6 @@ public final class JavaClass implements JavaNode {
|
|||||||
if (!f.contains(AFlag.DONT_GENERATE)) {
|
if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaField javaField = new JavaField(f, this);
|
JavaField javaField = new JavaField(f, this);
|
||||||
flds.add(javaField);
|
flds.add(javaField);
|
||||||
rootDecompiler.getFieldsMap().put(f, javaField);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.fields = Collections.unmodifiableList(flds);
|
this.fields = Collections.unmodifiableList(flds);
|
||||||
@@ -118,7 +120,6 @@ public final class JavaClass implements JavaNode {
|
|||||||
if (!m.contains(AFlag.DONT_GENERATE)) {
|
if (!m.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaMethod javaMethod = new JavaMethod(this, m);
|
JavaMethod javaMethod = new JavaMethod(this, m);
|
||||||
mths.add(javaMethod);
|
mths.add(javaMethod);
|
||||||
rootDecompiler.getMethodsMap().put(m, javaMethod);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mths.sort(Comparator.comparing(JavaMethod::getName));
|
mths.sort(Comparator.comparing(JavaMethod::getName));
|
||||||
@@ -126,22 +127,25 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private JadxDecompiler getRootDecompiler() {
|
protected JadxDecompiler getRootDecompiler() {
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
return parent.getRootDecompiler();
|
return parent.getRootDecompiler();
|
||||||
}
|
}
|
||||||
return decompiler;
|
return decompiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<CodePosition, Object> getCodeAnnotations() {
|
public Map<CodePosition, Object> getCodeAnnotations() {
|
||||||
decompile();
|
ICodeInfo code = getCodeInfo();
|
||||||
CodeWriter code = cls.getCode();
|
|
||||||
if (code == null) {
|
if (code == null) {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
return code.getAnnotations();
|
return code.getAnnotations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Object getAnnotationAt(CodePosition pos) {
|
||||||
|
return getCodeAnnotations().get(pos);
|
||||||
|
}
|
||||||
|
|
||||||
public Map<CodePosition, JavaNode> getUsageMap() {
|
public Map<CodePosition, JavaNode> getUsageMap() {
|
||||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||||
if (map.isEmpty() || decompiler == null) {
|
if (map.isEmpty() || decompiler == null) {
|
||||||
@@ -151,60 +155,50 @@ public final class JavaClass implements JavaNode {
|
|||||||
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
||||||
CodePosition codePosition = entry.getKey();
|
CodePosition codePosition = entry.getKey();
|
||||||
Object obj = entry.getValue();
|
Object obj = entry.getValue();
|
||||||
if (obj instanceof LineAttrNode) {
|
JavaNode node = getRootDecompiler().convertNode(obj);
|
||||||
JavaNode node = convertNode(obj);
|
if (node != null) {
|
||||||
if (node != null) {
|
resultMap.put(codePosition, node);
|
||||||
resultMap.put(codePosition, node);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resultMap;
|
return resultMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
public List<CodePosition> getUsageFor(JavaNode javaNode) {
|
||||||
private JavaNode convertNode(Object obj) {
|
|
||||||
if (!(obj instanceof LineAttrNode)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (obj instanceof ClassNode) {
|
|
||||||
return getRootDecompiler().getClassesMap().get(obj);
|
|
||||||
}
|
|
||||||
if (obj instanceof MethodNode) {
|
|
||||||
return getRootDecompiler().getJavaMethodByNode(((MethodNode) obj));
|
|
||||||
}
|
|
||||||
if (obj instanceof FieldNode) {
|
|
||||||
return getRootDecompiler().getJavaFieldByNode((FieldNode) obj);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public JavaNode getJavaNodeAtPosition(int line, int offset) {
|
|
||||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||||
if (map.isEmpty()) {
|
if (map.isEmpty() || decompiler == null) {
|
||||||
return null;
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
Object obj = map.get(new CodePosition(line, offset));
|
Object internalNode = getRootDecompiler().getInternalNode(javaNode);
|
||||||
if (obj == null) {
|
List<CodePosition> result = new ArrayList<>();
|
||||||
return null;
|
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
||||||
|
CodePosition codePosition = entry.getKey();
|
||||||
|
Object obj = entry.getValue();
|
||||||
|
if (internalNode.equals(obj)) {
|
||||||
|
result.add(codePosition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return convertNode(obj);
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<JavaNode> getUseIn() {
|
||||||
|
return getRootDecompiler().convertNodes(cls.getUseIn());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public CodePosition getDefinitionPosition(JavaNode javaNode) {
|
@Deprecated
|
||||||
JavaClass jCls = javaNode.getTopParentClass();
|
public JavaNode getJavaNodeAtPosition(int line, int offset) {
|
||||||
jCls.decompile();
|
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
|
||||||
int defLine = javaNode.getDecompiledLine();
|
}
|
||||||
if (defLine == 0) {
|
|
||||||
return null;
|
@Nullable
|
||||||
}
|
@Deprecated
|
||||||
return new CodePosition(jCls, defLine, 0);
|
public CodePosition getDefinitionPosition() {
|
||||||
|
return getRootDecompiler().getDefinitionPosition(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getSourceLine(int decompiledLine) {
|
public Integer getSourceLine(int decompiledLine) {
|
||||||
decompile();
|
return getCodeInfo().getLineMapping().get(decompiledLine);
|
||||||
return cls.getCode().getLineMapping().get(decompiledLine);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -217,6 +211,10 @@ public final class JavaClass implements JavaNode {
|
|||||||
return cls.getFullName();
|
return cls.getFullName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRawName() {
|
||||||
|
return cls.getRawName();
|
||||||
|
}
|
||||||
|
|
||||||
public String getPackage() {
|
public String getPackage() {
|
||||||
return cls.getPackage();
|
return cls.getPackage();
|
||||||
}
|
}
|
||||||
@@ -228,32 +226,68 @@ public final class JavaClass implements JavaNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaClass getTopParentClass() {
|
public JavaClass getTopParentClass() {
|
||||||
|
if (cls.contains(AFlag.ANONYMOUS_CLASS)) {
|
||||||
|
// moved to usage class
|
||||||
|
return getParentForAnonymousClass();
|
||||||
|
}
|
||||||
return parent == null ? this : parent.getTopParentClass();
|
return parent == null ? this : parent.getTopParentClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private JavaClass getParentForAnonymousClass() {
|
||||||
|
List<JavaNode> useIn = getUseIn();
|
||||||
|
if (useIn.isEmpty()) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
JavaNode useNode = useIn.get(0);
|
||||||
|
if (useNode.equals(this)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return useNode.getTopParentClass();
|
||||||
|
}
|
||||||
|
|
||||||
public AccessInfo getAccessInfo() {
|
public AccessInfo getAccessInfo() {
|
||||||
return cls.getAccessFlags();
|
return cls.getAccessFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaClass> getInnerClasses() {
|
public List<JavaClass> getInnerClasses() {
|
||||||
decompile();
|
loadLists();
|
||||||
return innerClasses;
|
return innerClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaField> getFields() {
|
public List<JavaField> getFields() {
|
||||||
decompile();
|
loadLists();
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaMethod> getMethods() {
|
public List<JavaMethod> getMethods() {
|
||||||
decompile();
|
loadLists();
|
||||||
return methods;
|
return methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public JavaMethod searchMethodByShortId(String shortId) {
|
||||||
|
MethodNode methodNode = cls.searchMethodByShortId(shortId);
|
||||||
|
if (methodNode == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new JavaMethod(this, methodNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAlias() {
|
||||||
|
this.cls.getClassInfo().removeAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getDecompiledLine() {
|
public int getDecompiledLine() {
|
||||||
return cls.getDecompiledLine();
|
return cls.getDecompiledLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefPos() {
|
||||||
|
return cls.getDefPosition();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
|
return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
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;
|
||||||
@@ -39,13 +43,37 @@ public final class JavaField implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ArgType getType() {
|
public ArgType getType() {
|
||||||
return ArgType.tryToResolveClassAlias(field.dex(), field.getType());
|
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getDecompiledLine() {
|
public int getDecompiledLine() {
|
||||||
return field.getDecompiledLine();
|
return field.getDecompiledLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefPos() {
|
||||||
|
return field.getDefPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<JavaNode> getUseIn() {
|
||||||
|
return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAlias() {
|
||||||
|
this.field.getFieldInfo().removeAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal API. Not Stable!
|
||||||
|
*/
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public FieldNode getFieldNode() {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return field.hashCode();
|
return field.hashCode();
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ package jadx.api;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
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.instructions.args.RegisterArg;
|
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public final class JavaMethod implements JavaNode {
|
public final class JavaMethod implements JavaNode {
|
||||||
private final MethodNode mth;
|
private final MethodNode mth;
|
||||||
@@ -44,27 +47,34 @@ public final class JavaMethod implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<ArgType> getArguments() {
|
public List<ArgType> getArguments() {
|
||||||
if (mth.getMethodInfo().getArgumentsTypes().isEmpty()) {
|
List<ArgType> infoArgTypes = mth.getMethodInfo().getArgumentsTypes();
|
||||||
|
if (infoArgTypes.isEmpty()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
List<RegisterArg> arguments = mth.getArguments(false);
|
List<ArgType> arguments = mth.getArgTypes();
|
||||||
Stream<ArgType> argTypeStream;
|
return Utils.collectionMap(arguments,
|
||||||
if (arguments == null || arguments.isEmpty() || mth.isNoCode()) {
|
type -> ArgType.tryToResolveClassAlias(mth.root(), type));
|
||||||
argTypeStream = mth.getMethodInfo().getArgumentsTypes().stream();
|
|
||||||
} else {
|
|
||||||
argTypeStream = arguments.stream().map(RegisterArg::getType);
|
|
||||||
}
|
|
||||||
return argTypeStream
|
|
||||||
.map(type -> ArgType.tryToResolveClassAlias(mth.dex(), type))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArgType getReturnType() {
|
public ArgType getReturnType() {
|
||||||
ArgType retType = mth.getReturnType();
|
ArgType retType = mth.getReturnType();
|
||||||
if (retType == null) {
|
return ArgType.tryToResolveClassAlias(mth.root(), retType);
|
||||||
retType = mth.getMethodInfo().getReturnType();
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<JavaNode> getUseIn() {
|
||||||
|
return getDeclaringClass().getRootDecompiler().convertNodes(mth.getUseIn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JavaMethod> getOverrideRelatedMethods() {
|
||||||
|
MethodOverrideAttr ovrdAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||||
|
if (ovrdAttr == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
return ArgType.tryToResolveClassAlias(mth.dex(), retType);
|
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
||||||
|
return ovrdAttr.getRelatedMthNodes().stream()
|
||||||
|
.map(m -> ((JavaMethod) decompiler.convertNode(m)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isConstructor() {
|
public boolean isConstructor() {
|
||||||
@@ -75,10 +85,29 @@ public final class JavaMethod implements JavaNode {
|
|||||||
return mth.getMethodInfo().isClassInit();
|
return mth.getMethodInfo().isClassInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getDecompiledLine() {
|
public int getDecompiledLine() {
|
||||||
return mth.getDecompiledLine();
|
return mth.getDecompiledLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefPos() {
|
||||||
|
return mth.getDefPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAlias() {
|
||||||
|
this.mth.getMethodInfo().removeAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal API. Not Stable!
|
||||||
|
*/
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public MethodNode getMethodNode() {
|
||||||
|
return mth;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return mth.hashCode();
|
return mth.hashCode();
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface JavaNode {
|
public interface JavaNode {
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
@@ -11,4 +13,11 @@ public interface JavaNode {
|
|||||||
JavaClass getTopParentClass();
|
JavaClass getTopParentClass();
|
||||||
|
|
||||||
int getDecompiledLine();
|
int getDecompiledLine();
|
||||||
|
|
||||||
|
int getDefPos();
|
||||||
|
|
||||||
|
List<JavaNode> getUseIn();
|
||||||
|
|
||||||
|
default void removeAlias() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -43,6 +44,16 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefPos() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<JavaNode> getUseIn() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
@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,93 @@
|
|||||||
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
import jadx.api.data.annotations.VarDeclareRef;
|
||||||
|
import jadx.api.data.annotations.VarRef;
|
||||||
|
|
||||||
|
public class JavaVariable implements JavaNode {
|
||||||
|
private final JavaMethod mth;
|
||||||
|
private final VarRef varRef;
|
||||||
|
|
||||||
|
public JavaVariable(JavaMethod mth, VarRef varRef) {
|
||||||
|
this.mth = mth;
|
||||||
|
this.varRef = varRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JavaMethod getMth() {
|
||||||
|
return mth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReg() {
|
||||||
|
return varRef.getReg();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSsa() {
|
||||||
|
return varRef.getSsa();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return varRef.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public VarRef getVarRef() {
|
||||||
|
return varRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFullName() {
|
||||||
|
return varRef.getType() + " " + varRef.getName() + " (r" + varRef.getReg() + "v" + varRef.getSsa() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaClass getDeclaringClass() {
|
||||||
|
return mth.getDeclaringClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaClass getTopParentClass() {
|
||||||
|
return mth.getTopParentClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDecompiledLine() {
|
||||||
|
if (varRef instanceof VarDeclareRef) {
|
||||||
|
return ((VarDeclareRef) varRef).getDecompiledLine();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefPos() {
|
||||||
|
if (varRef instanceof VarDeclareRef) {
|
||||||
|
return ((VarDeclareRef) varRef).getDefPosition();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<JavaNode> getUseIn() {
|
||||||
|
return Collections.singletonList(mth);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return varRef.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof JavaVariable)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return varRef.equals(((JavaVariable) o).varRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,9 @@ package jadx.api;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
import jadx.core.utils.files.ZipSecurity;
|
import jadx.api.plugins.utils.ZipSecurity;
|
||||||
import jadx.core.xmlgen.ResContainer;
|
import jadx.core.xmlgen.ResContainer;
|
||||||
|
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||||
|
|
||||||
public class ResourceFile {
|
public class ResourceFile {
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ public class ResourceFile {
|
|||||||
private final String name;
|
private final String name;
|
||||||
private final ResourceType type;
|
private final ResourceType type;
|
||||||
private ZipRef zipRef;
|
private ZipRef zipRef;
|
||||||
|
private String deobfName;
|
||||||
|
|
||||||
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)) {
|
||||||
@@ -48,10 +50,14 @@ public class ResourceFile {
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getOriginalName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDeobfName() {
|
||||||
|
return deobfName != null ? deobfName : name;
|
||||||
|
}
|
||||||
|
|
||||||
public ResourceType getType() {
|
public ResourceType getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
@@ -64,6 +70,15 @@ public class ResourceFile {
|
|||||||
this.zipRef = zipRef;
|
this.zipRef = zipRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAlias(ResourceEntry ri) {
|
||||||
|
int index = name.lastIndexOf('.');
|
||||||
|
deobfName = String.format("res/%s%s/%s%s",
|
||||||
|
ri.getTypeName(),
|
||||||
|
ri.getConfig(),
|
||||||
|
ri.getKeyName(),
|
||||||
|
index == -1 ? "" : name.substring(index));
|
||||||
|
}
|
||||||
|
|
||||||
public ZipRef getZipRef() {
|
public ZipRef getZipRef() {
|
||||||
return zipRef;
|
return zipRef;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
import jadx.core.codegen.CodeWriter;
|
|
||||||
import jadx.core.xmlgen.ResContainer;
|
import jadx.core.xmlgen.ResContainer;
|
||||||
|
|
||||||
public class ResourceFileContent extends ResourceFile {
|
public class ResourceFileContent extends ResourceFile {
|
||||||
private final CodeWriter content;
|
private final ICodeInfo content;
|
||||||
|
|
||||||
public ResourceFileContent(String name, ResourceType type, CodeWriter content) {
|
public ResourceFileContent(String name, ResourceType type, ICodeInfo content) {
|
||||||
super(null, name, type);
|
super(null, name, type);
|
||||||
this.content = content;
|
this.content = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResContainer loadContent() {
|
public ResContainer loadContent() {
|
||||||
return ResContainer.textResource(getName(), content);
|
return ResContainer.textResource(getDeobfName(), content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public enum ResourceType {
|
public enum ResourceType {
|
||||||
CODE(".dex", ".jar", ".class"),
|
CODE(".dex", ".jar", ".class"),
|
||||||
MANIFEST("AndroidManifest.xml"),
|
|
||||||
XML(".xml"),
|
XML(".xml"),
|
||||||
ARSC(".arsc"),
|
ARSC(".arsc"),
|
||||||
FONT(".ttf"),
|
FONT(".ttf", ".otf"),
|
||||||
IMG(".png", ".gif", ".jpg"),
|
IMG(".png", ".gif", ".jpg"),
|
||||||
|
MEDIA(".mp3", ".wav"),
|
||||||
LIB(".so"),
|
LIB(".so"),
|
||||||
|
MANIFEST,
|
||||||
UNKNOWN;
|
UNKNOWN;
|
||||||
|
|
||||||
private final String[] exts;
|
private final String[] exts;
|
||||||
@@ -20,14 +27,34 @@ public enum ResourceType {
|
|||||||
return exts;
|
return exts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResourceType getFileType(String fileName) {
|
private static final Map<String, ResourceType> EXT_MAP = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
for (ResourceType type : ResourceType.values()) {
|
for (ResourceType type : ResourceType.values()) {
|
||||||
for (String ext : type.getExts()) {
|
for (String ext : type.getExts()) {
|
||||||
if (fileName.endsWith(ext)) {
|
ResourceType prev = EXT_MAP.put(ext, type);
|
||||||
return type;
|
if (prev != null) {
|
||||||
|
throw new JadxRuntimeException("Duplicate extension in ResourceType: " + ext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResourceType getFileType(String fileName) {
|
||||||
|
if (fileName.matches("[^/]+/resources.pb")) {
|
||||||
|
return ARSC;
|
||||||
|
}
|
||||||
|
int dot = fileName.lastIndexOf('.');
|
||||||
|
if (dot != -1) {
|
||||||
|
String ext = fileName.substring(dot).toLowerCase(Locale.ROOT);
|
||||||
|
ResourceType resType = EXT_MAP.get(ext);
|
||||||
|
if (resType != null) {
|
||||||
|
if (resType == XML && fileName.equals("AndroidManifest.xml")) {
|
||||||
|
return MANIFEST;
|
||||||
|
}
|
||||||
|
return resType;
|
||||||
|
}
|
||||||
|
}
|
||||||
return UNKNOWN;
|
return UNKNOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import java.io.FileInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
@@ -16,13 +15,15 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.ResourceFile.ZipRef;
|
import jadx.api.ResourceFile.ZipRef;
|
||||||
import jadx.core.codegen.CodeWriter;
|
import jadx.api.impl.SimpleCodeInfo;
|
||||||
|
import jadx.api.plugins.utils.ZipSecurity;
|
||||||
|
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.InputFile;
|
import jadx.core.utils.files.FileUtils;
|
||||||
import jadx.core.utils.files.ZipSecurity;
|
|
||||||
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;
|
||||||
@@ -38,10 +39,11 @@ public final class ResourcesLoader {
|
|||||||
this.jadxRef = jadxRef;
|
this.jadxRef = jadxRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ResourceFile> load(List<InputFile> inputFiles) {
|
List<ResourceFile> load() {
|
||||||
|
List<File> inputFiles = jadxRef.getArgs().getInputFiles();
|
||||||
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
|
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
|
||||||
for (InputFile file : inputFiles) {
|
for (File file : inputFiles) {
|
||||||
loadFile(list, file.getFile());
|
loadFile(list, file);
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
@@ -54,7 +56,7 @@ public final class ResourcesLoader {
|
|||||||
try {
|
try {
|
||||||
ZipRef zipRef = rf.getZipRef();
|
ZipRef zipRef = rf.getZipRef();
|
||||||
if (zipRef == null) {
|
if (zipRef == null) {
|
||||||
File file = new File(rf.getName());
|
File file = new File(rf.getOriginalName());
|
||||||
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
|
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
|
||||||
return decoder.decode(file.length(), inputStream);
|
return decoder.decode(file.length(), inputStream);
|
||||||
}
|
}
|
||||||
@@ -67,13 +69,13 @@ public final class ResourcesLoader {
|
|||||||
if (!ZipSecurity.isValidZipEntry(entry)) {
|
if (!ZipSecurity.isValidZipEntry(entry)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try (InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(entry))) {
|
try (InputStream inputStream = ZipSecurity.getInputStreamForEntry(zipFile, entry)) {
|
||||||
return decoder.decode(entry.getSize(), inputStream);
|
return decoder.decode(entry.getSize(), inputStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new JadxException("Error decode: " + rf.getName(), e);
|
throw new JadxException("Error decode: " + rf.getDeobfName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,23 +84,34 @@ 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.getName(), cw);
|
return ResContainer.textResource(rf.getDeobfName(), cw.finish());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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: {
|
||||||
CodeWriter content = jadxRef.getXmlParser().parse(inputStream);
|
ICodeInfo content;
|
||||||
return ResContainer.textResource(rf.getName(), content);
|
if (root.isProto()) {
|
||||||
|
content = jadxRef.getProtoXmlParser().parse(inputStream);
|
||||||
|
} else {
|
||||||
|
content = jadxRef.getBinaryXmlParser().parse(inputStream);
|
||||||
|
}
|
||||||
|
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);
|
||||||
@@ -109,13 +122,12 @@ public final class ResourcesLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
|
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
|
||||||
String name = rf.getName();
|
String name = rf.getOriginalName();
|
||||||
if (name.endsWith(".9.png")) {
|
if (name.endsWith(".9.png")) {
|
||||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
||||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||||
try {
|
|
||||||
decoder.decode(inputStream, os);
|
decoder.decode(inputStream, os);
|
||||||
return ResContainer.decodedData(rf.getName(), 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,19 +136,15 @@ 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;
|
||||||
}
|
}
|
||||||
try (ZipFile zip = new ZipFile(file)) {
|
if (FileUtils.isZipFile(file)) {
|
||||||
Enumeration<? extends ZipEntry> entries = zip.entries();
|
ZipSecurity.visitZipEntries(file, (zipFile, entry) -> {
|
||||||
while (entries.hasMoreElements()) {
|
addEntry(list, file, entry);
|
||||||
ZipEntry entry = entries.nextElement();
|
return null;
|
||||||
if (ZipSecurity.isValidZipEntry(entry)) {
|
});
|
||||||
addEntry(list, file, entry);
|
} else {
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
|
|
||||||
addResourceFile(list, file);
|
addResourceFile(list, file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,11 +171,9 @@ public final class ResourcesLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
|
public static ICodeInfo loadToCodeWriter(InputStream is) throws IOException {
|
||||||
CodeWriter cw = new CodeWriter();
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
||||||
copyStream(is, baos);
|
copyStream(is, baos);
|
||||||
cw.add(baos.toString("UTF-8"));
|
return new SimpleCodeInfo(baos.toString("UTF-8"));
|
||||||
return cw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,5 @@
|
|||||||
|
package jadx.api.data.annotations;
|
||||||
|
|
||||||
|
public interface ICodeRawOffset {
|
||||||
|
int getOffset();
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package jadx.api.data.annotations;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
|
||||||
|
public class InsnCodeOffset implements ICodeRawOffset {
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOffset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "offset=" + offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package jadx.api.data.annotations;
|
||||||
|
|
||||||
|
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||||
|
import jadx.core.dex.instructions.args.CodeVar;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
|
||||||
|
public class VarDeclareRef extends VarRef implements ILineAttributeNode {
|
||||||
|
|
||||||
|
public static VarDeclareRef get(MethodNode mth, CodeVar codeVar) {
|
||||||
|
VarDeclareRef ref = new VarDeclareRef(mth, codeVar);
|
||||||
|
codeVar.setCachedVarRef(ref);
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int sourceLine;
|
||||||
|
private int decompiledLine;
|
||||||
|
private int defPosition;
|
||||||
|
|
||||||
|
private VarDeclareRef(MethodNode mth, CodeVar codeVar) {
|
||||||
|
super(mth, codeVar.getAnySsaVar());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSourceLine() {
|
||||||
|
return sourceLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSourceLine(int sourceLine) {
|
||||||
|
this.sourceLine = sourceLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDecompiledLine() {
|
||||||
|
return decompiledLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDecompiledLine(int decompiledLine) {
|
||||||
|
this.decompiledLine = decompiledLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefPosition() {
|
||||||
|
return defPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDefPosition(int pos) {
|
||||||
|
this.defPosition = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "VarDeclareRef{r" + getReg() + 'v' + getSsa() + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package jadx.api.data.annotations;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.CodeVar;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
|
||||||
|
public class VarRef {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static VarRef get(MethodNode mth, RegisterArg reg) {
|
||||||
|
SSAVar ssaVar = reg.getSVar();
|
||||||
|
if (ssaVar == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
CodeVar codeVar = ssaVar.getCodeVar();
|
||||||
|
VarRef cachedVarRef = codeVar.getCachedVarRef();
|
||||||
|
if (cachedVarRef != null) {
|
||||||
|
if (cachedVarRef.getName() == null) {
|
||||||
|
cachedVarRef.setName(codeVar.getName());
|
||||||
|
}
|
||||||
|
return cachedVarRef;
|
||||||
|
}
|
||||||
|
VarRef newVarRef = new VarRef(mth, ssaVar);
|
||||||
|
codeVar.setCachedVarRef(newVarRef);
|
||||||
|
return newVarRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final MethodNode mth;
|
||||||
|
private final int reg;
|
||||||
|
private final int ssa;
|
||||||
|
private final ArgType type;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
protected VarRef(MethodNode mth, SSAVar ssaVar) {
|
||||||
|
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
|
||||||
|
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private VarRef(MethodNode mth, int reg, int ssa, ArgType type, String name) {
|
||||||
|
this.mth = mth;
|
||||||
|
this.reg = reg;
|
||||||
|
this.ssa = ssa;
|
||||||
|
this.type = type;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodNode getMth() {
|
||||||
|
return mth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReg() {
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSsa() {
|
||||||
|
return ssa;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof VarRef)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
VarRef other = (VarRef) o;
|
||||||
|
return getReg() == other.getReg()
|
||||||
|
&& getSsa() == other.getSsa()
|
||||||
|
&& getMth().equals(other.getMth());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return 31 * getReg() + getSsa();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "VarUseRef{r" + reg + 'v' + ssa + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.data.annotations.VarRef;
|
||||||
|
|
||||||
|
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(VarRef varRef) {
|
||||||
|
return forVar(varRef.getReg(), varRef.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,43 @@
|
|||||||
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import jadx.api.CodePosition;
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
|
||||||
|
public class AnnotatedCodeInfo implements ICodeInfo {
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final Map<Integer, Integer> lineMapping;
|
||||||
|
private final Map<CodePosition, Object> annotations;
|
||||||
|
|
||||||
|
public AnnotatedCodeInfo(ICodeInfo codeInfo) {
|
||||||
|
this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<CodePosition, Object> annotations) {
|
||||||
|
this.code = code;
|
||||||
|
this.lineMapping = lineMapping;
|
||||||
|
this.annotations = annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCodeStr() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Integer, Integer> getLineMapping() {
|
||||||
|
return lineMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<CodePosition, Object> getAnnotations() {
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import jadx.api.CodePosition;
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||||
|
import jadx.core.utils.StringUtils;
|
||||||
|
|
||||||
|
public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter {
|
||||||
|
|
||||||
|
private int line = 1;
|
||||||
|
private int offset;
|
||||||
|
private Map<CodePosition, Object> 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 instanceof AnnotatedCodeWriter))) {
|
||||||
|
buf.append(cw.getCodeStr());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
|
||||||
|
line--;
|
||||||
|
int startLine = line;
|
||||||
|
int startPos = getLength();
|
||||||
|
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
|
||||||
|
CodePosition codePos = entry.getKey();
|
||||||
|
int newLine = startLine + codePos.getLine();
|
||||||
|
int newPos = startPos + codePos.getPos();
|
||||||
|
attachAnnotation(entry.getValue(), new CodePosition(newLine, codePos.getOffset(), 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DefinitionWrapper {
|
||||||
|
private final ILineAttributeNode node;
|
||||||
|
|
||||||
|
private DefinitionWrapper(ILineAttributeNode node) {
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILineAttributeNode getNode() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachDefinition(ILineAttributeNode obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
attachAnnotation(obj);
|
||||||
|
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset, getLength()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachAnnotation(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
attachAnnotation(obj, new CodePosition(line, offset + 1, getLength()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachLineAnnotation(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
attachAnnotation(obj, new CodePosition(line, 0, getLength() - offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attachAnnotation(Object obj, CodePosition 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();
|
||||||
|
String code = buf.toString();
|
||||||
|
buf = null;
|
||||||
|
return new AnnotatedCodeInfo(code, lineMap, annotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<CodePosition, Object> getRawAnnotations() {
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processDefinitionAnnotations() {
|
||||||
|
if (!annotations.isEmpty()) {
|
||||||
|
annotations.entrySet().removeIf(entry -> {
|
||||||
|
Object v = entry.getValue();
|
||||||
|
if (v instanceof DefinitionWrapper) {
|
||||||
|
ILineAttributeNode l = ((DefinitionWrapper) v).getNode();
|
||||||
|
CodePosition codePos = entry.getKey();
|
||||||
|
l.setDecompiledLine(codePos.getLine());
|
||||||
|
l.setDefPosition(codePos.getPos());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.ICodeCache;
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
|
||||||
|
public class InMemoryCodeCache implements ICodeCache {
|
||||||
|
|
||||||
|
private final Map<String, ICodeInfo> storage = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||||
|
storage.put(clsFullName, codeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String clsFullName) {
|
||||||
|
storage.remove(clsFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ICodeInfo get(String clsFullName) {
|
||||||
|
return storage.get(clsFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "InMemoryCodeCache: size=" + storage.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.ICodeCache;
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
|
||||||
|
public class NoOpCodeCache implements ICodeCache {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String clsFullName) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ICodeInfo get(String clsFullName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "NoOpCodeCache";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import jadx.api.CodePosition;
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
|
||||||
|
public class SimpleCodeInfo implements ICodeInfo {
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
public SimpleCodeInfo(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCodeStr() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Integer, Integer> getLineMapping() {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<CodePosition, Object> getAnnotations() {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.CodePosition;
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.core.dex.attributes.ILineAttributeNode;
|
||||||
|
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 void attachDefinition(ILineAttributeNode obj) {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachAnnotation(Object obj) {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachLineAnnotation(Object 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<CodePosition, Object> getRawAnnotations() {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCodeStr() {
|
||||||
|
removeFirstEmptyLine();
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getCodeStr();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,20 +2,21 @@ package jadx.core;
|
|||||||
|
|
||||||
public class Consts {
|
public class Consts {
|
||||||
public static final boolean DEBUG = false;
|
public static final boolean DEBUG = false;
|
||||||
|
public static final boolean DEBUG_WITH_ERRORS = false; // TODO: fix errors
|
||||||
|
public static final boolean DEBUG_USAGE = false;
|
||||||
|
public static final boolean DEBUG_TYPE_INFERENCE = false;
|
||||||
|
public static final boolean DEBUG_OVERLOADED_CASTS = false;
|
||||||
|
public static final boolean DEBUG_EXC_HANDLERS = 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";
|
||||||
public static final String CLASS_CLASS = "java.lang.Class";
|
public static final String CLASS_CLASS = "java.lang.Class";
|
||||||
public static final String CLASS_THROWABLE = "java.lang.Throwable";
|
public static final String CLASS_THROWABLE = "java.lang.Throwable";
|
||||||
|
public static final String CLASS_EXCEPTION = "java.lang.Exception";
|
||||||
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 = "dalvik.annotation.";
|
|
||||||
public static final String DALVIK_SIGNATURE = "dalvik.annotation.Signature";
|
|
||||||
public static final String DALVIK_INNER_CLASS = "dalvik.annotation.InnerClass";
|
|
||||||
public static final String DALVIK_THROWS = "dalvik.annotation.Throws";
|
|
||||||
public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.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,32 +10,42 @@ 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.visitors.AttachCommentsVisitor;
|
||||||
|
import jadx.core.dex.visitors.AttachMethodDetails;
|
||||||
|
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;
|
||||||
import jadx.core.dex.visitors.DependencyCollector;
|
import jadx.core.dex.visitors.DeboxingVisitor;
|
||||||
import jadx.core.dex.visitors.DotGraphVisitor;
|
import jadx.core.dex.visitors.DotGraphVisitor;
|
||||||
import jadx.core.dex.visitors.EnumVisitor;
|
import jadx.core.dex.visitors.EnumVisitor;
|
||||||
import jadx.core.dex.visitors.ExtractFieldInit;
|
import jadx.core.dex.visitors.ExtractFieldInit;
|
||||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||||
import jadx.core.dex.visitors.FixAccessModifiers;
|
import jadx.core.dex.visitors.FixAccessModifiers;
|
||||||
|
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.MarkFinallyVisitor;
|
import jadx.core.dex.visitors.InlineMethods;
|
||||||
import jadx.core.dex.visitors.MethodInlineVisitor;
|
import jadx.core.dex.visitors.MarkMethodsForInline;
|
||||||
|
import jadx.core.dex.visitors.MethodInvokeVisitor;
|
||||||
import jadx.core.dex.visitors.ModVisitor;
|
import jadx.core.dex.visitors.ModVisitor;
|
||||||
|
import jadx.core.dex.visitors.MoveInlineVisitor;
|
||||||
|
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.ReSugarCode;
|
import jadx.core.dex.visitors.ReSugarCode;
|
||||||
import jadx.core.dex.visitors.RenameVisitor;
|
import jadx.core.dex.visitors.ShadowFieldVisitor;
|
||||||
|
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.DebugInfoParseVisitor;
|
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
|
||||||
|
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
|
||||||
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;
|
||||||
@@ -43,9 +53,12 @@ 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.TypeInferenceVisitor;
|
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||||
|
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||||
|
|
||||||
public class Jadx {
|
public class Jadx {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||||
@@ -59,66 +72,100 @@ public class Jadx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
public static List<IDexTreeVisitor> getFallbackPassesList() {
|
||||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
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 List<IDexTreeVisitor> getPreDecompilePassesList() {
|
||||||
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
|
passes.add(new SignatureProcessor());
|
||||||
|
passes.add(new OverrideMethodVisitor());
|
||||||
|
passes.add(new RenameVisitor());
|
||||||
|
passes.add(new UsageInfoVisitor());
|
||||||
|
passes.add(new ProcessAnonymous());
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
||||||
if (args.isFallbackMode()) {
|
if (args.isFallbackMode()) {
|
||||||
passes.add(new FallbackModeVisitor());
|
return getFallbackPassesList();
|
||||||
} else {
|
}
|
||||||
if (args.isDebugInfo()) {
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
passes.add(new DebugInfoParseVisitor());
|
|
||||||
}
|
|
||||||
|
|
||||||
passes.add(new BlockSplitter());
|
// instructions IR
|
||||||
if (args.isRawCFGOutput()) {
|
passes.add(new CheckCode());
|
||||||
passes.add(DotGraphVisitor.dumpRaw());
|
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 BlockProcessor());
|
// blocks IR
|
||||||
passes.add(new BlockExceptionHandler());
|
passes.add(new BlockSplitter());
|
||||||
passes.add(new BlockFinish());
|
passes.add(new BlockProcessor());
|
||||||
|
if (args.isRawCFGOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dumpRaw());
|
||||||
|
}
|
||||||
|
|
||||||
passes.add(new SSATransform());
|
passes.add(new SSATransform());
|
||||||
passes.add(new ConstructorVisitor());
|
passes.add(new MoveInlineVisitor());
|
||||||
passes.add(new InitCodeVariables());
|
passes.add(new ConstructorVisitor());
|
||||||
|
passes.add(new InitCodeVariables());
|
||||||
|
if (args.isExtractFinally()) {
|
||||||
passes.add(new MarkFinallyVisitor());
|
passes.add(new MarkFinallyVisitor());
|
||||||
passes.add(new ConstInlineVisitor());
|
}
|
||||||
passes.add(new TypeInferenceVisitor());
|
passes.add(new ConstInlineVisitor());
|
||||||
if (args.isDebugInfo()) {
|
passes.add(new TypeInferenceVisitor());
|
||||||
passes.add(new DebugInfoApplyVisitor());
|
if (args.isDebugInfo()) {
|
||||||
}
|
passes.add(new DebugInfoApplyVisitor());
|
||||||
|
}
|
||||||
|
passes.add(new CodeRenameVisitor());
|
||||||
|
if (args.isInlineMethods()) {
|
||||||
|
passes.add(new InlineMethods());
|
||||||
|
}
|
||||||
|
passes.add(new GenericTypesVisitor());
|
||||||
|
passes.add(new ShadowFieldVisitor());
|
||||||
|
passes.add(new DeboxingVisitor());
|
||||||
|
passes.add(new ModVisitor());
|
||||||
|
passes.add(new CodeShrinkVisitor());
|
||||||
|
passes.add(new ReSugarCode());
|
||||||
|
if (args.isCfgOutput()) {
|
||||||
|
passes.add(DotGraphVisitor.dump());
|
||||||
|
}
|
||||||
|
|
||||||
passes.add(new ModVisitor());
|
// regions IR
|
||||||
passes.add(new CodeShrinkVisitor());
|
passes.add(new RegionMakerVisitor());
|
||||||
passes.add(new ReSugarCode());
|
passes.add(new IfRegionVisitor());
|
||||||
if (args.isCfgOutput()) {
|
passes.add(new ReturnVisitor());
|
||||||
passes.add(DotGraphVisitor.dump());
|
passes.add(new CleanRegions());
|
||||||
}
|
|
||||||
|
|
||||||
passes.add(new RegionMakerVisitor());
|
passes.add(new CodeShrinkVisitor());
|
||||||
passes.add(new IfRegionVisitor());
|
passes.add(new MethodInvokeVisitor());
|
||||||
passes.add(new ReturnVisitor());
|
passes.add(new SimplifyVisitor());
|
||||||
passes.add(new CleanRegions());
|
passes.add(new CheckRegions());
|
||||||
|
|
||||||
passes.add(new CodeShrinkVisitor());
|
passes.add(new EnumVisitor());
|
||||||
passes.add(new SimplifyVisitor());
|
passes.add(new ExtractFieldInit());
|
||||||
passes.add(new CheckRegions());
|
passes.add(new FixAccessModifiers());
|
||||||
|
passes.add(new ClassModifier());
|
||||||
|
passes.add(new LoopRegionVisitor());
|
||||||
|
|
||||||
passes.add(new ExtractFieldInit());
|
if (args.isInlineMethods()) {
|
||||||
passes.add(new FixAccessModifiers());
|
passes.add(new MarkMethodsForInline());
|
||||||
passes.add(new ProcessAnonymous());
|
}
|
||||||
passes.add(new ClassModifier());
|
passes.add(new ProcessVariables());
|
||||||
passes.add(new MethodInlineVisitor());
|
passes.add(new PrepareForCodeGen());
|
||||||
passes.add(new EnumVisitor());
|
if (args.isCfgOutput()) {
|
||||||
passes.add(new LoopRegionVisitor());
|
passes.add(DotGraphVisitor.dumpRegions());
|
||||||
|
|
||||||
passes.add(new ProcessVariables());
|
|
||||||
passes.add(new PrepareForCodeGen());
|
|
||||||
if (args.isCfgOutput()) {
|
|
||||||
passes.add(DotGraphVisitor.dumpRegions());
|
|
||||||
}
|
|
||||||
|
|
||||||
passes.add(new DependencyCollector());
|
|
||||||
passes.add(new RenameVisitor());
|
|
||||||
}
|
}
|
||||||
return passes;
|
return passes;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,123 @@
|
|||||||
package jadx.core;
|
package jadx.core;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.core.codegen.CodeGen;
|
import jadx.core.codegen.CodeGen;
|
||||||
|
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.visitors.DepthTraversal;
|
import jadx.core.dex.visitors.DepthTraversal;
|
||||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
import jadx.core.utils.ErrorsCounter;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
|
||||||
|
import static jadx.core.dex.nodes.ProcessState.LOADED;
|
||||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||||
import static jadx.core.dex.nodes.ProcessState.PROCESSED;
|
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
||||||
import static jadx.core.dex.nodes.ProcessState.STARTED;
|
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
|
||||||
|
|
||||||
public final class ProcessClass {
|
public final class ProcessClass {
|
||||||
|
|
||||||
private ProcessClass() {
|
private ProcessClass() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, boolean generateCode) {
|
@Nullable
|
||||||
if (!generateCode && cls.getState() == PROCESSED) {
|
private static ICodeInfo process(ClassNode cls, boolean codegen) {
|
||||||
return;
|
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
|
||||||
|
// nothing to do
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
synchronized (getSyncObj(cls)) {
|
synchronized (cls.getClassInfo()) {
|
||||||
try {
|
try {
|
||||||
|
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
|
||||||
|
cls.remove(AFlag.CLASS_DEEP_RELOAD);
|
||||||
|
cls.deepUnload();
|
||||||
|
cls.root().runPreDecompileStageForClass(cls);
|
||||||
|
}
|
||||||
|
if (cls.contains(AFlag.CLASS_UNLOADED)) {
|
||||||
|
cls.remove(AFlag.CLASS_UNLOADED);
|
||||||
|
cls.root().runPreDecompileStageForClass(cls);
|
||||||
|
}
|
||||||
|
if (codegen) {
|
||||||
|
if (cls.getState() == GENERATED_AND_UNLOADED) {
|
||||||
|
// allow to run code generation again
|
||||||
|
cls.setState(NOT_LOADED);
|
||||||
|
}
|
||||||
|
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
|
||||||
|
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
|
||||||
|
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
|
||||||
|
cls.unload();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cls.setLoadStage(LoadStage.PROCESS_STAGE);
|
||||||
|
}
|
||||||
if (cls.getState() == NOT_LOADED) {
|
if (cls.getState() == NOT_LOADED) {
|
||||||
cls.load();
|
cls.load();
|
||||||
cls.setState(STARTED);
|
}
|
||||||
for (IDexTreeVisitor visitor : passes) {
|
if (cls.getState() == LOADED) {
|
||||||
|
cls.setState(PROCESS_STARTED);
|
||||||
|
for (IDexTreeVisitor visitor : cls.root().getPasses()) {
|
||||||
DepthTraversal.visit(visitor, cls);
|
DepthTraversal.visit(visitor, cls);
|
||||||
}
|
}
|
||||||
cls.setState(PROCESSED);
|
cls.setState(PROCESS_COMPLETE);
|
||||||
}
|
}
|
||||||
if (cls.getState() == PROCESSED && generateCode) {
|
if (codegen) {
|
||||||
processDependencies(cls, passes);
|
ICodeInfo code = CodeGen.generate(cls);
|
||||||
CodeGen.generate(cls);
|
if (!cls.contains(AFlag.DONT_UNLOAD_CLASS)) {
|
||||||
|
cls.unload();
|
||||||
|
cls.setState(GENERATED_AND_UNLOADED);
|
||||||
|
}
|
||||||
|
return code;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
return null;
|
||||||
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
|
} catch (Throwable e) {
|
||||||
|
if (codegen) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
cls.addError("Class process error: " + e.getClass().getSimpleName(), e);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object getSyncObj(ClassNode cls) {
|
@NotNull
|
||||||
return cls.getClassInfo();
|
public static ICodeInfo generateCode(ClassNode cls) {
|
||||||
}
|
ClassNode topParentClass = cls.getTopParentClass();
|
||||||
|
if (topParentClass != cls) {
|
||||||
private static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
|
return generateCode(topParentClass);
|
||||||
cls.getDependencies().forEach(depCls -> process(depCls, passes, false));
|
}
|
||||||
|
try {
|
||||||
|
Set<ClassNode> useIn = new HashSet<>(cls.getUseIn());
|
||||||
|
List<ClassNode> usedInDeps = new ArrayList<>();
|
||||||
|
for (ClassNode depCls : cls.getDependencies()) {
|
||||||
|
if (useIn.contains(depCls)) {
|
||||||
|
// postpone to resolve cross dependencies
|
||||||
|
usedInDeps.add(depCls);
|
||||||
|
} else {
|
||||||
|
process(depCls, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!usedInDeps.isEmpty()) {
|
||||||
|
// process current class before its usage
|
||||||
|
process(cls, false);
|
||||||
|
for (ClassNode depCls : usedInDeps) {
|
||||||
|
process(depCls, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ICodeInfo code = process(cls, true);
|
||||||
|
if (code == null) {
|
||||||
|
throw new JadxRuntimeException("Codegen failed");
|
||||||
|
}
|
||||||
|
return code;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jadx.core.clsp;
|
package jadx.core.clsp;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
@@ -12,25 +13,31 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.plugins.utils.ZipSecurity;
|
||||||
|
import jadx.core.dex.info.AccessInfo;
|
||||||
|
import jadx.core.dex.info.ClassInfo;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
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.exceptions.DecodeException;
|
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 jadx.core.utils.files.ZipSecurity;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Classes list for import into classpath graph
|
* Classes list for import into classpath graph
|
||||||
@@ -40,126 +47,130 @@ 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 = 2;
|
private static final int VERSION = 3;
|
||||||
|
|
||||||
private static final String STRING_CHARSET = "US-ASCII";
|
private static final String STRING_CHARSET = "US-ASCII";
|
||||||
|
|
||||||
private static final NClass[] EMPTY_NCLASS_ARRAY = new NClass[0];
|
private static final ArgType[] EMPTY_ARGTYPE_ARRAY = new ArgType[0];
|
||||||
|
|
||||||
|
private final RootNode root;
|
||||||
|
|
||||||
|
public ClsSet(RootNode root) {
|
||||||
|
this.root = root;
|
||||||
|
}
|
||||||
|
|
||||||
private enum TypeEnum {
|
private enum TypeEnum {
|
||||||
WILDCARD, GENERIC, GENERIC_TYPE, OBJECT, ARRAY, PRIMITIVE
|
WILDCARD,
|
||||||
|
GENERIC,
|
||||||
|
GENERIC_TYPE_VARIABLE,
|
||||||
|
OUTER_GENERIC,
|
||||||
|
OBJECT,
|
||||||
|
ARRAY,
|
||||||
|
PRIMITIVE
|
||||||
}
|
}
|
||||||
|
|
||||||
private NClass[] classes;
|
private ClspClass[] classes;
|
||||||
|
|
||||||
public void load(RootNode root) {
|
public void loadFromClstFile() throws IOException, DecodeException {
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
try (InputStream input = ClsSet.class.getResourceAsStream(CLST_PATH)) {
|
||||||
|
if (input == null) {
|
||||||
|
throw new JadxRuntimeException("Can't load classpath file: " + CLST_PATH);
|
||||||
|
}
|
||||||
|
load(input);
|
||||||
|
}
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
long time = System.currentTimeMillis() - startTime;
|
||||||
|
int methodsCount = Stream.of(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
|
||||||
|
LOG.debug("Clst file loaded in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadFrom(RootNode root) {
|
||||||
List<ClassNode> list = root.getClasses(true);
|
List<ClassNode> list = root.getClasses(true);
|
||||||
Map<String, NClass> names = new HashMap<>(list.size());
|
Map<String, ClspClass> names = new HashMap<>(list.size());
|
||||||
int k = 0;
|
int k = 0;
|
||||||
for (ClassNode cls : list) {
|
for (ClassNode cls : list) {
|
||||||
String clsRawName = cls.getRawName();
|
ArgType clsType = cls.getClassInfo().getType();
|
||||||
if (cls.getAccessFlags().isPublic()) {
|
String clsRawName = clsType.getObject();
|
||||||
cls.load();
|
cls.load();
|
||||||
NClass nClass = new NClass(clsRawName, k);
|
ClspClass nClass = new ClspClass(clsType, k);
|
||||||
if (names.put(clsRawName, nClass) != null) {
|
if (names.put(clsRawName, nClass) != null) {
|
||||||
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||||
}
|
|
||||||
k++;
|
|
||||||
nClass.setMethods(loadMethods(cls, nClass));
|
|
||||||
} else {
|
|
||||||
names.put(clsRawName, null);
|
|
||||||
}
|
}
|
||||||
|
k++;
|
||||||
|
nClass.setTypeParameters(cls.getGenericTypeParameters());
|
||||||
|
nClass.setMethods(getMethodsDetails(cls));
|
||||||
}
|
}
|
||||||
classes = new NClass[k];
|
classes = new ClspClass[k];
|
||||||
k = 0;
|
k = 0;
|
||||||
for (ClassNode cls : list) {
|
for (ClassNode cls : list) {
|
||||||
if (cls.getAccessFlags().isPublic()) {
|
ClspClass nClass = getCls(cls, names);
|
||||||
NClass nClass = getCls(cls.getRawName(), names);
|
if (nClass == null) {
|
||||||
if (nClass == null) {
|
throw new JadxRuntimeException("Missing class: " + cls);
|
||||||
throw new JadxRuntimeException("Missing class: " + cls);
|
|
||||||
}
|
|
||||||
nClass.setParents(makeParentsArray(cls, names));
|
|
||||||
classes[k] = nClass;
|
|
||||||
k++;
|
|
||||||
}
|
}
|
||||||
|
nClass.setParents(makeParentsArray(cls));
|
||||||
|
classes[k] = nClass;
|
||||||
|
k++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private NMethod[] loadMethods(ClassNode cls, NClass nClass) {
|
private List<ClspMethod> getMethodsDetails(ClassNode cls) {
|
||||||
List<NMethod> methods = new ArrayList<>();
|
List<MethodNode> methodsList = cls.getMethods();
|
||||||
for (MethodNode m : cls.getMethods()) {
|
List<ClspMethod> methods = new ArrayList<>(methodsList.size());
|
||||||
if (!m.getAccessFlags().isPublic()
|
for (MethodNode mth : methodsList) {
|
||||||
&& !m.getAccessFlags().isProtected()) {
|
processMethodDetails(mth, methods);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ArgType> args = new ArrayList<>();
|
|
||||||
|
|
||||||
boolean genericArg = false;
|
|
||||||
for (RegisterArg r : m.getArguments(false)) {
|
|
||||||
ArgType argType = r.getType();
|
|
||||||
if (argType.isGeneric() || argType.isGenericType()) {
|
|
||||||
args.add(argType);
|
|
||||||
genericArg = true;
|
|
||||||
} else {
|
|
||||||
args.add(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgType retType = m.getReturnType();
|
|
||||||
if (!retType.isGeneric() && !retType.isGenericType()) {
|
|
||||||
retType = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean varArgs = m.getAccessFlags().isVarArgs();
|
|
||||||
|
|
||||||
if (genericArg || retType != null || varArgs) {
|
|
||||||
methods.add(new NMethod(
|
|
||||||
m.getMethodInfo().getShortId(),
|
|
||||||
args.isEmpty()
|
|
||||||
? new ArgType[0]
|
|
||||||
: args.toArray(new ArgType[args.size()]),
|
|
||||||
retType,
|
|
||||||
varArgs));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return methods.toArray(new NMethod[methods.size()]);
|
return methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
|
private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) {
|
||||||
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
|
AccessInfo accessFlags = mth.getAccessFlags();
|
||||||
|
if (accessFlags.isPrivate() || accessFlags.isSynthetic() || accessFlags.isBridge()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(), mth.getArgTypes(),
|
||||||
|
mth.getReturnType(), mth.getTypeParameters(),
|
||||||
|
mth.getThrows(), accessFlags.rawValue());
|
||||||
|
methods.add(clspMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArgType[] makeParentsArray(ClassNode cls) {
|
||||||
ArgType superClass = cls.getSuperClass();
|
ArgType superClass = cls.getSuperClass();
|
||||||
if (superClass != null) {
|
if (superClass == null) {
|
||||||
NClass c = getCls(superClass.getObject(), names);
|
// cls is java.lang.Object
|
||||||
if (c != null) {
|
return EMPTY_ARGTYPE_ARRAY;
|
||||||
parents.add(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
ArgType[] parents = new ArgType[1 + cls.getInterfaces().size()];
|
||||||
|
parents[0] = superClass;
|
||||||
|
int k = 1;
|
||||||
for (ArgType iface : cls.getInterfaces()) {
|
for (ArgType iface : cls.getInterfaces()) {
|
||||||
NClass c = getCls(iface.getObject(), names);
|
parents[k] = iface;
|
||||||
if (c != null) {
|
k++;
|
||||||
parents.add(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
int size = parents.size();
|
return parents;
|
||||||
if (size == 0) {
|
|
||||||
return EMPTY_NCLASS_ARRAY;
|
|
||||||
}
|
|
||||||
return parents.toArray(new NClass[size]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static NClass getCls(String fullName, Map<String, NClass> names) {
|
private static ClspClass getCls(ClassNode cls, Map<String, ClspClass> names) {
|
||||||
NClass cls = names.get(fullName);
|
return getCls(cls.getRawName(), names);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClspClass getCls(ArgType clsType, Map<String, ClspClass> names) {
|
||||||
|
return getCls(clsType.getObject(), names);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClspClass getCls(String fullName, Map<String, ClspClass> names) {
|
||||||
|
ClspClass cls = names.get(fullName);
|
||||||
if (cls == null) {
|
if (cls == null) {
|
||||||
LOG.debug("Class not found: {}", fullName);
|
LOG.debug("Class not found: {}", fullName);
|
||||||
}
|
}
|
||||||
return cls;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
void save(Path path) throws IOException {
|
public void save(Path path) throws IOException {
|
||||||
FileUtils.makeDirsForFile(path);
|
FileUtils.makeDirsForFile(path);
|
||||||
String outputName = path.getFileName().toString();
|
String outputName = path.getFileName().toString();
|
||||||
if (outputName.endsWith(CLST_EXTENSION)) {
|
if (outputName.endsWith(CLST_EXTENSION)) {
|
||||||
@@ -172,149 +183,155 @@ 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;
|
||||||
out.putNextEntry(new ZipEntry(clst));
|
boolean clstReplaced = false;
|
||||||
save(out);
|
|
||||||
ZipEntry entry = in.getNextEntry();
|
ZipEntry entry = in.getNextEntry();
|
||||||
while (entry != null) {
|
while (entry != null) {
|
||||||
if (!entry.getName().equals(clst)) {
|
String entryName = entry.getName();
|
||||||
out.putNextEntry(new ZipEntry(entry.getName()));
|
ZipEntry copyEntry = new ZipEntry(entryName);
|
||||||
|
copyEntry.setLastModifiedTime(entry.getLastModifiedTime()); // preserve modified time
|
||||||
|
out.putNextEntry(copyEntry);
|
||||||
|
if (entryName.equals(clst)) {
|
||||||
|
save(out);
|
||||||
|
clstReplaced = true;
|
||||||
|
} else {
|
||||||
FileUtils.copyStream(in, out);
|
FileUtils.copyStream(in, out);
|
||||||
}
|
}
|
||||||
entry = in.getNextEntry();
|
entry = in.getNextEntry();
|
||||||
}
|
}
|
||||||
|
if (!clstReplaced) {
|
||||||
|
out.putNextEntry(new ZipEntry(clst));
|
||||||
|
save(out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save(OutputStream output) throws IOException {
|
private void save(OutputStream output) throws IOException {
|
||||||
DataOutputStream out = new DataOutputStream(output);
|
DataOutputStream out = new DataOutputStream(output);
|
||||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||||
out.writeByte(VERSION);
|
out.writeByte(VERSION);
|
||||||
|
|
||||||
LOG.info("Classes count: {}", classes.length);
|
Map<String, ClspClass> names = new HashMap<>(classes.length);
|
||||||
Map<String, NClass> names = new HashMap<>(classes.length);
|
|
||||||
out.writeInt(classes.length);
|
out.writeInt(classes.length);
|
||||||
for (NClass cls : classes) {
|
for (ClspClass cls : classes) {
|
||||||
writeString(out, cls.getName());
|
String clsName = cls.getName();
|
||||||
names.put(cls.getName(), cls);
|
writeString(out, clsName);
|
||||||
|
names.put(clsName, cls);
|
||||||
}
|
}
|
||||||
for (NClass cls : classes) {
|
for (ClspClass cls : classes) {
|
||||||
NClass[] parents = cls.getParents();
|
writeArgTypesArray(out, cls.getParents(), names);
|
||||||
out.writeByte(parents.length);
|
writeArgTypesList(out, cls.getTypeParameters(), names);
|
||||||
for (NClass parent : parents) {
|
List<ClspMethod> methods = cls.getSortedMethodsList();
|
||||||
out.writeInt(parent.getId());
|
out.writeShort(methods.size());
|
||||||
}
|
for (ClspMethod method : methods) {
|
||||||
NMethod[] methods = cls.getMethods();
|
|
||||||
out.writeByte(methods.length);
|
|
||||||
for (NMethod method : methods) {
|
|
||||||
writeMethod(out, method, names);
|
writeMethod(out, method, names);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
int methodsCount = Stream.of(classes).mapToInt(c -> c.getMethodsMap().size()).sum();
|
||||||
|
LOG.info("Classes: {}, methods: {}, file size: {} bytes", classes.length, methodsCount, out.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void writeMethod(DataOutputStream out, NMethod method, Map<String, NClass> names) throws IOException {
|
private static void writeMethod(DataOutputStream out, ClspMethod method, Map<String, ClspClass> names) throws IOException {
|
||||||
int argCount = 0;
|
MethodInfo methodInfo = method.getMethodInfo();
|
||||||
ArgType[] argTypes = method.getArgType();
|
writeString(out, methodInfo.getName());
|
||||||
for (ArgType arg : argTypes) {
|
writeArgTypesList(out, methodInfo.getArgumentsTypes(), names);
|
||||||
if (arg != null) {
|
writeArgType(out, methodInfo.getReturnType(), names);
|
||||||
argCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLongString(out, method.getShortId());
|
writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names);
|
||||||
out.writeByte(argCount);
|
writeArgType(out, method.getReturnType(), names);
|
||||||
|
writeArgTypesList(out, method.getTypeParameters(), names);
|
||||||
// last argument first
|
out.writeInt(method.getRawAccessFlags());
|
||||||
for (int i = argTypes.length - 1; i >= 0; i--) {
|
writeArgTypesList(out, method.getThrows(), names);
|
||||||
ArgType argType = argTypes[i];
|
|
||||||
if (argType != null) {
|
|
||||||
out.writeByte(i);
|
|
||||||
writeArgType(out, argType, names);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (method.getReturnType() != null) {
|
|
||||||
out.writeBoolean(true);
|
|
||||||
writeArgType(out, method.getReturnType(), names);
|
|
||||||
} else {
|
|
||||||
out.writeBoolean(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
out.writeBoolean(method.isVarArgs());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, NClass> names) throws IOException {
|
private static void writeArgTypesList(DataOutputStream out, List<ArgType> list, Map<String, ClspClass> names) throws IOException {
|
||||||
if (argType.getWildcardType() != null) {
|
int size = list.size();
|
||||||
|
writeUnsignedByte(out, size);
|
||||||
|
if (size != 0) {
|
||||||
|
for (ArgType type : list) {
|
||||||
|
writeArgType(out, type, names);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeArgTypesArray(DataOutputStream out, @Nullable ArgType[] arr, Map<String, ClspClass> names) throws IOException {
|
||||||
|
if (arr == null) {
|
||||||
|
out.writeByte(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int size = arr.length;
|
||||||
|
out.writeByte(size);
|
||||||
|
if (size != 0) {
|
||||||
|
for (ArgType type : arr) {
|
||||||
|
writeArgType(out, type, names);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, ClspClass> names) throws IOException {
|
||||||
|
if (argType == null) {
|
||||||
|
out.writeByte(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (argType.isPrimitive()) {
|
||||||
|
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
|
||||||
|
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
|
||||||
|
} else if (argType.getOuterType() != null) {
|
||||||
|
out.writeByte(TypeEnum.OUTER_GENERIC.ordinal());
|
||||||
|
writeArgType(out, argType.getOuterType(), names);
|
||||||
|
writeArgType(out, argType.getInnerType(), names);
|
||||||
|
} else if (argType.getWildcardType() != null) {
|
||||||
out.writeByte(TypeEnum.WILDCARD.ordinal());
|
out.writeByte(TypeEnum.WILDCARD.ordinal());
|
||||||
int bounds = argType.getWildcardBounds();
|
ArgType.WildcardBound bound = argType.getWildcardBound();
|
||||||
out.writeByte(bounds);
|
out.writeByte(bound.getNum());
|
||||||
if (bounds != 0) {
|
if (bound != ArgType.WildcardBound.UNBOUND) {
|
||||||
writeArgType(out, argType.getWildcardType(), names);
|
writeArgType(out, argType.getWildcardType(), names);
|
||||||
}
|
}
|
||||||
} else if (argType.isGeneric()) {
|
} else if (argType.isGeneric()) {
|
||||||
out.writeByte(TypeEnum.GENERIC.ordinal());
|
out.writeByte(TypeEnum.GENERIC.ordinal());
|
||||||
out.writeInt(names.get(argType.getObject()).getId());
|
out.writeInt(getCls(argType, names).getId());
|
||||||
ArgType[] types = argType.getGenericTypes();
|
writeArgTypesList(out, argType.getGenericTypes(), names);
|
||||||
if (types == null) {
|
|
||||||
out.writeByte(0);
|
|
||||||
} else {
|
|
||||||
out.writeByte(types.length);
|
|
||||||
for (ArgType type : types) {
|
|
||||||
writeArgType(out, type, names);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (argType.isGenericType()) {
|
} else if (argType.isGenericType()) {
|
||||||
out.writeByte(TypeEnum.GENERIC_TYPE.ordinal());
|
out.writeByte(TypeEnum.GENERIC_TYPE_VARIABLE.ordinal());
|
||||||
writeString(out, argType.getObject());
|
writeString(out, argType.getObject());
|
||||||
|
writeArgTypesList(out, argType.getExtendTypes(), names);
|
||||||
} else if (argType.isObject()) {
|
} else if (argType.isObject()) {
|
||||||
out.writeByte(TypeEnum.OBJECT.ordinal());
|
out.writeByte(TypeEnum.OBJECT.ordinal());
|
||||||
out.writeInt(names.get(argType.getObject()).getId());
|
out.writeInt(getCls(argType, names).getId());
|
||||||
} else if (argType.isArray()) {
|
} else if (argType.isArray()) {
|
||||||
out.writeByte(TypeEnum.ARRAY.ordinal());
|
out.writeByte(TypeEnum.ARRAY.ordinal());
|
||||||
writeArgType(out, argType.getArrayElement(), names);
|
writeArgType(out, argType.getArrayElement(), names);
|
||||||
} else if (argType.isPrimitive()) {
|
|
||||||
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
|
|
||||||
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
|
|
||||||
} else {
|
} else {
|
||||||
throw new JadxRuntimeException("Cannot save type: " + argType);
|
throw new JadxRuntimeException("Cannot save type: " + argType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void load() throws IOException, DecodeException {
|
private void load(File input) throws IOException, DecodeException {
|
||||||
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
|
|
||||||
if (input == null) {
|
|
||||||
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
|
|
||||||
}
|
|
||||||
load(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void load(File input) throws IOException, DecodeException {
|
|
||||||
String name = input.getName();
|
String name = input.getName();
|
||||||
try (InputStream inputStream = new FileInputStream(input)) {
|
if (name.endsWith(CLST_EXTENSION)) {
|
||||||
if (name.endsWith(CLST_EXTENSION)) {
|
try (InputStream inputStream = new FileInputStream(input)) {
|
||||||
load(inputStream);
|
load(inputStream);
|
||||||
} else if (name.endsWith(".jar")) {
|
}
|
||||||
try (ZipInputStream in = new ZipInputStream(inputStream)) {
|
} else if (name.endsWith(".jar")) {
|
||||||
ZipEntry entry = in.getNextEntry();
|
ZipSecurity.readZipEntries(input, (entry, in) -> {
|
||||||
while (entry != null) {
|
if (entry.getName().endsWith(CLST_EXTENSION)) {
|
||||||
if (entry.getName().endsWith(CLST_EXTENSION) && ZipSecurity.isValidZipEntry(entry)) {
|
try {
|
||||||
load(in);
|
load(in);
|
||||||
}
|
} catch (Exception e) {
|
||||||
entry = in.getNextEntry();
|
throw new JadxRuntimeException("Failed to load jadx class set");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
throw new JadxRuntimeException("Unknown file format: " + name);
|
} else {
|
||||||
}
|
throw new JadxRuntimeException("Unknown file format: " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void load(InputStream input) throws IOException, DecodeException {
|
private void load(InputStream input) throws IOException, DecodeException {
|
||||||
try (DataInputStream in = new DataInputStream(input)) {
|
try (DataInputStream in = new DataInputStream(new BufferedInputStream(input))) {
|
||||||
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
|
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
|
||||||
int readHeaderLength = in.read(header);
|
int readHeaderLength = in.read(header);
|
||||||
int version = in.readByte();
|
int version = in.readByte();
|
||||||
@@ -323,96 +340,121 @@ public class ClsSet {
|
|||||||
|| version != VERSION) {
|
|| version != VERSION) {
|
||||||
throw new DecodeException("Wrong jadx class set header");
|
throw new DecodeException("Wrong jadx class set header");
|
||||||
}
|
}
|
||||||
int count = in.readInt();
|
int clsCount = in.readInt();
|
||||||
classes = new NClass[count];
|
classes = new ClspClass[clsCount];
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < clsCount; i++) {
|
||||||
String name = readString(in);
|
String name = readString(in);
|
||||||
classes[i] = new NClass(name, i);
|
classes[i] = new ClspClass(ArgType.object(name), i);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < clsCount; i++) {
|
||||||
int pCount = in.readByte();
|
ClspClass nClass = classes[i];
|
||||||
NClass[] parents = new NClass[pCount];
|
ClassInfo clsInfo = ClassInfo.fromType(root, nClass.getClsType());
|
||||||
for (int j = 0; j < pCount; j++) {
|
nClass.setParents(readArgTypesArray(in));
|
||||||
parents[j] = classes[in.readInt()];
|
nClass.setTypeParameters(readArgTypesList(in));
|
||||||
}
|
nClass.setMethods(readClsMethods(in, clsInfo));
|
||||||
classes[i].setParents(parents);
|
|
||||||
|
|
||||||
int mCount = in.readByte();
|
|
||||||
NMethod[] methods = new NMethod[mCount];
|
|
||||||
for (int j = 0; j < mCount; j++) {
|
|
||||||
methods[j] = readMethod(in);
|
|
||||||
}
|
|
||||||
classes[i].setMethods(methods);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private NMethod readMethod(DataInputStream in) throws IOException {
|
private List<ClspMethod> readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException {
|
||||||
String shortId = readLongString(in);
|
int mCount = in.readShort();
|
||||||
int argCount = in.readByte();
|
List<ClspMethod> methods = new ArrayList<>(mCount);
|
||||||
ArgType[] argTypes = null;
|
for (int j = 0; j < mCount; j++) {
|
||||||
for (int i = 0; i < argCount; i++) {
|
methods.add(readMethod(in, clsInfo));
|
||||||
int index = in.readByte();
|
|
||||||
ArgType argType = readArgType(in);
|
|
||||||
if (argTypes == null) {
|
|
||||||
argTypes = new ArgType[index + 1];
|
|
||||||
}
|
|
||||||
argTypes[index] = argType;
|
|
||||||
}
|
}
|
||||||
ArgType retType = in.readBoolean() ? readArgType(in) : null;
|
return methods;
|
||||||
boolean varArgs = in.readBoolean();
|
}
|
||||||
return new NMethod(shortId, argTypes, retType, varArgs);
|
|
||||||
|
private ClspMethod readMethod(DataInputStream in, ClassInfo clsInfo) throws IOException {
|
||||||
|
String name = readString(in);
|
||||||
|
List<ArgType> argTypes = readArgTypesList(in);
|
||||||
|
ArgType retType = readArgType(in);
|
||||||
|
List<ArgType> genericArgTypes = readArgTypesList(in);
|
||||||
|
if (genericArgTypes.isEmpty() || Objects.equals(genericArgTypes, argTypes)) {
|
||||||
|
genericArgTypes = argTypes;
|
||||||
|
}
|
||||||
|
ArgType genericRetType = readArgType(in);
|
||||||
|
if (Objects.equals(genericRetType, retType)) {
|
||||||
|
genericRetType = retType;
|
||||||
|
}
|
||||||
|
List<ArgType> typeParameters = readArgTypesList(in);
|
||||||
|
int accFlags = in.readInt();
|
||||||
|
List<ArgType> throwList = readArgTypesList(in);
|
||||||
|
MethodInfo methodInfo = MethodInfo.fromDetails(root, clsInfo, name, argTypes, retType);
|
||||||
|
return new ClspMethod(methodInfo,
|
||||||
|
genericArgTypes, genericRetType,
|
||||||
|
typeParameters, throwList, accFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ArgType> readArgTypesList(DataInputStream in) throws IOException {
|
||||||
|
int count = in.readByte();
|
||||||
|
if (count == 0) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<ArgType> list = new ArrayList<>(count);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
list.add(readArgType(in));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ArgType[] readArgTypesArray(DataInputStream in) throws IOException {
|
||||||
|
int count = in.readByte();
|
||||||
|
if (count == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (count == 0) {
|
||||||
|
return EMPTY_ARGTYPE_ARRAY;
|
||||||
|
}
|
||||||
|
ArgType[] arr = new ArgType[count];
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
arr[i] = readArgType(in);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArgType readArgType(DataInputStream in) throws IOException {
|
private ArgType readArgType(DataInputStream in) throws IOException {
|
||||||
int ordinal = in.readByte();
|
int ordinal = in.readByte();
|
||||||
|
if (ordinal == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (ordinal >= TypeEnum.values().length) {
|
||||||
|
throw new JadxRuntimeException("Incorrect ordinal for type enum: " + ordinal);
|
||||||
|
}
|
||||||
switch (TypeEnum.values()[ordinal]) {
|
switch (TypeEnum.values()[ordinal]) {
|
||||||
case WILDCARD:
|
case WILDCARD:
|
||||||
int bounds = in.readByte();
|
ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte());
|
||||||
return bounds == 0
|
if (bound == ArgType.WildcardBound.UNBOUND) {
|
||||||
? ArgType.wildcard()
|
return ArgType.WILDCARD;
|
||||||
: ArgType.wildcard(readArgType(in), bounds);
|
|
||||||
case GENERIC:
|
|
||||||
String obj = classes[in.readInt()].getName();
|
|
||||||
int typeLength = in.readByte();
|
|
||||||
ArgType[] generics;
|
|
||||||
if (typeLength == 0) {
|
|
||||||
generics = null;
|
|
||||||
} else {
|
|
||||||
generics = new ArgType[typeLength];
|
|
||||||
for (int i = 0; i < typeLength; i++) {
|
|
||||||
generics[i] = readArgType(in);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return ArgType.generic(obj, generics);
|
ArgType objType = readArgType(in);
|
||||||
case GENERIC_TYPE:
|
return ArgType.wildcard(objType, bound);
|
||||||
return ArgType.genericType(readString(in));
|
|
||||||
|
case OUTER_GENERIC:
|
||||||
|
ArgType outerType = readArgType(in);
|
||||||
|
ArgType innerType = readArgType(in);
|
||||||
|
return ArgType.outerGeneric(outerType, innerType);
|
||||||
|
|
||||||
|
case GENERIC:
|
||||||
|
ArgType clsType = classes[in.readInt()].getClsType();
|
||||||
|
return ArgType.generic(clsType, readArgTypesList(in));
|
||||||
|
|
||||||
|
case GENERIC_TYPE_VARIABLE:
|
||||||
|
String typeVar = readString(in);
|
||||||
|
List<ArgType> extendTypes = readArgTypesList(in);
|
||||||
|
return ArgType.genericType(typeVar, extendTypes);
|
||||||
|
|
||||||
case OBJECT:
|
case OBJECT:
|
||||||
return ArgType.object(classes[in.readInt()].getName());
|
return classes[in.readInt()].getClsType();
|
||||||
|
|
||||||
case ARRAY:
|
case ARRAY:
|
||||||
return ArgType.array(readArgType(in));
|
return ArgType.array(readArgType(in));
|
||||||
|
|
||||||
case PRIMITIVE:
|
case PRIMITIVE:
|
||||||
int shortName = in.readByte();
|
char shortName = (char) in.readByte();
|
||||||
switch (shortName) {
|
return ArgType.parse(shortName);
|
||||||
case 'Z':
|
|
||||||
return ArgType.BOOLEAN;
|
|
||||||
case 'C':
|
|
||||||
return ArgType.CHAR;
|
|
||||||
case 'B':
|
|
||||||
return ArgType.BYTE;
|
|
||||||
case 'S':
|
|
||||||
return ArgType.SHORT;
|
|
||||||
case 'I':
|
|
||||||
return ArgType.INT;
|
|
||||||
case 'F':
|
|
||||||
return ArgType.FLOAT;
|
|
||||||
case 'J':
|
|
||||||
return ArgType.LONG;
|
|
||||||
case 'D':
|
|
||||||
return ArgType.DOUBLE;
|
|
||||||
default:
|
|
||||||
return ArgType.VOID;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
throw new JadxRuntimeException("Unsupported Arg Type: " + ordinal);
|
throw new JadxRuntimeException("Unsupported Arg Type: " + ordinal);
|
||||||
}
|
}
|
||||||
@@ -420,23 +462,16 @@ public class ClsSet {
|
|||||||
|
|
||||||
private static void writeString(DataOutputStream out, String name) throws IOException {
|
private static void writeString(DataOutputStream out, String name) throws IOException {
|
||||||
byte[] bytes = name.getBytes(STRING_CHARSET);
|
byte[] bytes = name.getBytes(STRING_CHARSET);
|
||||||
out.writeByte(bytes.length);
|
int len = bytes.length;
|
||||||
out.write(bytes);
|
if (len >= 0xFF) {
|
||||||
}
|
throw new JadxRuntimeException("String is too long: " + name);
|
||||||
|
}
|
||||||
private static void writeLongString(DataOutputStream out, String name) throws IOException {
|
writeUnsignedByte(out, bytes.length);
|
||||||
byte[] bytes = name.getBytes(STRING_CHARSET);
|
|
||||||
out.writeShort(bytes.length);
|
|
||||||
out.write(bytes);
|
out.write(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String readString(DataInputStream in) throws IOException {
|
private static String readString(DataInputStream in) throws IOException {
|
||||||
int len = in.readByte();
|
int len = readUnsignedByte(in);
|
||||||
return readString(in, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String readLongString(DataInputStream in) throws IOException {
|
|
||||||
int len = in.readShort();
|
|
||||||
return readString(in, len);
|
return readString(in, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,12 +489,23 @@ public class ClsSet {
|
|||||||
return new String(bytes, STRING_CHARSET);
|
return new String(bytes, STRING_CHARSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void writeUnsignedByte(DataOutputStream out, int value) throws IOException {
|
||||||
|
if (value < 0 || value >= 0xFF) {
|
||||||
|
throw new JadxRuntimeException("Unsigned byte value is too big: " + value);
|
||||||
|
}
|
||||||
|
out.writeByte(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int readUnsignedByte(DataInputStream in) throws IOException {
|
||||||
|
return ((int) in.readByte()) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
public int getClassesCount() {
|
public int getClassesCount() {
|
||||||
return classes.length;
|
return classes.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addToMap(Map<String, NClass> nameMap) {
|
public void addToMap(Map<String, ClspClass> nameMap) {
|
||||||
for (NClass cls : classes) {
|
for (ClspClass cls : classes) {
|
||||||
nameMap.put(cls.getName(), cls);
|
nameMap.put(cls.getName(), cls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package jadx.core.clsp;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class node in classpath graph
|
||||||
|
*/
|
||||||
|
public class ClspClass {
|
||||||
|
|
||||||
|
private final ArgType clsType;
|
||||||
|
private final int id;
|
||||||
|
private ArgType[] parents;
|
||||||
|
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
|
||||||
|
private List<ArgType> typeParameters = Collections.emptyList();
|
||||||
|
|
||||||
|
public ClspClass(ArgType clsType, int id) {
|
||||||
|
this.clsType = clsType;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return clsType.getObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getClsType() {
|
||||||
|
return clsType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType[] getParents() {
|
||||||
|
return parents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParents(ArgType[] parents) {
|
||||||
|
this.parents = parents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, ClspMethod> getMethodsMap() {
|
||||||
|
return methodsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ClspMethod> getSortedMethodsList() {
|
||||||
|
List<ClspMethod> list = new ArrayList<>(methodsMap.size());
|
||||||
|
list.addAll(methodsMap.values());
|
||||||
|
Collections.sort(list);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMethodsMap(Map<String, ClspMethod> methodsMap) {
|
||||||
|
this.methodsMap = Objects.requireNonNull(methodsMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMethods(List<ClspMethod> methods) {
|
||||||
|
Map<String, ClspMethod> map = new HashMap<>(methods.size());
|
||||||
|
for (ClspMethod mth : methods) {
|
||||||
|
map.put(mth.getMethodInfo().getShortId(), mth);
|
||||||
|
}
|
||||||
|
setMethodsMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ArgType> getTypeParameters() {
|
||||||
|
return typeParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTypeParameters(List<ArgType> typeParameters) {
|
||||||
|
this.typeParameters = typeParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return clsType.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ClspClass nClass = (ClspClass) o;
|
||||||
|
return clsType.equals(nClass.clsType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return clsType.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,29 +8,39 @@ 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.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.IMethodDetails;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.utils.exceptions.DecodeException;
|
import jadx.core.utils.exceptions.DecodeException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Classes hierarchy graph
|
* Classes hierarchy graph with methods additional info
|
||||||
*/
|
*/
|
||||||
public class ClspGraph {
|
public class ClspGraph {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
||||||
|
|
||||||
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>());
|
private final RootNode root;
|
||||||
private Map<String, NClass> 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<>();
|
||||||
|
|
||||||
|
public ClspGraph(RootNode rootNode) {
|
||||||
|
this.root = rootNode;
|
||||||
|
}
|
||||||
|
|
||||||
public void load() throws IOException, DecodeException {
|
public void load() throws IOException, DecodeException {
|
||||||
ClsSet set = new ClsSet();
|
ClsSet set = new ClsSet(root);
|
||||||
set.load();
|
set.loadFromClstFile();
|
||||||
addClasspath(set);
|
addClasspath(set);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,51 +57,90 @@ public class ClspGraph {
|
|||||||
if (nameMap == null) {
|
if (nameMap == null) {
|
||||||
throw new JadxRuntimeException("Classpath must be loaded first");
|
throw new JadxRuntimeException("Classpath must be loaded first");
|
||||||
}
|
}
|
||||||
int size = classes.size();
|
|
||||||
NClass[] nClasses = new NClass[size];
|
|
||||||
int k = 0;
|
|
||||||
for (ClassNode cls : classes) {
|
for (ClassNode cls : classes) {
|
||||||
nClasses[k++] = addClass(cls);
|
addClass(cls);
|
||||||
}
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void initCache() {
|
||||||
|
fillSuperTypesCache();
|
||||||
|
fillImplementsCache();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isClsKnown(String fullName) {
|
public boolean isClsKnown(String fullName) {
|
||||||
return nameMap.containsKey(fullName);
|
return nameMap.containsKey(fullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private NClass addClass(ClassNode cls) {
|
public ClspClass getClsDetails(ArgType type) {
|
||||||
String rawName = cls.getRawName();
|
return nameMap.get(type.getObject());
|
||||||
NClass nClass = new NClass(rawName, -1);
|
}
|
||||||
nameMap.put(rawName, nClass);
|
|
||||||
return nClass;
|
@Nullable
|
||||||
|
public IMethodDetails getMethodDetails(MethodInfo methodInfo) {
|
||||||
|
ClspClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
|
||||||
|
if (cls == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ClspMethod clspMethod = getMethodFromClass(cls, methodInfo);
|
||||||
|
if (clspMethod != null) {
|
||||||
|
return clspMethod;
|
||||||
|
}
|
||||||
|
// deep search
|
||||||
|
for (ArgType parent : cls.getParents()) {
|
||||||
|
ClspClass clspParent = getClspClass(parent);
|
||||||
|
if (clspParent != null) {
|
||||||
|
ClspMethod methodFromParent = getMethodFromClass(clspParent, methodInfo);
|
||||||
|
if (methodFromParent != null) {
|
||||||
|
return methodFromParent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// unknown method
|
||||||
|
return new SimpleMethodDetails(methodInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClspMethod getMethodFromClass(ClspClass cls, MethodInfo methodInfo) {
|
||||||
|
return cls.getMethodsMap().get(methodInfo.getShortId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addClass(ClassNode cls) {
|
||||||
|
ArgType clsType = cls.getClassInfo().getType();
|
||||||
|
String rawName = clsType.getObject();
|
||||||
|
ClspClass clspClass = new ClspClass(clsType, -1);
|
||||||
|
clspClass.setParents(ClsSet.makeParentsArray(cls));
|
||||||
|
nameMap.put(rawName, clspClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@code clsName} instanceof {@code implClsName}
|
* @return {@code clsName} instanceof {@code implClsName}
|
||||||
*/
|
*/
|
||||||
public boolean isImplements(String clsName, String implClsName) {
|
public boolean isImplements(String clsName, String implClsName) {
|
||||||
Set<String> anc = getAncestors(clsName);
|
Set<String> anc = getSuperTypes(clsName);
|
||||||
return anc.contains(implClsName);
|
return anc.contains(implClsName);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
if (clsName.equals(implClsName)) {
|
if (clsName.equals(implClsName)) {
|
||||||
return clsName;
|
return clsName;
|
||||||
}
|
}
|
||||||
NClass cls = nameMap.get(implClsName);
|
ClspClass cls = nameMap.get(implClsName);
|
||||||
if (cls == null) {
|
if (cls == null) {
|
||||||
missingClasses.add(clsName);
|
missingClasses.add(clsName);
|
||||||
return null;
|
return null;
|
||||||
@@ -99,52 +148,74 @@ public class ClspGraph {
|
|||||||
if (isImplements(clsName, implClsName)) {
|
if (isImplements(clsName, implClsName)) {
|
||||||
return implClsName;
|
return implClsName;
|
||||||
}
|
}
|
||||||
Set<String> anc = getAncestors(clsName);
|
Set<String> anc = getSuperTypes(clsName);
|
||||||
return searchCommonParent(anc, cls);
|
return searchCommonParent(anc, cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String searchCommonParent(Set<String> anc, NClass cls) {
|
private String searchCommonParent(Set<String> anc, ClspClass cls) {
|
||||||
for (NClass p : cls.getParents()) {
|
for (ArgType p : cls.getParents()) {
|
||||||
String name = p.getName();
|
String name = p.getObject();
|
||||||
if (anc.contains(name)) {
|
if (anc.contains(name)) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
String r = searchCommonParent(anc, p);
|
ClspClass nCls = getClspClass(p);
|
||||||
if (r != null) {
|
if (nCls != null) {
|
||||||
return r;
|
String r = searchCommonParent(anc, nCls);
|
||||||
|
if (r != null) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> getAncestors(String clsName) {
|
public Set<String> getSuperTypes(String clsName) {
|
||||||
Set<String> result = ancestorCache.get(clsName);
|
Set<String> result = superTypesCache.get(clsName);
|
||||||
if (result != null) {
|
return result == null ? Collections.emptySet() : result;
|
||||||
return result;
|
|
||||||
}
|
|
||||||
NClass cls = nameMap.get(clsName);
|
|
||||||
if (cls == null) {
|
|
||||||
missingClasses.add(clsName);
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
result = new HashSet<>();
|
|
||||||
addAncestorsNames(cls, result);
|
|
||||||
if (result.isEmpty()) {
|
|
||||||
result = Collections.emptySet();
|
|
||||||
}
|
|
||||||
ancestorCache.put(clsName, result);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAncestorsNames(NClass cls, Set<String> result) {
|
private void fillSuperTypesCache() {
|
||||||
boolean isNew = result.add(cls.getName());
|
Map<String, Set<String>> map = new HashMap<>(nameMap.size());
|
||||||
if (isNew) {
|
Set<String> tmpSet = new HashSet<>();
|
||||||
for (NClass p : cls.getParents()) {
|
for (Map.Entry<String, ClspClass> entry : nameMap.entrySet()) {
|
||||||
addAncestorsNames(p, result);
|
ClspClass cls = entry.getValue();
|
||||||
|
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 = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addSuperTypes(ClspClass cls, Set<String> result) {
|
||||||
|
for (ArgType parentType : cls.getParents()) {
|
||||||
|
if (parentType == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ClspClass parentCls = getClspClass(parentType);
|
||||||
|
if (parentCls != null) {
|
||||||
|
boolean isNew = result.add(parentCls.getName());
|
||||||
|
if (isNew) {
|
||||||
|
addSuperTypes(parentCls, result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ClspClass getClspClass(ArgType clsType) {
|
||||||
|
ClspClass clspClass = nameMap.get(clsType.getObject());
|
||||||
|
if (clspClass == null) {
|
||||||
|
missingClasses.add(clsType.getObject());
|
||||||
|
}
|
||||||
|
return clspClass;
|
||||||
|
}
|
||||||
|
|
||||||
public void printMissingClasses() {
|
public void printMissingClasses() {
|
||||||
int count = missingClasses.size();
|
int count = missingClasses.size();
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package jadx.core.clsp;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.IMethodDetails;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method node in classpath graph.
|
||||||
|
*/
|
||||||
|
public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
|
||||||
|
|
||||||
|
private final MethodInfo methodInfo;
|
||||||
|
private final List<ArgType> argTypes;
|
||||||
|
private final ArgType returnType;
|
||||||
|
private final List<ArgType> typeParameters;
|
||||||
|
private final List<ArgType> throwList;
|
||||||
|
private final int accFlags;
|
||||||
|
|
||||||
|
public ClspMethod(MethodInfo methodInfo,
|
||||||
|
List<ArgType> argTypes, ArgType returnType,
|
||||||
|
List<ArgType> typeParameters, List<ArgType> throwList, int accFlags) {
|
||||||
|
this.methodInfo = methodInfo;
|
||||||
|
this.argTypes = argTypes;
|
||||||
|
this.returnType = returnType;
|
||||||
|
this.typeParameters = typeParameters;
|
||||||
|
this.throwList = throwList;
|
||||||
|
this.accFlags = accFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodInfo getMethodInfo() {
|
||||||
|
return methodInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArgType getReturnType() {
|
||||||
|
return returnType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ArgType> getArgTypes() {
|
||||||
|
return argTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsGenericArgs() {
|
||||||
|
return !Objects.equals(argTypes, methodInfo.getArgumentsTypes());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getArgsCount() {
|
||||||
|
return argTypes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ArgType> getTypeParameters() {
|
||||||
|
return typeParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ArgType> getThrows() {
|
||||||
|
return throwList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVarArg() {
|
||||||
|
return (accFlags & AccessFlags.VARARGS) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRawAccessFlags() {
|
||||||
|
return accFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof ClspMethod)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ClspMethod other = (ClspMethod) o;
|
||||||
|
return methodInfo.equals(other.methodInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return methodInfo.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(@NotNull ClspMethod other) {
|
||||||
|
return this.methodInfo.compareTo(other.methodInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toAttrString() {
|
||||||
|
return IMethodDetails.super.toAttrString() + " (c)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("ClspMth{");
|
||||||
|
if (Utils.notEmpty(getTypeParameters())) {
|
||||||
|
sb.append('<');
|
||||||
|
sb.append(Utils.listToString(getTypeParameters()));
|
||||||
|
sb.append("> ");
|
||||||
|
}
|
||||||
|
sb.append(getMethodInfo().getFullName());
|
||||||
|
sb.append('(');
|
||||||
|
sb.append(Utils.listToString(getArgTypes()));
|
||||||
|
sb.append("):");
|
||||||
|
sb.append(getReturnType());
|
||||||
|
if (isVarArg()) {
|
||||||
|
sb.append(" VARARG");
|
||||||
|
}
|
||||||
|
List<ArgType> throwsList = getThrows();
|
||||||
|
if (Utils.notEmpty(throwsList)) {
|
||||||
|
sb.append(" throws ").append(Utils.listToString(throwsList));
|
||||||
|
}
|
||||||
|
sb.append('}');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
package jadx.core.clsp;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.api.JadxArgs;
|
|
||||||
import jadx.core.dex.nodes.RootNode;
|
|
||||||
import jadx.core.utils.exceptions.DecodeException;
|
|
||||||
import jadx.core.utils.files.InputFile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class for convert dex or jar to jadx classes set (.jcst)
|
|
||||||
*/
|
|
||||||
public class ConvertToClsSet {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
|
|
||||||
|
|
||||||
public static void usage() {
|
|
||||||
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException, DecodeException {
|
|
||||||
if (args.length < 2) {
|
|
||||||
usage();
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
Path output = Paths.get(args[0]);
|
|
||||||
|
|
||||||
List<InputFile> inputFiles = new ArrayList<>(args.length - 1);
|
|
||||||
for (int i = 1; i < args.length; i++) {
|
|
||||||
File f = new File(args[i]);
|
|
||||||
if (f.isDirectory()) {
|
|
||||||
addFilesFromDirectory(f, inputFiles);
|
|
||||||
} else {
|
|
||||||
InputFile.addFilesFrom(f, inputFiles, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (InputFile inputFile : inputFiles) {
|
|
||||||
LOG.info("Loaded: {}", inputFile.getFile());
|
|
||||||
}
|
|
||||||
|
|
||||||
RootNode root = new RootNode(new JadxArgs());
|
|
||||||
root.load(inputFiles);
|
|
||||||
|
|
||||||
ClsSet set = new ClsSet();
|
|
||||||
set.load(root);
|
|
||||||
set.save(output);
|
|
||||||
LOG.info("Output: {}", output);
|
|
||||||
LOG.info("done");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) {
|
|
||||||
File[] files = dir.listFiles();
|
|
||||||
if (files == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (File file : files) {
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
addFilesFromDirectory(file, inputFiles);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
InputFile.addFilesFrom(file, inputFiles, false);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package jadx.core.clsp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class node in classpath graph
|
|
||||||
*/
|
|
||||||
public class NClass {
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private NClass[] parents;
|
|
||||||
private NMethod[] methods;
|
|
||||||
private final int id;
|
|
||||||
|
|
||||||
public NClass(String name, int id) {
|
|
||||||
this.name = name;
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NClass[] getParents() {
|
|
||||||
return parents;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setParents(NClass[] parents) {
|
|
||||||
this.parents = parents;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return name.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
NClass nClass = (NClass) o;
|
|
||||||
return name.equals(nClass.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMethods(NMethod[] methods) {
|
|
||||||
this.methods = methods;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NMethod[] getMethods() {
|
|
||||||
return methods;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package jadx.core.clsp;
|
|
||||||
|
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic method node in classpath graph.
|
|
||||||
*/
|
|
||||||
public class NMethod {
|
|
||||||
|
|
||||||
private final String shortId;
|
|
||||||
private final ArgType[] argType;
|
|
||||||
private final ArgType retType;
|
|
||||||
private final boolean varArgs;
|
|
||||||
|
|
||||||
public NMethod(String shortId, ArgType[] argType, ArgType retType, boolean varArgs) {
|
|
||||||
this.shortId = shortId;
|
|
||||||
this.argType = argType;
|
|
||||||
this.retType = retType;
|
|
||||||
this.varArgs = varArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShortId() {
|
|
||||||
return shortId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArgType[] getArgType() {
|
|
||||||
return argType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArgType getReturnType() {
|
|
||||||
return retType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isVarArgs() {
|
|
||||||
return varArgs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package jadx.core.clsp;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.nodes.IMethodDetails;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method details build from MethodInfo.
|
||||||
|
* Note: some fields have unknown values.
|
||||||
|
*/
|
||||||
|
public class SimpleMethodDetails implements IMethodDetails {
|
||||||
|
|
||||||
|
private final MethodInfo methodInfo;
|
||||||
|
|
||||||
|
public SimpleMethodDetails(MethodInfo methodInfo) {
|
||||||
|
this.methodInfo = methodInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodInfo getMethodInfo() {
|
||||||
|
return methodInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArgType getReturnType() {
|
||||||
|
return methodInfo.getReturnType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ArgType> getArgTypes() {
|
||||||
|
return methodInfo.getArgumentsTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ArgType> getTypeParameters() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ArgType> getThrows() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVarArg() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRawAccessFlags() {
|
||||||
|
return AccessFlags.PUBLIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toAttrString() {
|
||||||
|
return IMethodDetails.super.toAttrString() + " (s)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SimpleMethodDetails{" + methodInfo + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,17 +7,22 @@ import java.util.Map.Entry;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
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.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.AnnotationsAttr;
|
||||||
|
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
|
||||||
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.Annotation;
|
|
||||||
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;
|
||||||
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.utils.StringUtils;
|
import jadx.core.utils.StringUtils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
@@ -31,61 +36,61 @@ 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, MethodParamsAttr 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;
|
||||||
}
|
}
|
||||||
for (Annotation a : aList.getAll()) {
|
for (IAnnotation a : aList.getAll()) {
|
||||||
formatAnnotation(code, a);
|
formatAnnotation(code, a);
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (Annotation 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, Annotation a) {
|
private void formatAnnotation(ICodeWriter code, IAnnotation a) {
|
||||||
code.add('@');
|
code.add('@');
|
||||||
ClassNode annCls = cls.dex().resolveClass(a.getType());
|
ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass());
|
||||||
if (annCls != null) {
|
if (annCls != null) {
|
||||||
classGen.useClass(code, annCls);
|
classGen.useClass(code, annCls);
|
||||||
} else {
|
} else {
|
||||||
classGen.useType(code, a.getType());
|
classGen.useClass(code, a.getAnnotationClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> vl = a.getValues();
|
Map<String, EncodedValue> vl = a.getValues();
|
||||||
if (!vl.isEmpty()) {
|
if (!vl.isEmpty()) {
|
||||||
code.add('(');
|
code.add('(');
|
||||||
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext();) {
|
for (Iterator<Entry<String, EncodedValue>> it = vl.entrySet().iterator(); it.hasNext();) {
|
||||||
Entry<String, Object> e = it.next();
|
Entry<String, EncodedValue> e = it.next();
|
||||||
String paramName = getParamName(annCls, e.getKey());
|
String paramName = getParamName(annCls, e.getKey());
|
||||||
if (paramName.equals("value") && vl.size() == 1) {
|
if (paramName.equals("value") && vl.size() == 1) {
|
||||||
// don't add "value = " if no other parameters
|
// don't add "value = " if no other parameters
|
||||||
@@ -93,7 +98,7 @@ public class AnnotationGen {
|
|||||||
code.add(paramName);
|
code.add(paramName);
|
||||||
code.add(" = ");
|
code.add(" = ");
|
||||||
}
|
}
|
||||||
encodeValue(code, e.getValue());
|
encodeValue(cls.root(), code, e.getValue());
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
code.add(", ");
|
code.add(", ");
|
||||||
}
|
}
|
||||||
@@ -113,13 +118,11 @@ public class AnnotationGen {
|
|||||||
return paramName;
|
return paramName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
public void addThrows(MethodNode mth, ICodeWriter code) {
|
||||||
public void addThrows(MethodNode mth, CodeWriter code) {
|
List<ArgType> throwList = mth.getThrows();
|
||||||
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
|
if (!throwList.isEmpty()) {
|
||||||
if (an != null) {
|
|
||||||
Object exs = an.getDefaultValue();
|
|
||||||
code.add(" throws ");
|
code.add(" throws ");
|
||||||
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext();) {
|
for (Iterator<ArgType> it = throwList.iterator(); it.hasNext();) {
|
||||||
ArgType ex = it.next();
|
ArgType ex = it.next();
|
||||||
classGen.useType(code, ex);
|
classGen.useType(code, ex);
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
@@ -129,66 +132,93 @@ public class AnnotationGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getAnnotationDefaultValue(String name) {
|
public EncodedValue getAnnotationDefaultValue(MethodNode mth) {
|
||||||
Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
|
AnnotationDefaultAttr defaultAttr = mth.get(JadxAttrType.ANNOTATION_DEFAULT);
|
||||||
if (an != null) {
|
if (defaultAttr == null) {
|
||||||
Annotation defAnnotation = (Annotation) an.getDefaultValue();
|
return null;
|
||||||
return defAnnotation.getValues().get(name);
|
|
||||||
}
|
}
|
||||||
return null;
|
return defaultAttr.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refactor this boilerplate code
|
// TODO: refactor this boilerplate code
|
||||||
public void encodeValue(CodeWriter code, Object val) {
|
public void encodeValue(RootNode root, ICodeWriter code, EncodedValue encodedValue) {
|
||||||
if (val == null) {
|
if (encodedValue == null) {
|
||||||
code.add("null");
|
code.add("null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (val instanceof String) {
|
Object value = encodedValue.getValue();
|
||||||
code.add(getStringUtils().unescapeString((String) val));
|
switch (encodedValue.getType()) {
|
||||||
} else if (val instanceof Integer) {
|
case ENCODED_NULL:
|
||||||
code.add(TypeGen.formatInteger((Integer) val));
|
code.add("null");
|
||||||
} else if (val instanceof Character) {
|
break;
|
||||||
code.add(getStringUtils().unescapeChar((Character) val));
|
case ENCODED_BOOLEAN:
|
||||||
} else if (val instanceof Boolean) {
|
code.add(Boolean.TRUE.equals(value) ? "true" : "false");
|
||||||
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
|
break;
|
||||||
} else if (val instanceof Float) {
|
case ENCODED_BYTE:
|
||||||
code.add(TypeGen.formatFloat((Float) val));
|
code.add(TypeGen.formatByte((Byte) value, false));
|
||||||
} else if (val instanceof Double) {
|
break;
|
||||||
code.add(TypeGen.formatDouble((Double) val));
|
case ENCODED_SHORT:
|
||||||
} else if (val instanceof Long) {
|
code.add(TypeGen.formatShort((Short) value, false));
|
||||||
code.add(TypeGen.formatLong((Long) val));
|
break;
|
||||||
} else if (val instanceof Short) {
|
case ENCODED_CHAR:
|
||||||
code.add(TypeGen.formatShort((Short) val));
|
code.add(getStringUtils().unescapeChar((Character) value));
|
||||||
} else if (val instanceof Byte) {
|
break;
|
||||||
code.add(TypeGen.formatByte((Byte) val));
|
case ENCODED_INT:
|
||||||
} else if (val instanceof ArgType) {
|
code.add(TypeGen.formatInteger((Integer) value, false));
|
||||||
classGen.useType(code, (ArgType) val);
|
break;
|
||||||
code.add(".class");
|
case ENCODED_LONG:
|
||||||
} else if (val instanceof FieldInfo) {
|
code.add(TypeGen.formatLong((Long) value, false));
|
||||||
// must be a static field
|
break;
|
||||||
FieldInfo field = (FieldInfo) val;
|
case ENCODED_FLOAT:
|
||||||
InsnGen.makeStaticFieldAccess(code, field, classGen);
|
code.add(TypeGen.formatFloat((Float) value));
|
||||||
} else if (val instanceof Iterable) {
|
break;
|
||||||
code.add('{');
|
case ENCODED_DOUBLE:
|
||||||
Iterator<?> it = ((Iterable<?>) val).iterator();
|
code.add(TypeGen.formatDouble((Double) value));
|
||||||
while (it.hasNext()) {
|
break;
|
||||||
Object obj = it.next();
|
case ENCODED_STRING:
|
||||||
encodeValue(code, obj);
|
code.add(getStringUtils().unescapeString((String) value));
|
||||||
if (it.hasNext()) {
|
break;
|
||||||
code.add(", ");
|
case ENCODED_TYPE:
|
||||||
|
classGen.useType(code, ArgType.parse((String) value));
|
||||||
|
code.add(".class");
|
||||||
|
break;
|
||||||
|
case ENCODED_ENUM:
|
||||||
|
case ENCODED_FIELD:
|
||||||
|
// must be a static field
|
||||||
|
if (value instanceof IFieldRef) {
|
||||||
|
FieldInfo fieldInfo = FieldInfo.fromRef(root, (IFieldRef) value);
|
||||||
|
InsnGen.makeStaticFieldAccess(code, fieldInfo, classGen);
|
||||||
|
} else if (value instanceof FieldInfo) {
|
||||||
|
InsnGen.makeStaticFieldAccess(code, (FieldInfo) value, classGen);
|
||||||
|
} else {
|
||||||
|
throw new JadxRuntimeException("Unexpected field type class: " + value.getClass());
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
code.add('}');
|
case ENCODED_METHOD:
|
||||||
} else if (val instanceof Annotation) {
|
// TODO
|
||||||
formatAnnotation(code, (Annotation) val);
|
break;
|
||||||
} else {
|
case ENCODED_ARRAY:
|
||||||
// TODO: also can be method values
|
code.add('{');
|
||||||
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ')');
|
Iterator<?> it = ((Iterable<?>) value).iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
EncodedValue v = (EncodedValue) it.next();
|
||||||
|
encodeValue(cls.root(), code, v);
|
||||||
|
if (it.hasNext()) {
|
||||||
|
code.add(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.add('}');
|
||||||
|
break;
|
||||||
|
case ENCODED_ANNOTATION:
|
||||||
|
formatAnnotation(code, (IAnnotation) value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Can't decode value: " + encodedValue.getType() + " (" + encodedValue + ')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private StringUtils getStringUtils() {
|
private StringUtils getStringUtils() {
|
||||||
return cls.dex().root().getStringUtils();
|
return cls.root().getStringUtils();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,52 @@
|
|||||||
package jadx.core.codegen;
|
package jadx.core.codegen;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Objects;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import com.android.dx.rop.code.AccessFlags;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.CommentsLevel;
|
||||||
|
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.annotations.EncodedType;
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
|
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.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.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;
|
||||||
import jadx.core.dex.nodes.DexNode;
|
|
||||||
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.parser.FieldInitAttr;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
|
|
||||||
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;
|
||||||
|
|
||||||
@@ -47,7 +60,12 @@ 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;
|
||||||
|
|
||||||
|
@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());
|
||||||
@@ -71,11 +89,11 @@ public class ClassGen {
|
|||||||
return cls;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CodeWriter 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();
|
||||||
@@ -100,36 +118,39 @@ 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;
|
||||||
}
|
}
|
||||||
CodeGenUtils.addComments(code, cls);
|
if (Consts.DEBUG_USAGE) {
|
||||||
insertDecompilationProblems(code, cls);
|
addClassUsageInfo(code, cls);
|
||||||
|
}
|
||||||
|
CodeGenUtils.addErrorsAndComments(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.ACC_ABSTRACT)
|
af = af.remove(AccessFlags.ABSTRACT)
|
||||||
.remove(AccessFlags.ACC_STATIC);
|
.remove(AccessFlags.STATIC);
|
||||||
} else if (af.isEnum()) {
|
} else if (af.isEnum()) {
|
||||||
af = af.remove(AccessFlags.ACC_FINAL)
|
af = af.remove(AccessFlags.FINAL)
|
||||||
.remove(AccessFlags.ACC_ABSTRACT)
|
.remove(AccessFlags.ABSTRACT)
|
||||||
.remove(AccessFlags.ACC_STATIC);
|
.remove(AccessFlags.STATIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'static' and 'private' modifier not allowed for top classes (not inner)
|
// 'static' and 'private' modifier not allowed for top classes (not inner)
|
||||||
if (!cls.getClassInfo().isInner()) {
|
if (!cls.getClassInfo().isInner()) {
|
||||||
af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE);
|
af = af.remove(AccessFlags.STATIC).remove(AccessFlags.PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
annotationGen.addForClass(clsCode);
|
annotationGen.addForClass(clsCode);
|
||||||
insertRenameInfo(clsCode, cls);
|
insertRenameInfo(clsCode, cls);
|
||||||
CodeGenUtils.addSourceFileInfo(clsCode, cls);
|
CodeGenUtils.addInputFileInfo(clsCode, cls);
|
||||||
clsCode.startLine(af.makeString());
|
clsCode.startLineWithNum(cls.getSourceLine()).add(af.makeString(cls.checkCommentsLevel(CommentsLevel.INFO)));
|
||||||
if (af.isInterface()) {
|
if (af.isInterface()) {
|
||||||
if (af.isAnnotation()) {
|
if (af.isAnnotation()) {
|
||||||
clsCode.add('@');
|
clsCode.add('@');
|
||||||
@@ -143,13 +164,13 @@ public class ClassGen {
|
|||||||
clsCode.attachDefinition(cls);
|
clsCode.attachDefinition(cls);
|
||||||
clsCode.add(cls.getClassInfo().getAliasShortName());
|
clsCode.add(cls.getClassInfo().getAliasShortName());
|
||||||
|
|
||||||
addGenericMap(clsCode, cls.getGenericMap(), true);
|
addGenericTypeParameters(clsCode, cls.getGenericTypeParameters(), true);
|
||||||
clsCode.add(' ');
|
clsCode.add(' ');
|
||||||
|
|
||||||
ArgType sup = cls.getSuperClass();
|
ArgType sup = cls.getSuperClass();
|
||||||
if (sup != null
|
if (sup != null
|
||||||
&& !sup.equals(ArgType.OBJECT)
|
&& !sup.equals(ArgType.OBJECT)
|
||||||
&& !sup.getObject().equals(ArgType.ENUM.getObject())) {
|
&& !cls.isEnum()) {
|
||||||
clsCode.add("extends ");
|
clsCode.add("extends ");
|
||||||
useClass(clsCode, sup);
|
useClass(clsCode, sup);
|
||||||
clsCode.add(' ');
|
clsCode.add(' ');
|
||||||
@@ -174,23 +195,22 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap, boolean classDeclaration) {
|
public boolean addGenericTypeParameters(ICodeWriter code, List<ArgType> generics, boolean classDeclaration) {
|
||||||
if (gmap == null || gmap.isEmpty()) {
|
if (generics == null || generics.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
code.add('<');
|
code.add('<');
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (Entry<ArgType, List<ArgType>> e : gmap.entrySet()) {
|
for (ArgType genericInfo : generics) {
|
||||||
ArgType type = e.getKey();
|
|
||||||
List<ArgType> list = e.getValue();
|
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
code.add(", ");
|
code.add(", ");
|
||||||
}
|
}
|
||||||
if (type.isGenericType()) {
|
if (genericInfo.isGenericType()) {
|
||||||
code.add(type.getObject());
|
code.add(genericInfo.getObject());
|
||||||
} else {
|
} else {
|
||||||
useClass(code, type);
|
useClass(code, genericInfo);
|
||||||
}
|
}
|
||||||
|
List<ArgType> list = genericInfo.getExtendTypes();
|
||||||
if (list != null && !list.isEmpty()) {
|
if (list != null && !list.isEmpty()) {
|
||||||
code.add(" extends ");
|
code.add(" extends ");
|
||||||
for (Iterator<ArgType> it = list.iterator(); it.hasNext();) {
|
for (Iterator<ArgType> it = list.iterator(); it.hasNext();) {
|
||||||
@@ -216,26 +236,50 @@ public class ClassGen {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addClassBody(CodeWriter clsCode) throws CodegenException {
|
public void addClassBody(ICodeWriter clsCode) throws CodegenException {
|
||||||
|
addClassBody(clsCode, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param printClassName allows to print the original class name as comment (e.g. for inlined
|
||||||
|
* classes)
|
||||||
|
*/
|
||||||
|
public void addClassBody(ICodeWriter clsCode, boolean printClassName) throws CodegenException {
|
||||||
clsCode.add('{');
|
clsCode.add('{');
|
||||||
clsDeclLine = clsCode.getLine();
|
if (printClassName && cls.checkCommentsLevel(CommentsLevel.INFO)) {
|
||||||
|
clsCode.add(" // from class: " + cls.getClassInfo().getFullName());
|
||||||
|
}
|
||||||
|
setBodyGenStarted(true);
|
||||||
|
clsDeclOffset = clsCode.getLength();
|
||||||
clsCode.incIndent();
|
clsCode.incIndent();
|
||||||
addFields(clsCode);
|
addFields(clsCode);
|
||||||
addInnerClasses(clsCode, cls);
|
addInnerClsAndMethods(clsCode);
|
||||||
addMethods(clsCode);
|
|
||||||
clsCode.decIndent();
|
clsCode.decIndent();
|
||||||
clsCode.startLine('}');
|
clsCode.startLine('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
|
private void addInnerClsAndMethods(ICodeWriter clsCode) {
|
||||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
Stream.of(cls.getInnerClasses(), cls.getMethods())
|
||||||
if (innerCls.contains(AFlag.DONT_GENERATE)) {
|
.flatMap(Collection::stream)
|
||||||
continue;
|
.filter(node -> !node.contains(AFlag.DONT_GENERATE) || fallback)
|
||||||
}
|
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
|
||||||
|
.forEach(node -> {
|
||||||
|
if (node instanceof ClassNode) {
|
||||||
|
addInnerClass(clsCode, (ClassNode) node);
|
||||||
|
} else {
|
||||||
|
addMethod(clsCode, (MethodNode) node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addInnerClass(ICodeWriter code, ClassNode innerCls) {
|
||||||
|
try {
|
||||||
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
|
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
|
||||||
code.newLine();
|
code.newLine();
|
||||||
inClGen.addClassCode(code);
|
inClGen.addClassCode(code);
|
||||||
imports.addAll(inClGen.getImports());
|
imports.addAll(inClGen.getImports());
|
||||||
|
} catch (Exception e) {
|
||||||
|
innerCls.addError("Inner class code generation error", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,36 +292,47 @@ public class ClassGen {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMethods(CodeWriter code) {
|
private void addMethod(ICodeWriter code, MethodNode mth) {
|
||||||
List<MethodNode> methods = sortMethodsByLine(cls.getMethods());
|
if (skipMethod(mth)) {
|
||||||
for (MethodNode mth : methods) {
|
return;
|
||||||
if (mth.contains(AFlag.DONT_GENERATE)) {
|
}
|
||||||
continue;
|
if (code.getLength() != clsDeclOffset) {
|
||||||
}
|
code.newLine();
|
||||||
if (code.getLine() != clsDeclLine) {
|
}
|
||||||
code.newLine();
|
int savedIndent = code.getIndent();
|
||||||
}
|
try {
|
||||||
int savedIndent = code.getIndent();
|
addMethodCode(code, mth);
|
||||||
try {
|
} catch (Exception e) {
|
||||||
addMethod(code, mth);
|
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
|
||||||
} catch (Exception e) {
|
throw new JadxRuntimeException("Method generation error", e);
|
||||||
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
|
|
||||||
throw new JadxRuntimeException("Method generation error", e);
|
|
||||||
}
|
|
||||||
code.newLine().add("/*");
|
|
||||||
code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e));
|
|
||||||
Utils.appendStackTrace(code, e);
|
|
||||||
code.newLine().add("*/");
|
|
||||||
code.setIndent(savedIndent);
|
|
||||||
mth.addError("Method generation error: " + e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
mth.addError("Method generation error", e);
|
||||||
|
CodeGenUtils.addErrors(code, mth);
|
||||||
|
code.setIndent(savedIndent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
|
/**
|
||||||
List<MethodNode> out = new ArrayList<>(methods);
|
* Additional checks for inlined methods
|
||||||
out.sort(Comparator.comparingInt(LineAttrNode::getSourceLine));
|
*/
|
||||||
return out;
|
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() {
|
||||||
@@ -289,17 +344,15 @@ public class ClassGen {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addMethod(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.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
|
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;
|
||||||
@@ -319,61 +372,56 @@ 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;
|
||||||
}
|
}
|
||||||
|
if (Consts.DEBUG_USAGE) {
|
||||||
|
addFieldUsageInfo(code, f);
|
||||||
|
}
|
||||||
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.getValue() == null) {
|
addInsnBody(insnGen, code, initInsnAttr.getInsn());
|
||||||
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
|
} else {
|
||||||
} else {
|
EncodedValue constVal = f.get(JadxAttrType.CONSTANT_VALUE);
|
||||||
if (fv.getValueType() == InitType.CONST) {
|
if (constVal != null) {
|
||||||
annotationGen.encodeValue(code, fv.getValue());
|
code.add(" = ");
|
||||||
} else if (fv.getValueType() == InitType.INSN) {
|
if (constVal.getType() == EncodedType.ENCODED_NULL) {
|
||||||
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
|
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
|
||||||
addInsnBody(insnGen, code, fv.getInsn());
|
} else {
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -389,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;
|
||||||
@@ -397,14 +445,17 @@ 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();
|
||||||
if (constrInsn.getArgsCount() > f.getStartArg()) {
|
MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth());
|
||||||
|
int skipCount = getEnumCtrSkipArgsCount(callMth);
|
||||||
|
if (constrInsn.getArgsCount() > skipCount) {
|
||||||
if (igen == null) {
|
if (igen == null) {
|
||||||
igen = makeInsnGen(enumFields.getStaticMethod());
|
igen = makeInsnGen(enumFields.getStaticMethod());
|
||||||
}
|
}
|
||||||
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth());
|
igen.generateMethodArguments(code, constrInsn, 0, callMth);
|
||||||
igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth);
|
|
||||||
}
|
}
|
||||||
if (f.getCls() != null) {
|
if (f.getCls() != null) {
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
@@ -425,20 +476,30 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getEnumCtrSkipArgsCount(@Nullable MethodNode callMth) {
|
||||||
|
if (callMth != null) {
|
||||||
|
SkipMethodArgsAttr skipArgsAttr = callMth.get(AType.SKIP_MTH_ARGS);
|
||||||
|
if (skipArgsAttr != null) {
|
||||||
|
return skipArgsAttr.getSkipCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private InsnGen makeInsnGen(MethodNode mth) {
|
private InsnGen makeInsnGen(MethodNode mth) {
|
||||||
MethodGen mthGen = new MethodGen(this, mth);
|
MethodGen mthGen = new MethodGen(this, mth);
|
||||||
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) {
|
||||||
ErrorsCounter.classError(cls, "Failed to generate init code", e);
|
cls.addError("Failed to generate init code", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
||||||
@@ -456,23 +517,35 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useClass(CodeWriter code, ArgType type) {
|
public void useClass(ICodeWriter code, String rawCls) {
|
||||||
|
useClass(code, ArgType.object(rawCls));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useClass(ICodeWriter code, ArgType type) {
|
||||||
|
ArgType outerType = type.getOuterType();
|
||||||
|
if (outerType != null) {
|
||||||
|
useClass(code, outerType);
|
||||||
|
code.add('.');
|
||||||
|
// import not needed, force use short name
|
||||||
|
useClassShortName(code, type.getObject());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
useClass(code, ClassInfo.fromType(cls.root(), type));
|
useClass(code, ClassInfo.fromType(cls.root(), type));
|
||||||
ArgType[] generics = type.getGenericTypes();
|
List<ArgType> generics = type.getGenericTypes();
|
||||||
if (generics != null) {
|
if (generics != null) {
|
||||||
code.add('<');
|
code.add('<');
|
||||||
int len = generics.length;
|
int len = generics.size();
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
code.add(", ");
|
code.add(", ");
|
||||||
}
|
}
|
||||||
ArgType gt = generics[i];
|
ArgType gt = generics.get(i);
|
||||||
ArgType wt = gt.getWildcardType();
|
ArgType wt = gt.getWildcardType();
|
||||||
if (wt != null) {
|
if (wt != null) {
|
||||||
code.add('?');
|
ArgType.WildcardBound bound = gt.getWildcardBound();
|
||||||
int bounds = gt.getWildcardBounds();
|
code.add(bound.getStr());
|
||||||
if (bounds != 0) {
|
if (bound != ArgType.WildcardBound.UNBOUND) {
|
||||||
code.add(bounds == -1 ? " super " : " extends ");
|
|
||||||
useType(code, wt);
|
useType(code, wt);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -483,8 +556,17 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useClass(CodeWriter code, ClassInfo classInfo) {
|
private void useClassShortName(ICodeWriter code, String object) {
|
||||||
ClassNode classNode = cls.dex().resolveClass(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(ICodeWriter code, ClassInfo classInfo) {
|
||||||
|
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||||
if (classNode != null) {
|
if (classNode != null) {
|
||||||
useClass(code, classNode);
|
useClass(code, classNode);
|
||||||
} else {
|
} else {
|
||||||
@@ -492,12 +574,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);
|
||||||
}
|
}
|
||||||
@@ -514,6 +596,9 @@ public class ClassGen {
|
|||||||
if (isClassInnerFor(useCls, extClsInfo)) {
|
if (isClassInnerFor(useCls, extClsInfo)) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
|
if (extClsInfo.isInner()) {
|
||||||
|
return expandInnerClassName(useCls, extClsInfo);
|
||||||
|
}
|
||||||
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
@@ -521,12 +606,7 @@ public class ClassGen {
|
|||||||
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
// don't add import if class not public (must be accessed using inheritance)
|
if (searchCollision(cls.root(), useCls, extClsInfo)) {
|
||||||
ClassNode classNode = cls.dex().resolveClass(extClsInfo);
|
|
||||||
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
|
|
||||||
return shortName;
|
|
||||||
}
|
|
||||||
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
|
|
||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
// ignore classes from default package
|
// ignore classes from default package
|
||||||
@@ -551,6 +631,26 @@ public class ClassGen {
|
|||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String expandInnerClassName(ClassInfo useCls, ClassInfo extClsInfo) {
|
||||||
|
List<ClassInfo> clsList = new ArrayList<>();
|
||||||
|
clsList.add(extClsInfo);
|
||||||
|
ClassInfo parentCls = extClsInfo.getParentClass();
|
||||||
|
boolean addImport = true;
|
||||||
|
while (parentCls != null) {
|
||||||
|
if (parentCls == useCls || isClassInnerFor(useCls, parentCls)) {
|
||||||
|
addImport = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
clsList.add(parentCls);
|
||||||
|
parentCls = parentCls.getParentClass();
|
||||||
|
}
|
||||||
|
Collections.reverse(clsList);
|
||||||
|
if (addImport) {
|
||||||
|
addImport(clsList.get(0));
|
||||||
|
}
|
||||||
|
return Utils.listToString(clsList, ".", ClassInfo::getAliasShortName);
|
||||||
|
}
|
||||||
|
|
||||||
private void addImport(ClassInfo classInfo) {
|
private void addImport(ClassInfo classInfo) {
|
||||||
if (parentGen != null) {
|
if (parentGen != null) {
|
||||||
parentGen.addImport(classInfo);
|
parentGen.addImport(classInfo);
|
||||||
@@ -580,12 +680,12 @@ public class ClassGen {
|
|||||||
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
|
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
|
||||||
if (inner.isInner()) {
|
if (inner.isInner()) {
|
||||||
ClassInfo p = inner.getParentClass();
|
ClassInfo p = inner.getParentClass();
|
||||||
return p.equals(parent) || isClassInnerFor(p, parent);
|
return Objects.equals(p, parent) || isClassInnerFor(p, parent);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) {
|
private static boolean searchCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
|
||||||
if (useCls == null) {
|
if (useCls == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -593,7 +693,7 @@ public class ClassGen {
|
|||||||
if (useCls.getAliasShortName().equals(shortName)) {
|
if (useCls.getAliasShortName().equals(shortName)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
ClassNode classNode = dex.resolveClass(useCls);
|
ClassNode classNode = root.resolveClass(useCls);
|
||||||
if (classNode != null) {
|
if (classNode != null) {
|
||||||
for (ClassNode inner : classNode.getInnerClasses()) {
|
for (ClassNode inner : classNode.getInnerClasses()) {
|
||||||
if (inner.getShortName().equals(shortName)
|
if (inner.getShortName().equals(shortName)
|
||||||
@@ -602,16 +702,50 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return searchCollision(dex, useCls.getParentClass(), searchCls);
|
return searchCollision(root, useCls.getParentClass(), searchCls);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
|
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(ICodeWriter code, ClassNode cls) {
|
||||||
|
List<ClassNode> deps = cls.getDependencies();
|
||||||
|
code.startLine("// deps - ").add(Integer.toString(deps.size()));
|
||||||
|
for (ClassNode depCls : deps) {
|
||||||
|
code.startLine("// ").add(depCls.getClassInfo().getFullName());
|
||||||
|
}
|
||||||
|
List<ClassNode> useIn = cls.getUseIn();
|
||||||
|
code.startLine("// use in - ").add(Integer.toString(useIn.size()));
|
||||||
|
for (ClassNode useCls : useIn) {
|
||||||
|
code.startLine("// ").add(useCls.getClassInfo().getFullName());
|
||||||
|
}
|
||||||
|
List<MethodNode> useInMths = cls.getUseInMth();
|
||||||
|
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
||||||
|
for (MethodNode useMth : useInMths) {
|
||||||
|
code.startLine("// ").add(useMth.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void addMthUsageInfo(ICodeWriter code, MethodNode mth) {
|
||||||
|
List<MethodNode> useInMths = mth.getUseIn();
|
||||||
|
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
||||||
|
for (MethodNode useMth : useInMths) {
|
||||||
|
code.startLine("// ").add(useMth.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addFieldUsageInfo(ICodeWriter code, FieldNode fieldNode) {
|
||||||
|
List<MethodNode> useInMths = fieldNode.getUseIn();
|
||||||
|
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
||||||
|
for (MethodNode useMth : useInMths) {
|
||||||
|
code.startLine("// ").add(useMth.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ClassGen getParentGen() {
|
public ClassGen getParentGen() {
|
||||||
return parentGen == null ? this : parentGen;
|
return parentGen == null ? this : parentGen;
|
||||||
}
|
}
|
||||||
@@ -623,4 +757,21 @@ public class ClassGen {
|
|||||||
public boolean isFallbackMode() {
|
public boolean isFallbackMode() {
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBodyGenStarted() {
|
||||||
|
return bodyGenStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBodyGenStarted(boolean bodyGenStarted) {
|
||||||
|
this.bodyGenStarted = bodyGenStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public NameGen getOuterNameGen() {
|
||||||
|
return outerNameGen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOuterNameGen(@NotNull NameGen outerNameGen) {
|
||||||
|
this.outerNameGen = outerNameGen;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package jadx.core.codegen;
|
|||||||
|
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.impl.SimpleCodeInfo;
|
||||||
import jadx.core.codegen.json.JsonCodeGen;
|
import jadx.core.codegen.json.JsonCodeGen;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
@@ -10,33 +12,32 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
|||||||
|
|
||||||
public class CodeGen {
|
public class CodeGen {
|
||||||
|
|
||||||
public static void generate(ClassNode cls) {
|
public static ICodeInfo generate(ClassNode cls) {
|
||||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||||
cls.setCode(CodeWriter.EMPTY);
|
return ICodeInfo.EMPTY;
|
||||||
} else {
|
}
|
||||||
JadxArgs args = cls.root().getArgs();
|
JadxArgs args = cls.root().getArgs();
|
||||||
switch (args.getOutputFormat()) {
|
switch (args.getOutputFormat()) {
|
||||||
case JAVA:
|
case JAVA:
|
||||||
generateJavaCode(cls, args);
|
return generateJavaCode(cls, args);
|
||||||
break;
|
|
||||||
|
|
||||||
case JSON:
|
case JSON:
|
||||||
generateJson(cls);
|
return generateJson(cls);
|
||||||
break;
|
|
||||||
}
|
default:
|
||||||
|
throw new JadxRuntimeException("Unknown output format");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void generateJavaCode(ClassNode cls, JadxArgs args) {
|
private static ICodeInfo generateJavaCode(ClassNode cls, JadxArgs args) {
|
||||||
ClassGen clsGen = new ClassGen(cls, args);
|
ClassGen clsGen = new ClassGen(cls, args);
|
||||||
CodeWriter code = wrapCodeGen(cls, clsGen::makeClass);
|
return wrapCodeGen(cls, clsGen::makeClass);
|
||||||
cls.setCode(code);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void generateJson(ClassNode cls) {
|
private static ICodeInfo generateJson(ClassNode cls) {
|
||||||
JsonCodeGen codeGen = new JsonCodeGen(cls);
|
JsonCodeGen codeGen = new JsonCodeGen(cls);
|
||||||
String clsJson = wrapCodeGen(cls, codeGen::process);
|
String clsJson = wrapCodeGen(cls, codeGen::process);
|
||||||
cls.setCode(new CodeWriter(clsJson));
|
return new SimpleCodeInfo(clsJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <R> R wrapCodeGen(ClassNode cls, Callable<R> codeGenFunc) {
|
private static <R> R wrapCodeGen(ClassNode cls, Callable<R> codeGenFunc) {
|
||||||
|
|||||||
@@ -1,330 +0,0 @@
|
|||||||
package jadx.core.codegen;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
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.core.dex.attributes.nodes.LineAttrNode;
|
|
||||||
import jadx.core.utils.StringUtils;
|
|
||||||
import jadx.core.utils.files.FileUtils;
|
|
||||||
import jadx.core.utils.files.ZipSecurity;
|
|
||||||
|
|
||||||
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 = " ";
|
|
||||||
|
|
||||||
public static final CodeWriter EMPTY = new CodeWriter().finish();
|
|
||||||
|
|
||||||
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(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create filled instance (just string wrapper)
|
|
||||||
public CodeWriter(String code) {
|
|
||||||
this.buf = null;
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
StringBuilder s = new StringBuilder(curIndent * INDENT_STR.length());
|
|
||||||
for (int i = 0; i < curIndent; i++) {
|
|
||||||
s.append(INDENT_STR);
|
|
||||||
}
|
|
||||||
this.indentStr = s.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Map<CodePosition, Object> getAnnotations() {
|
|
||||||
return annotations;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Map<Integer, Integer> getLineMapping() {
|
|
||||||
return lineMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CodeWriter finish() {
|
|
||||||
removeFirstEmptyLine();
|
|
||||||
buf.trimToSize();
|
|
||||||
code = buf.toString();
|
|
||||||
buf = null;
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeFirstEmptyLine() {
|
|
||||||
int len = NL.length();
|
|
||||||
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
|
|
||||||
buf.delete(0, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int bufLength() {
|
|
||||||
return buf.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCodeStr() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return buf == null ? code : buf.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save(File dir, String subDir, String fileName) {
|
|
||||||
if (!ZipSecurity.isValidZipEntryName(subDir) || !ZipSecurity.isValidZipEntryName(fileName)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
save(dir, new File(subDir, fileName).getPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save(File dir, String fileName) {
|
|
||||||
if (!ZipSecurity.isValidZipEntryName(fileName)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
save(new File(dir, fileName));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save(File file) {
|
|
||||||
if (code == null) {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
File outFile = FileUtils.prepareFile(file);
|
|
||||||
try (PrintWriter out = new PrintWriter(outFile, "UTF-8")) {
|
|
||||||
out.println(code);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Save file error", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
@@ -16,7 +17,6 @@ import jadx.core.dex.nodes.InsnNode;
|
|||||||
import jadx.core.dex.regions.conditions.Compare;
|
import jadx.core.dex.regions.conditions.Compare;
|
||||||
import jadx.core.dex.regions.conditions.IfCondition;
|
import jadx.core.dex.regions.conditions.IfCondition;
|
||||||
import jadx.core.dex.regions.conditions.IfCondition.Mode;
|
import jadx.core.dex.regions.conditions.IfCondition.Mode;
|
||||||
import jadx.core.utils.ErrorsCounter;
|
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
@@ -42,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 {
|
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:
|
||||||
@@ -76,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('(');
|
||||||
@@ -87,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('(');
|
||||||
@@ -98,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();
|
||||||
@@ -123,7 +123,7 @@ public class ConditionGen extends InsnGen {
|
|||||||
wrap(code, firstArg);
|
wrap(code, firstArg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ErrorsCounter.methodWarn(mth, "Unsupported boolean condition " + op.getSymbol());
|
mth.addWarn("Unsupported boolean condition " + op.getSymbol());
|
||||||
}
|
}
|
||||||
|
|
||||||
addArg(code, firstArg, isArgWrapNeeded(firstArg));
|
addArg(code, firstArg, isArgWrapNeeded(firstArg));
|
||||||
@@ -131,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());
|
||||||
@@ -139,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()) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package jadx.core.codegen;
|
package jadx.core.codegen;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -10,33 +9,40 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.core.Consts;
|
import jadx.api.CommentsLevel;
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.data.annotations.InsnCodeOffset;
|
||||||
|
import jadx.api.data.annotations.VarDeclareRef;
|
||||||
|
import jadx.api.data.annotations.VarRef;
|
||||||
|
import jadx.api.plugins.input.data.MethodHandleType;
|
||||||
import jadx.core.deobf.NameMapper;
|
import jadx.core.deobf.NameMapper;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.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.LoopLabelAttr;
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
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;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
import jadx.core.dex.instructions.ArithNode;
|
import jadx.core.dex.instructions.ArithNode;
|
||||||
import jadx.core.dex.instructions.ArithOp;
|
import jadx.core.dex.instructions.ArithOp;
|
||||||
|
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||||
import jadx.core.dex.instructions.ConstClassNode;
|
import jadx.core.dex.instructions.ConstClassNode;
|
||||||
import jadx.core.dex.instructions.ConstStringNode;
|
import jadx.core.dex.instructions.ConstStringNode;
|
||||||
import jadx.core.dex.instructions.FillArrayNode;
|
import jadx.core.dex.instructions.FillArrayInsn;
|
||||||
import jadx.core.dex.instructions.FilledNewArrayNode;
|
import jadx.core.dex.instructions.FilledNewArrayNode;
|
||||||
import jadx.core.dex.instructions.GotoNode;
|
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;
|
||||||
import jadx.core.dex.instructions.SwitchNode;
|
import jadx.core.dex.instructions.SwitchInsn;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.CodeVar;
|
import jadx.core.dex.instructions.args.CodeVar;
|
||||||
import jadx.core.dex.instructions.args.FieldArg;
|
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||||
import jadx.core.dex.instructions.args.LiteralArg;
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
@@ -50,6 +56,7 @@ import jadx.core.dex.nodes.FieldNode;
|
|||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.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;
|
||||||
@@ -63,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,
|
||||||
@@ -76,48 +82,58 @@ 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(VarRef.get(mth, reg));
|
||||||
|
}
|
||||||
|
code.add(mgen.getNameGen().useArg(reg));
|
||||||
} else if (arg.isLiteral()) {
|
} else if (arg.isLiteral()) {
|
||||||
code.add(lit((LiteralArg) arg));
|
code.add(lit((LiteralArg) arg));
|
||||||
} else if (arg.isInsnWrap()) {
|
} else if (arg.isInsnWrap()) {
|
||||||
Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
|
addWrappedArg(code, (InsnWrapArg) arg, flags);
|
||||||
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
|
|
||||||
} else if (arg.isNamed()) {
|
} else if (arg.isNamed()) {
|
||||||
code.add(((Named) arg).getName());
|
code.add(((Named) arg).getName());
|
||||||
} else if (arg.isField()) {
|
|
||||||
FieldArg f = (FieldArg) arg;
|
|
||||||
if (f.isStatic()) {
|
|
||||||
staticField(code, f.getField());
|
|
||||||
} else {
|
|
||||||
instanceField(code, f.getField(), f.getInstanceArg());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
throw new CodegenException("Unknown arg type " + arg);
|
throw new CodegenException("Unknown arg type " + arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
|
private void addWrappedArg(ICodeWriter code, InsnWrapArg arg, Set<Flags> flags) throws CodegenException {
|
||||||
|
InsnNode wrapInsn = arg.getWrapInsn();
|
||||||
|
if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
|
||||||
|
code.add('(');
|
||||||
|
makeInsn(wrapInsn, code, Flags.INLINE);
|
||||||
|
code.add(')');
|
||||||
|
} else {
|
||||||
|
makeInsnBody(code, wrapInsn, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
@@ -126,26 +142,29 @@ 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(' ');
|
||||||
|
if (code.isMetadataSupported()) {
|
||||||
|
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
|
||||||
|
}
|
||||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String lit(LiteralArg arg) {
|
private String lit(LiteralArg arg) {
|
||||||
return TypeGen.literalToString(arg.getLiteral(), arg.getType(), 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.dex().root().deepResolveField(field);
|
FieldNode fieldNode = pCls.root().deepResolveField(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) {
|
||||||
@@ -172,17 +191,18 @@ 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
|
||||||
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
||||||
if (!fieldFromThisClass) {
|
if (!fieldFromThisClass || !clsGen.isBodyGenStarted()) {
|
||||||
// Android specific resources class handler
|
// Android specific resources class handler
|
||||||
if (!handleAppResField(code, clsGen, declClass)) {
|
if (!handleAppResField(code, clsGen, declClass)) {
|
||||||
clsGen.useClass(code, declClass);
|
clsGen.useClass(code, declClass);
|
||||||
}
|
}
|
||||||
code.add('.');
|
code.add('.');
|
||||||
}
|
}
|
||||||
FieldNode fieldNode = clsGen.getClassNode().dex().root().deepResolveField(field);
|
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field);
|
||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
code.attachAnnotation(fieldNode);
|
code.attachAnnotation(fieldNode);
|
||||||
}
|
}
|
||||||
@@ -193,23 +213,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,21 +237,25 @@ 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) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
|
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
|
||||||
makeInsnBody(code, insn, flag == Flags.BODY_ONLY ? BODY_ONLY_FLAG : BODY_ONLY_NOWRAP_FLAGS);
|
makeInsnBody(code, insn, flag == Flags.BODY_ONLY ? BODY_ONLY_FLAG : BODY_ONLY_NOWRAP_FLAGS);
|
||||||
} 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)) {
|
||||||
|
code.add("// ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (insn.getResult() != null) {
|
RegisterArg resArg = insn.getResult();
|
||||||
SSAVar var = insn.getResult().getSVar();
|
if (resArg != null) {
|
||||||
if ((var == null || var.getUseCount() != 0 || insn.getType() != InsnType.CONSTRUCTOR)
|
SSAVar var = resArg.getSVar();
|
||||||
&& !insn.contains(AFlag.ARITH_ONEARG)) {
|
if (var == null || var.getUseCount() != 0 || insn.getType() != InsnType.CONSTRUCTOR) {
|
||||||
assignVar(code, insn);
|
assignVar(code, insn);
|
||||||
code.add(" = ");
|
code.add(" = ");
|
||||||
}
|
}
|
||||||
@@ -239,6 +263,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) {
|
||||||
@@ -246,11 +271,11 @@ 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();
|
||||||
code.add(mth.dex().root().getStringUtils().unescapeString(str));
|
code.add(mth.root().getStringUtils().unescapeString(str));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CONST_CLASS:
|
case CONST_CLASS:
|
||||||
@@ -361,11 +386,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;
|
||||||
@@ -381,7 +410,7 @@ public class InsnGen {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case FILL_ARRAY:
|
case FILL_ARRAY:
|
||||||
FillArrayNode arrayNode = (FillArrayNode) insn;
|
FillArrayInsn arrayNode = (FillArrayInsn) insn;
|
||||||
if (fallback) {
|
if (fallback) {
|
||||||
String arrStr = arrayNode.dataToString();
|
String arrStr = arrayNode.dataToString();
|
||||||
addArg(code, insn.getArg(0));
|
addArg(code, insn.getArg(0));
|
||||||
@@ -456,7 +485,9 @@ public class InsnGen {
|
|||||||
case MONITOR_EXIT:
|
case MONITOR_EXIT:
|
||||||
if (isFallback()) {
|
if (isFallback()) {
|
||||||
code.add("monitor-exit(");
|
code.add("monitor-exit(");
|
||||||
addArg(code, insn.getArg(0));
|
if (insn.getArgsCount() == 1) {
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
}
|
||||||
code.add(')');
|
code.add(')');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -466,7 +497,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 */
|
||||||
@@ -493,15 +524,16 @@ public class InsnGen {
|
|||||||
|
|
||||||
case SWITCH:
|
case SWITCH:
|
||||||
fallbackOnlyInsn(insn);
|
fallbackOnlyInsn(insn);
|
||||||
SwitchNode sw = (SwitchNode) insn;
|
SwitchInsn sw = (SwitchInsn) insn;
|
||||||
code.add("switch(");
|
code.add("switch(");
|
||||||
addArg(code, insn.getArg(0));
|
addArg(code, insn.getArg(0));
|
||||||
code.add(") {");
|
code.add(") {");
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
for (int i = 0; i < sw.getCasesCount(); i++) {
|
int[] keys = sw.getKeys();
|
||||||
String key = sw.getKeys()[i].toString();
|
int[] targets = sw.getTargets();
|
||||||
code.startLine("case ").add(key).add(": goto ");
|
for (int i = 0; i < keys.length; i++) {
|
||||||
code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
|
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
|
||||||
|
code.add(MethodGen.getLabelName(targets[i])).add(';');
|
||||||
}
|
}
|
||||||
code.startLine("default: goto ");
|
code.startLine("default: goto ");
|
||||||
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
|
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
|
||||||
@@ -525,6 +557,32 @@ public class InsnGen {
|
|||||||
code.add(')');
|
code.add(')');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MOVE_RESULT:
|
||||||
|
fallbackOnlyInsn(insn);
|
||||||
|
code.add("move-result");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FILL_ARRAY_DATA:
|
||||||
|
fallbackOnlyInsn(insn);
|
||||||
|
code.add("fill-array " + insn);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SWITCH_DATA:
|
||||||
|
fallbackOnlyInsn(insn);
|
||||||
|
code.add(insn.toString());
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
@@ -534,11 +592,21 @@ 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, FillArrayNode 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();
|
||||||
List<LiteralArg> args = arrayNode.getLiteralArgs(arrayNode.getElementType());
|
|
||||||
InsnArg arrArg = arrayNode.getArg(0);
|
InsnArg arrArg = arrayNode.getArg(0);
|
||||||
|
ArgType arrayType = arrArg.getType();
|
||||||
|
ArgType elemType;
|
||||||
|
if (arrayType.isTypeKnown() && arrayType.isArray()) {
|
||||||
|
elemType = arrayType.getArrayElement();
|
||||||
|
} else {
|
||||||
|
ArgType elementType = arrayNode.getElementType(); // unknown type
|
||||||
|
elemType = elementType.selectFirst();
|
||||||
|
}
|
||||||
|
List<LiteralArg> args = arrayNode.getLiteralArgs(elemType);
|
||||||
int len = args.size();
|
int len = args.size();
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
@@ -550,7 +618,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('(');
|
||||||
@@ -572,60 +640,76 @@ 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)
|
private void makeConstructor(ConstructorInsn insn, ICodeWriter code) throws CodegenException {
|
||||||
throws CodegenException {
|
ClassNode cls = mth.root().resolveClass(insn.getClassType());
|
||||||
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
|
||||||
if (cls != null && cls.isAnonymous() && !fallback) {
|
if (cls != null && cls.isAnonymous() && !fallback) {
|
||||||
|
cls.ensureProcessed();
|
||||||
inlineAnonymousConstructor(code, cls, insn);
|
inlineAnonymousConstructor(code, cls, insn);
|
||||||
|
mth.getParentClass().addInlinedClass(cls);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (insn.isSelf()) {
|
if (insn.isSelf()) {
|
||||||
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());
|
||||||
if (insn.isSuper()) {
|
if (insn.isSuper()) {
|
||||||
|
code.attachAnnotation(callMth);
|
||||||
code.add("super");
|
code.add("super");
|
||||||
} else if (insn.isThis()) {
|
} else if (insn.isThis()) {
|
||||||
|
code.attachAnnotation(callMth);
|
||||||
code.add("this");
|
code.add("this");
|
||||||
} else {
|
} else {
|
||||||
code.add("new ");
|
code.add("new ");
|
||||||
useClass(code, insn.getClassType());
|
if (callMth == null || callMth.contains(AFlag.DONT_GENERATE)) {
|
||||||
ArgType argType = insn.getResult().getSVar().getCodeVar().getType();
|
// use class reference if constructor method is missing (default constructor)
|
||||||
if (argType.isGeneric()) {
|
code.attachAnnotation(mth.root().resolveClass(insn.getCallMth().getDeclClass()));
|
||||||
|
} else {
|
||||||
|
code.attachAnnotation(callMth);
|
||||||
|
}
|
||||||
|
mgen.getClassGen().addClsName(code, insn.getClassType());
|
||||||
|
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
|
||||||
|
if (genericInfoAttr != null) {
|
||||||
code.add('<');
|
code.add('<');
|
||||||
if (insn.contains(AFlag.EXPLICIT_GENERICS)) {
|
if (genericInfoAttr.isExplicit()) {
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
for (ArgType type : argType.getGenericTypes()) {
|
for (ArgType type : genericInfoAttr.getGenericTypes()) {
|
||||||
if (!first) {
|
if (!first) {
|
||||||
code.add(',');
|
code.add(',');
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
}
|
}
|
||||||
mgen.getClassGen().useType(code, type);
|
mgen.getClassGen().useType(code, type);
|
||||||
first = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
code.add('>');
|
code.add('>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MethodNode callMth = mth.dex().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(AFlag.ANONYMOUS_CLASS);
|
||||||
cls.remove(AFlag.DONT_GENERATE);
|
cls.remove(AFlag.DONT_GENERATE);
|
||||||
@@ -655,23 +739,25 @@ public class InsnGen {
|
|||||||
} else {
|
} else {
|
||||||
useClass(code, parent);
|
useClass(code, parent);
|
||||||
}
|
}
|
||||||
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
|
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
||||||
generateMethodArguments(code, insn, 0, callMth);
|
generateMethodArguments(code, insn, 0, callMth);
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code);
|
|
||||||
|
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 {
|
||||||
MethodInfo callMth = insn.getCallMth();
|
InvokeType type = insn.getInvokeType();
|
||||||
|
if (type == InvokeType.CUSTOM) {
|
||||||
// inline method
|
makeInvokeLambda(code, (InvokeCustomNode) insn);
|
||||||
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
|
|
||||||
if (callMthNode != null && inlineMethod(callMthNode, insn, code)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
MethodInfo callMth = insn.getCallMth();
|
||||||
|
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
|
||||||
|
|
||||||
int k = 0;
|
int k = 0;
|
||||||
InvokeType type = insn.getInvokeType();
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case DIRECT:
|
case DIRECT:
|
||||||
case VIRTUAL:
|
case VIRTUAL:
|
||||||
@@ -713,8 +799,123 @@ public class InsnGen {
|
|||||||
generateMethodArguments(code, insn, k, callMthNode);
|
generateMethodArguments(code, insn, k, callMthNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
code.add(nameGen.assignArg(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);
|
||||||
|
callArgs.get(i).setName(extArg.getName());
|
||||||
|
}
|
||||||
|
code.add(" -> {");
|
||||||
|
code.incIndent();
|
||||||
|
callMthGen.addInstructions(code);
|
||||||
|
|
||||||
|
code.decIndent();
|
||||||
|
code.startLine('}');
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private ClassInfo getClassForSuperCall(CodeWriter code, MethodInfo callMth) {
|
private ClassInfo getClassForSuperCall(ICodeWriter code, MethodInfo callMth) {
|
||||||
ClassNode useCls = mth.getParentClass();
|
ClassNode useCls = mth.getParentClass();
|
||||||
ClassInfo insnCls = useCls.getClassInfo();
|
ClassInfo insnCls = useCls.getClassInfo();
|
||||||
ClassInfo declClass = callMth.getDeclClass();
|
ClassInfo declClass = callMth.getDeclClass();
|
||||||
@@ -743,31 +944,31 @@ public class InsnGen {
|
|||||||
return useCls.getParentClass().getClassInfo();
|
return useCls.getParentClass().getClassInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
|
void generateMethodArguments(ICodeWriter code, BaseInvokeNode insn, int startArgNum,
|
||||||
@Nullable MethodNode callMth) throws CodegenException {
|
@Nullable MethodNode mthNode) throws CodegenException {
|
||||||
int k = startArgNum;
|
int k = startArgNum;
|
||||||
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
|
if (mthNode != null && mthNode.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||||
k++;
|
k++;
|
||||||
}
|
}
|
||||||
int argsCount = insn.getArgsCount();
|
int argsCount = insn.getArgsCount();
|
||||||
code.add('(');
|
code.add('(');
|
||||||
boolean firstArg = true;
|
boolean firstArg = true;
|
||||||
if (k < argsCount) {
|
if (k < argsCount) {
|
||||||
boolean overloaded = callMth != null && callMth.isArgsOverload();
|
|
||||||
for (int i = k; i < argsCount; i++) {
|
for (int i = k; i < argsCount; i++) {
|
||||||
InsnArg arg = insn.getArg(i);
|
InsnArg arg = insn.getArg(i);
|
||||||
if (arg.contains(AFlag.SKIP_ARG)) {
|
if (arg.contains(AFlag.SKIP_ARG)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
RegisterArg callArg = getCallMthArg(callMth, i - startArgNum);
|
int argOrigPos = i - startArgNum;
|
||||||
if (callArg != null && callArg.contains(AFlag.SKIP_ARG)) {
|
if (SkipMethodArgsAttr.isSkip(mthNode, argOrigPos)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!firstArg) {
|
if (!firstArg) {
|
||||||
code.add(", ");
|
code.add(", ");
|
||||||
|
} else {
|
||||||
|
firstArg = false;
|
||||||
}
|
}
|
||||||
boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum);
|
if (i == argsCount - 1 && processVarArg(code, insn, arg)) {
|
||||||
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
addArg(code, arg, false);
|
addArg(code, arg, false);
|
||||||
@@ -777,128 +978,32 @@ public class InsnGen {
|
|||||||
code.add(')');
|
code.add(')');
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RegisterArg getCallMthArg(@Nullable MethodNode callMth, int num) {
|
|
||||||
if (callMth == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
List<RegisterArg> args = callMth.getArguments(false);
|
|
||||||
if (args != null && num < args.size()) {
|
|
||||||
return args.get(num);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add additional cast for overloaded method argument.
|
|
||||||
*/
|
|
||||||
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
|
|
||||||
ArgType origType;
|
|
||||||
List<RegisterArg> arguments = callMth.getArguments(false);
|
|
||||||
if (arguments == null || arguments.isEmpty()) {
|
|
||||||
mth.addComment("JADX INFO: used method not loaded: " + callMth + ", types can be incorrect");
|
|
||||||
origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
|
|
||||||
} else {
|
|
||||||
origType = arguments.get(origPos).getInitType();
|
|
||||||
if (origType.isGenericType() && !callMth.getParentClass().equals(mth.getParentClass())) {
|
|
||||||
// cancel cast
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ArgType argType = arg.getType();
|
|
||||||
if (argType.equals(origType)
|
|
||||||
// null cast to object
|
|
||||||
&& (!arg.isLiteral() || ((LiteralArg) arg).getLiteral() != 0
|
|
||||||
|| (!argType.isArray() && !argType.isObject()))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (origType.isGeneric()) {
|
|
||||||
if (argType.isObject()) {
|
|
||||||
if (!argType.isGeneric() && arg.isInsnWrap()) {
|
|
||||||
((InsnWrapArg) arg).getWrapInsn().getResult().setType(
|
|
||||||
ArgType.generic(argType.getObject(), origType.getGenericTypes()));
|
|
||||||
}
|
|
||||||
if (origType.getObject().equals(argType.getObject())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (arg.isInsnWrap()) {
|
|
||||||
((InsnWrapArg) arg).getWrapInsn().add(AFlag.EXPLICIT_GENERICS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
code.add('(');
|
|
||||||
useType(code, origType);
|
|
||||||
code.add(") ");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expand varArgs from filled array.
|
* Expand varArgs from filled array.
|
||||||
*/
|
*/
|
||||||
private boolean processVarArg(CodeWriter code, MethodNode callMth, InsnArg lastArg) throws CodegenException {
|
private boolean processVarArg(ICodeWriter code, BaseInvokeNode invokeInsn, InsnArg lastArg) throws CodegenException {
|
||||||
if (callMth == null || !callMth.getAccessFlags().isVarArgs()) {
|
if (!invokeInsn.contains(AFlag.VARARG_CALL)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
|
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
|
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
|
||||||
if (insn.getType() == InsnType.FILLED_NEW_ARRAY) {
|
if (insn.getType() != InsnType.FILLED_NEW_ARRAY) {
|
||||||
int count = insn.getArgsCount();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
InsnArg elemArg = insn.getArg(i);
|
|
||||||
addArg(code, elemArg, false);
|
|
||||||
if (i < count - 1) {
|
|
||||||
code.add(", ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
|
|
||||||
MethodInlineAttr mia = callMthNode.get(AType.METHOD_INLINE);
|
|
||||||
if (mia == null) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
InsnNode inl = mia.getInsn();
|
int count = insn.getArgsCount();
|
||||||
if (Consts.DEBUG) {
|
for (int i = 0; i < count; i++) {
|
||||||
code.add("/* inline method: ").add(callMthNode.toString()).add("*/").startLine();
|
InsnArg elemArg = insn.getArg(i);
|
||||||
}
|
addArg(code, elemArg, false);
|
||||||
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
|
if (i < count - 1) {
|
||||||
makeInsn(inl, code, Flags.BODY_ONLY);
|
code.add(", ");
|
||||||
} else {
|
|
||||||
// remap args
|
|
||||||
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
|
|
||||||
List<RegisterArg> callArgs = callMthNode.getArguments(true);
|
|
||||||
for (int i = 0; i < callArgs.size(); i++) {
|
|
||||||
InsnArg arg = insn.getArg(i);
|
|
||||||
RegisterArg callArg = callArgs.get(i);
|
|
||||||
regs[callArg.getRegNum()] = arg;
|
|
||||||
}
|
}
|
||||||
// replace args
|
|
||||||
InsnNode inlCopy = inl.copy();
|
|
||||||
List<RegisterArg> inlArgs = new ArrayList<>();
|
|
||||||
inlCopy.getRegisterArgs(inlArgs);
|
|
||||||
for (RegisterArg r : inlArgs) {
|
|
||||||
int regNum = r.getRegNum();
|
|
||||||
if (regNum >= regs.length) {
|
|
||||||
LOG.warn("Unknown register number {} in method call: {} from {}", r, callMthNode, mth);
|
|
||||||
} else {
|
|
||||||
InsnArg repl = regs[regNum];
|
|
||||||
if (repl == null) {
|
|
||||||
LOG.warn("Not passed register {} in method call: {} from {}", r, callMthNode, mth);
|
|
||||||
} else {
|
|
||||||
inlCopy.replaceArg(r, repl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
makeInsn(inlCopy, code, Flags.BODY_ONLY);
|
|
||||||
}
|
}
|
||||||
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('(');
|
||||||
@@ -906,7 +1011,7 @@ public class InsnGen {
|
|||||||
InsnArg first = insn.getArg(0);
|
InsnArg first = insn.getArg(0);
|
||||||
InsnArg second = insn.getArg(1);
|
InsnArg second = insn.getArg(1);
|
||||||
ConditionGen condGen = new ConditionGen(this);
|
ConditionGen condGen = new ConditionGen(this);
|
||||||
if (first.equals(LiteralArg.TRUE) && second.equals(LiteralArg.FALSE)) {
|
if (first.isTrue() && second.isFalse()) {
|
||||||
condGen.add(code, insn.getCondition());
|
condGen.add(code, insn.getCondition());
|
||||||
} else {
|
} else {
|
||||||
condGen.wrap(code, insn.getCondition());
|
condGen.wrap(code, insn.getCondition());
|
||||||
@@ -921,7 +1026,7 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCastIfNeeded(CodeWriter code, InsnArg first, InsnArg second) {
|
private void addCastIfNeeded(ICodeWriter code, InsnArg first, InsnArg second) {
|
||||||
if (first.isLiteral() && second.isLiteral()) {
|
if (first.isLiteral() && second.isLiteral()) {
|
||||||
if (first.getType() == ArgType.BYTE) {
|
if (first.getType() == ArgType.BYTE) {
|
||||||
long lit1 = ((LiteralArg) first).getLiteral();
|
long lit1 = ((LiteralArg) first).getLiteral();
|
||||||
@@ -948,7 +1053,7 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeArith(ArithNode insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
private void makeArith(ArithNode insn, ICodeWriter 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;
|
||||||
@@ -968,21 +1073,24 @@ 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 arg = insn.getArg(1);
|
InsnArg arg = insn.getArg(1);
|
||||||
|
|
||||||
// "++" or "--"
|
// "++" or "--"
|
||||||
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
|
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
|
||||||
LiteralArg lit = (LiteralArg) arg;
|
LiteralArg lit = (LiteralArg) arg;
|
||||||
if (lit.isInteger() && lit.getLiteral() == 1) {
|
if (lit.getLiteral() == 1 && lit.isInteger()) {
|
||||||
assignVar(code, insn);
|
addArg(code, resArg, false);
|
||||||
String opSymbol = op.getSymbol();
|
String opSymbol = op.getSymbol();
|
||||||
code.add(opSymbol).add(opSymbol);
|
code.add(opSymbol).add(opSymbol);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// +=, -= ...
|
|
||||||
assignVar(code, insn);
|
// +=, -=, ...
|
||||||
|
addArg(code, resArg, false);
|
||||||
code.add(' ').add(op.getSymbol()).add("= ");
|
code.add(' ').add(op.getSymbol()).add("= ");
|
||||||
addArg(code, arg, false);
|
addArg(code, arg, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,31 @@
|
|||||||
package jadx.core.codegen;
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.android.dx.rop.code.AccessFlags;
|
import jadx.api.CommentsLevel;
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.data.annotations.InsnCodeOffset;
|
||||||
|
import jadx.api.data.annotations.VarDeclareRef;
|
||||||
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
|
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
|
import jadx.core.Jadx;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
import jadx.core.dex.attributes.nodes.JadxError;
|
||||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||||
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
|
import jadx.core.dex.instructions.ConstStringNode;
|
||||||
import jadx.core.dex.instructions.IfNode;
|
import jadx.core.dex.instructions.IfNode;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
import jadx.core.dex.instructions.InsnType;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
@@ -24,14 +36,17 @@ import jadx.core.dex.nodes.InsnNode;
|
|||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.trycatch.CatchAttr;
|
import jadx.core.dex.trycatch.CatchAttr;
|
||||||
import jadx.core.dex.visitors.DepthTraversal;
|
import jadx.core.dex.visitors.DepthTraversal;
|
||||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
import jadx.core.utils.CodeGenUtils;
|
import jadx.core.utils.CodeGenUtils;
|
||||||
import jadx.core.utils.InsnUtils;
|
import jadx.core.utils.InsnUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
import jadx.core.utils.exceptions.DecodeException;
|
|
||||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||||
|
|
||||||
|
import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP;
|
||||||
|
import static jadx.core.codegen.MethodGen.FallbackOption.COMMENTED_DUMP;
|
||||||
|
import static jadx.core.codegen.MethodGen.FallbackOption.FALLBACK_MODE;
|
||||||
|
|
||||||
public class MethodGen {
|
public class MethodGen {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
|
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
|
||||||
|
|
||||||
@@ -44,7 +59,7 @@ public class MethodGen {
|
|||||||
this.mth = mth;
|
this.mth = mth;
|
||||||
this.classGen = classGen;
|
this.classGen = classGen;
|
||||||
this.annotationGen = classGen.getAnnotationGen();
|
this.annotationGen = classGen.getAnnotationGen();
|
||||||
this.nameGen = new NameGen(mth, classGen.isFallbackMode());
|
this.nameGen = new NameGen(mth, classGen);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClassGen getClassGen() {
|
public ClassGen getClassGen() {
|
||||||
@@ -59,7 +74,7 @@ public class MethodGen {
|
|||||||
return mth;
|
return mth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addDefinition(CodeWriter code) {
|
public boolean addDefinition(ICodeWriter code) {
|
||||||
if (mth.getMethodInfo().isClassInit()) {
|
if (mth.getMethodInfo().isClassInit()) {
|
||||||
code.attachDefinition(mth);
|
code.attachDefinition(mth);
|
||||||
code.startLine("static");
|
code.startLine("static");
|
||||||
@@ -71,35 +86,42 @@ public class MethodGen {
|
|||||||
code.attachDefinition(mth);
|
code.attachDefinition(mth);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (Consts.DEBUG_USAGE) {
|
||||||
|
ClassGen.addMthUsageInfo(code, mth);
|
||||||
|
}
|
||||||
|
addOverrideAnnotation(code, mth);
|
||||||
annotationGen.addForMethod(code, mth);
|
annotationGen.addForMethod(code, mth);
|
||||||
|
|
||||||
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
|
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
|
||||||
AccessInfo ai = mth.getAccessFlags();
|
AccessInfo ai = mth.getAccessFlags();
|
||||||
// don't add 'abstract' and 'public' to methods in interface
|
// don't add 'abstract' and 'public' to methods in interface
|
||||||
if (clsAccFlags.isInterface()) {
|
if (clsAccFlags.isInterface()) {
|
||||||
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
|
ai = ai.remove(AccessFlags.ABSTRACT);
|
||||||
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
ai = ai.remove(AccessFlags.PUBLIC);
|
||||||
}
|
}
|
||||||
// don't add 'public' for annotations
|
// don't add 'public' for annotations
|
||||||
if (clsAccFlags.isAnnotation()) {
|
if (clsAccFlags.isAnnotation()) {
|
||||||
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
ai = ai.remove(AccessFlags.PUBLIC);
|
||||||
|
}
|
||||||
|
if (mth.getMethodInfo().isConstructor() && mth.getParentClass().isEnum()) {
|
||||||
|
ai = ai.remove(AccessInfo.VISIBILITY_FLAGS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
|
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
|
||||||
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
|
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
|
||||||
}
|
}
|
||||||
CodeGenUtils.addSourceFileInfo(code, mth);
|
if (mth.contains(AFlag.INCONSISTENT_CODE) && mth.checkCommentsLevel(CommentsLevel.ERROR)) {
|
||||||
if (mth.contains(AFlag.INCONSISTENT_CODE)) {
|
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump */");
|
||||||
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
code.startLineWithNum(mth.getSourceLine());
|
code.startLineWithNum(mth.getSourceLine());
|
||||||
code.add(ai.makeString());
|
code.add(ai.makeString(mth.checkCommentsLevel(CommentsLevel.INFO)));
|
||||||
if (Consts.DEBUG) {
|
if (clsAccFlags.isInterface() && !mth.isNoCode() && !mth.getAccessFlags().isStatic()) {
|
||||||
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
|
// add 'default' for method with code in interface
|
||||||
|
code.add("default ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (classGen.addGenericMap(code, mth.getGenericMap(), false)) {
|
if (classGen.addGenericTypeParameters(code, mth.getTypeParameters(), false)) {
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
}
|
}
|
||||||
if (ai.isConstructor()) {
|
if (ai.isConstructor()) {
|
||||||
@@ -113,15 +135,15 @@ public class MethodGen {
|
|||||||
}
|
}
|
||||||
code.add('(');
|
code.add('(');
|
||||||
|
|
||||||
List<RegisterArg> args = mth.getArguments(false);
|
List<RegisterArg> args = mth.getArgRegs();
|
||||||
if (mth.getMethodInfo().isConstructor()
|
if (mth.getMethodInfo().isConstructor()
|
||||||
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
|
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
|
||||||
if (args.size() == 2) {
|
if (args.size() == 2) {
|
||||||
args.clear();
|
args = Collections.emptyList();
|
||||||
} else if (args.size() > 2) {
|
} else if (args.size() > 2) {
|
||||||
args = args.subList(2, args.size());
|
args = args.subList(2, args.size());
|
||||||
} else {
|
} else {
|
||||||
mth.addComment("JADX WARN: Incorrect number of args for enum constructor: " + args.size() + " (expected >= 2)");
|
mth.addWarnComment("Incorrect number of args for enum constructor: " + args.size() + " (expected >= 2)");
|
||||||
}
|
}
|
||||||
} else if (mth.contains(AFlag.SKIP_FIRST_ARG)) {
|
} else if (mth.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||||
args = args.subList(1, args.size());
|
args = args.subList(1, args.size());
|
||||||
@@ -131,19 +153,38 @@ public class MethodGen {
|
|||||||
|
|
||||||
annotationGen.addThrows(mth, code);
|
annotationGen.addThrows(mth, code);
|
||||||
|
|
||||||
// add default value if in annotation class
|
// add default value for annotation class
|
||||||
if (mth.getParentClass().getAccessFlags().isAnnotation()) {
|
if (mth.getParentClass().getAccessFlags().isAnnotation()) {
|
||||||
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
|
EncodedValue def = annotationGen.getAnnotationDefaultValue(mth);
|
||||||
if (def != null) {
|
if (def != null) {
|
||||||
code.add(" default ");
|
code.add(" default ");
|
||||||
annotationGen.encodeValue(code, def);
|
annotationGen.encodeValue(mth.root(), code, def);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMethodArguments(CodeWriter code, List<RegisterArg> args) {
|
private void addOverrideAnnotation(ICodeWriter code, MethodNode mth) {
|
||||||
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
|
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||||
|
if (overrideAttr == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!overrideAttr.isAtBaseMth()) {
|
||||||
|
code.startLine("@Override");
|
||||||
|
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
|
||||||
|
code.add(" // ");
|
||||||
|
code.add(Utils.listToString(overrideAttr.getOverrideList(), ", ",
|
||||||
|
md -> md.getMethodInfo().getDeclClass().getAliasFullName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Consts.DEBUG) {
|
||||||
|
code.startLine("// related by override: ");
|
||||||
|
code.add(Utils.listToString(overrideAttr.getRelatedMthNodes(), ", ", m -> m.getParentClass().getFullName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMethodArguments(ICodeWriter code, List<RegisterArg> args) {
|
||||||
|
MethodParamsAttr paramsAnnotation = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
Iterator<RegisterArg> it = args.iterator();
|
Iterator<RegisterArg> it = args.iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
@@ -151,8 +192,8 @@ public class MethodGen {
|
|||||||
SSAVar ssaVar = mthArg.getSVar();
|
SSAVar ssaVar = mthArg.getSVar();
|
||||||
CodeVar var;
|
CodeVar var;
|
||||||
if (ssaVar == null) {
|
if (ssaVar == null) {
|
||||||
// null for abstract or interface methods
|
// abstract or interface methods
|
||||||
var = CodeVar.fromMthArg(mthArg);
|
var = CodeVar.fromMthArg(mthArg, classGen.isFallbackMode());
|
||||||
} else {
|
} else {
|
||||||
var = ssaVar.getCodeVar();
|
var = ssaVar.getCodeVar();
|
||||||
}
|
}
|
||||||
@@ -165,11 +206,12 @@ public class MethodGen {
|
|||||||
code.add("final ");
|
code.add("final ");
|
||||||
}
|
}
|
||||||
ArgType argType;
|
ArgType argType;
|
||||||
if (var.getType() == ArgType.UNKNOWN) {
|
ArgType varType = var.getType();
|
||||||
|
if (varType == null || varType == ArgType.UNKNOWN) {
|
||||||
// occur on decompilation errors
|
// occur on decompilation errors
|
||||||
argType = mthArg.getInitType();
|
argType = mthArg.getInitType();
|
||||||
} else {
|
} else {
|
||||||
argType = var.getType();
|
argType = varType;
|
||||||
}
|
}
|
||||||
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
||||||
// change last array argument to varargs
|
// change last array argument to varargs
|
||||||
@@ -178,13 +220,16 @@ public class MethodGen {
|
|||||||
classGen.useType(code, elType);
|
classGen.useType(code, elType);
|
||||||
code.add("...");
|
code.add("...");
|
||||||
} else {
|
} else {
|
||||||
mth.addComment("JADX INFO: Last argument in varargs method is not array: " + var);
|
mth.addWarnComment("Last argument in varargs method is not array: " + var);
|
||||||
classGen.useType(code, argType);
|
classGen.useType(code, argType);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
classGen.useType(code, argType);
|
classGen.useType(code, argType);
|
||||||
}
|
}
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
|
if (code.isMetadataSupported() && ssaVar != null) {
|
||||||
|
code.attachDefinition(VarDeclareRef.get(mth, var));
|
||||||
|
}
|
||||||
code.add(nameGen.assignArg(var));
|
code.add(nameGen.assignArg(var));
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
@@ -194,9 +239,9 @@ public class MethodGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addInstructions(CodeWriter code) throws CodegenException {
|
public void addInstructions(ICodeWriter code) throws CodegenException {
|
||||||
if (mth.root().getArgs().isFallbackMode()) {
|
if (mth.root().getArgs().isFallbackMode()) {
|
||||||
addFallbackMethodCode(code);
|
addFallbackMethodCode(code, FALLBACK_MODE);
|
||||||
} else if (classGen.isFallbackMode()) {
|
} else if (classGen.isFallbackMode()) {
|
||||||
dumpInstructions(code);
|
dumpInstructions(code);
|
||||||
} else {
|
} else {
|
||||||
@@ -204,29 +249,30 @@ public class MethodGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addRegionInsns(CodeWriter code) throws CodegenException {
|
public void addRegionInsns(ICodeWriter code) throws CodegenException {
|
||||||
try {
|
try {
|
||||||
RegionGen regionGen = new RegionGen(this);
|
RegionGen regionGen = new RegionGen(this);
|
||||||
regionGen.makeRegion(code, mth.getRegion());
|
regionGen.makeRegion(code, mth.getRegion());
|
||||||
} catch (StackOverflowError | BootstrapMethodError e) {
|
} catch (StackOverflowError | BootstrapMethodError e) {
|
||||||
mth.addError("Method code generation error", new JadxOverflowException("StackOverflow"));
|
mth.addError("Method code generation error", new JadxOverflowException("StackOverflow"));
|
||||||
classGen.insertDecompilationProblems(code, mth);
|
CodeGenUtils.addErrors(code, mth);
|
||||||
dumpInstructions(code);
|
dumpInstructions(code);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
|
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
mth.addError("Method code generation error", e);
|
mth.addError("Method code generation error", e);
|
||||||
classGen.insertDecompilationProblems(code, mth);
|
CodeGenUtils.addErrors(code, mth);
|
||||||
dumpInstructions(code);
|
dumpInstructions(code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dumpInstructions(CodeWriter code) {
|
public void dumpInstructions(ICodeWriter code) {
|
||||||
code.startLine("/*");
|
if (mth.checkCommentsLevel(CommentsLevel.ERROR)) {
|
||||||
addFallbackMethodCode(code);
|
code.startLine("/*");
|
||||||
code.startLine("*/");
|
addFallbackMethodCode(code, COMMENTED_DUMP);
|
||||||
|
code.startLine("*/");
|
||||||
|
}
|
||||||
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
|
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
|
||||||
.add(mth.getParentClass().getClassInfo().getAliasFullName())
|
.add(mth.getParentClass().getClassInfo().getAliasFullName())
|
||||||
.add('.')
|
.add('.')
|
||||||
@@ -238,17 +284,23 @@ public class MethodGen {
|
|||||||
.add("\");");
|
.add("\");");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addFallbackMethodCode(CodeWriter code) {
|
public void addFallbackMethodCode(ICodeWriter code, FallbackOption fallbackOption) {
|
||||||
if (mth.getInstructions() == null) {
|
if (fallbackOption != FALLBACK_MODE) {
|
||||||
// load original instructions
|
List<JadxError> errors = mth.getAll(AType.JADX_ERROR); // preserve error before unload
|
||||||
try {
|
try {
|
||||||
|
// load original instructions
|
||||||
mth.unload();
|
mth.unload();
|
||||||
mth.load();
|
mth.load();
|
||||||
DepthTraversal.visit(new FallbackModeVisitor(), mth);
|
for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) {
|
||||||
} catch (DecodeException e) {
|
DepthTraversal.visit(visitor, mth);
|
||||||
|
}
|
||||||
|
errors.forEach(err -> mth.addAttr(AType.JADX_ERROR, err));
|
||||||
|
} catch (Exception e) {
|
||||||
LOG.error("Error reload instructions in fallback mode:", e);
|
LOG.error("Error reload instructions in fallback mode:", e);
|
||||||
code.startLine("// Can't load method instructions: " + e.getMessage());
|
code.startLine("// Can't load method instructions: " + e.getMessage());
|
||||||
return;
|
return;
|
||||||
|
} finally {
|
||||||
|
errors.forEach(err -> mth.addAttr(AType.JADX_ERROR, err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InsnNode[] insnArr = mth.getInstructions();
|
InsnNode[] insnArr = mth.getInstructions();
|
||||||
@@ -256,23 +308,45 @@ public class MethodGen {
|
|||||||
code.startLine("// Can't load method instructions.");
|
code.startLine("// Can't load method instructions.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (fallbackOption == COMMENTED_DUMP && mth.getCommentsLevel() != CommentsLevel.DEBUG) {
|
||||||
|
long insnCountEstimate = Stream.of(insnArr)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.filter(insn -> insn.getType() != InsnType.NOP)
|
||||||
|
.count();
|
||||||
|
if (insnCountEstimate > 100) {
|
||||||
|
code.startLine("// Method dump skipped, instructions count: " + insnArr.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
if (mth.getThisArg() != null) {
|
if (mth.getThisArg() != null) {
|
||||||
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
|
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
|
||||||
}
|
}
|
||||||
addFallbackInsns(code, mth, insnArr, true);
|
addFallbackInsns(code, mth, insnArr, fallbackOption);
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
|
public enum FallbackOption {
|
||||||
|
FALLBACK_MODE,
|
||||||
|
BLOCK_DUMP,
|
||||||
|
COMMENTED_DUMP
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addFallbackInsns(ICodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
|
||||||
|
int startIndent = code.getIndent();
|
||||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
||||||
boolean attachInsns = mth.root().getArgs().isJsonOutput();
|
|
||||||
InsnNode prevInsn = null;
|
InsnNode prevInsn = null;
|
||||||
for (InsnNode insn : insnArr) {
|
for (InsnNode insn : insnArr) {
|
||||||
if (insn == null) {
|
if (insn == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (addLabels && needLabel(insn, prevInsn)) {
|
if (insn.contains(AType.JADX_ERROR)) {
|
||||||
|
for (JadxError error : insn.getAll(AType.JADX_ERROR)) {
|
||||||
|
code.startLine("// ").add(error.getError());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
code.startLine(getLabelName(insn.getOffset()) + ':');
|
code.startLine(getLabelName(insn.getOffset()) + ':');
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
@@ -281,10 +355,15 @@ public class MethodGen {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
code.startLine();
|
boolean escapeComment = isCommentEscapeNeeded(insn, option);
|
||||||
if (attachInsns) {
|
if (escapeComment) {
|
||||||
code.attachLineAnnotation(insn);
|
code.decIndent();
|
||||||
|
code.startLine("*/");
|
||||||
|
code.startLine("// ");
|
||||||
|
} else {
|
||||||
|
code.startLineWithNum(insn.getSourceLine());
|
||||||
}
|
}
|
||||||
|
InsnCodeOffset.attach(code, insn);
|
||||||
RegisterArg resArg = insn.getResult();
|
RegisterArg resArg = insn.getResult();
|
||||||
if (resArg != null) {
|
if (resArg != null) {
|
||||||
ArgType varType = resArg.getInitType();
|
ArgType varType = resArg.getInitType();
|
||||||
@@ -293,18 +372,35 @@ public class MethodGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
|
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
|
||||||
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
|
if (escapeComment) {
|
||||||
|
code.startLine("/*");
|
||||||
|
code.incIndent();
|
||||||
|
}
|
||||||
|
|
||||||
|
CatchAttr catchAttr = insn.get(AType.EXC_CATCH);
|
||||||
if (catchAttr != null) {
|
if (catchAttr != null) {
|
||||||
code.add(" // " + catchAttr);
|
code.add(" // " + catchAttr);
|
||||||
}
|
}
|
||||||
} catch (CodegenException e) {
|
CodeGenUtils.addCodeComments(code, mth, insn);
|
||||||
|
} catch (Exception e) {
|
||||||
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
||||||
|
code.setIndent(startIndent);
|
||||||
code.startLine("// error: " + insn);
|
code.startLine("// error: " + insn);
|
||||||
}
|
}
|
||||||
prevInsn = insn;
|
prevInsn = insn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isCommentEscapeNeeded(InsnNode insn, FallbackOption option) {
|
||||||
|
if (option == COMMENTED_DUMP) {
|
||||||
|
if (insn.getType() == InsnType.CONST_STR) {
|
||||||
|
String str = ((ConstStringNode) insn).getString();
|
||||||
|
return str.contains("*/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean needLabel(InsnNode insn, InsnNode prevInsn) {
|
private static boolean needLabel(InsnNode insn, InsnNode prevInsn) {
|
||||||
if (insn.contains(AType.EXC_HANDLER)) {
|
if (insn.contains(AType.EXC_HANDLER)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -53,16 +53,26 @@ public class NameGen {
|
|||||||
"java.lang.Exception", "exc");
|
"java.lang.Exception", "exc");
|
||||||
}
|
}
|
||||||
|
|
||||||
public NameGen(MethodNode mth, boolean fallback) {
|
public NameGen(MethodNode mth, ClassGen classGen) {
|
||||||
this.mth = mth;
|
this.mth = mth;
|
||||||
this.fallback = fallback;
|
this.fallback = classGen.isFallbackMode();
|
||||||
|
NameGen outerNameGen = classGen.getOuterNameGen();
|
||||||
|
if (outerNameGen != null) {
|
||||||
|
inheritUsedNames(outerNameGen);
|
||||||
|
}
|
||||||
addNamesUsedInClass();
|
addNamesUsedInClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void inheritUsedNames(NameGen otherNameGen) {
|
||||||
|
varNames.addAll(otherNameGen.varNames);
|
||||||
|
}
|
||||||
|
|
||||||
private void addNamesUsedInClass() {
|
private void addNamesUsedInClass() {
|
||||||
ClassNode parentClass = mth.getParentClass();
|
ClassNode parentClass = mth.getParentClass();
|
||||||
for (FieldNode field : parentClass.getFields()) {
|
for (FieldNode field : parentClass.getFields()) {
|
||||||
varNames.add(field.getAlias());
|
if (field.isStatic()) {
|
||||||
|
varNames.add(field.getAlias());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (ClassNode innerClass : parentClass.getInnerClasses()) {
|
for (ClassNode innerClass : parentClass.getInnerClasses()) {
|
||||||
varNames.add(innerClass.getClassInfo().getAliasShortName());
|
varNames.add(innerClass.getClassInfo().getAliasShortName());
|
||||||
@@ -72,11 +82,13 @@ public class NameGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String assignArg(CodeVar var) {
|
public String assignArg(CodeVar var) {
|
||||||
String name = makeArgName(var);
|
|
||||||
if (fallback) {
|
if (fallback) {
|
||||||
return name;
|
return getFallbackName(var);
|
||||||
}
|
}
|
||||||
name = getUniqueVarName(name);
|
if (var.isThis()) {
|
||||||
|
return RegisterArg.THIS_ARG_NAME;
|
||||||
|
}
|
||||||
|
String name = getUniqueVarName(makeArgName(var));
|
||||||
var.setName(name);
|
var.setName(name);
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@@ -118,25 +130,22 @@ public class NameGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String makeArgName(CodeVar var) {
|
private String makeArgName(CodeVar var) {
|
||||||
if (fallback) {
|
|
||||||
return getFallbackName(var);
|
|
||||||
}
|
|
||||||
if (var.isThis()) {
|
|
||||||
return RegisterArg.THIS_ARG_NAME;
|
|
||||||
}
|
|
||||||
String name = var.getName();
|
String name = var.getName();
|
||||||
String varName = name != null ? name : guessName(var);
|
if (name == null) {
|
||||||
if (NameMapper.isReserved(varName)) {
|
name = guessName(var);
|
||||||
varName = varName + 'R';
|
|
||||||
}
|
}
|
||||||
if (!NameMapper.isValidAndPrintable(varName)) {
|
if (!NameMapper.isValidAndPrintable(name)) {
|
||||||
varName = getFallbackName(var);
|
name = getFallbackName(var);
|
||||||
}
|
}
|
||||||
return varName;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFallbackName(CodeVar var) {
|
private String getFallbackName(CodeVar var) {
|
||||||
return getFallbackName(var.getSsaVars().get(0).getAssign());
|
List<SSAVar> ssaVars = var.getSsaVars();
|
||||||
|
if (ssaVars.isEmpty()) {
|
||||||
|
return "v";
|
||||||
|
}
|
||||||
|
return getFallbackName(ssaVars.get(0).getAssign());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFallbackName(RegisterArg arg) {
|
private String getFallbackName(RegisterArg arg) {
|
||||||
|
|||||||
@@ -8,13 +8,20 @@ import java.util.Objects;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.CommentsLevel;
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.data.annotations.InsnCodeOffset;
|
||||||
|
import jadx.api.data.annotations.VarDeclareRef;
|
||||||
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.instructions.SwitchNode;
|
import jadx.core.dex.instructions.SwitchInsn;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.CodeVar;
|
import jadx.core.dex.instructions.args.CodeVar;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.NamedArg;
|
import jadx.core.dex.instructions.args.NamedArg;
|
||||||
@@ -23,11 +30,10 @@ import jadx.core.dex.nodes.BlockNode;
|
|||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.IBlock;
|
import jadx.core.dex.nodes.IBlock;
|
||||||
import jadx.core.dex.nodes.IContainer;
|
import jadx.core.dex.nodes.IContainer;
|
||||||
import jadx.core.dex.nodes.IRegion;
|
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
|
||||||
import jadx.core.dex.regions.Region;
|
import jadx.core.dex.regions.Region;
|
||||||
import jadx.core.dex.regions.SwitchRegion;
|
import jadx.core.dex.regions.SwitchRegion;
|
||||||
|
import jadx.core.dex.regions.SwitchRegion.CaseInfo;
|
||||||
import jadx.core.dex.regions.SynchronizedRegion;
|
import jadx.core.dex.regions.SynchronizedRegion;
|
||||||
import jadx.core.dex.regions.TryCatchRegion;
|
import jadx.core.dex.regions.TryCatchRegion;
|
||||||
import jadx.core.dex.regions.conditions.IfCondition;
|
import jadx.core.dex.regions.conditions.IfCondition;
|
||||||
@@ -38,8 +44,9 @@ import jadx.core.dex.regions.loops.LoopRegion;
|
|||||||
import jadx.core.dex.regions.loops.LoopType;
|
import jadx.core.dex.regions.loops.LoopType;
|
||||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||||
import jadx.core.utils.BlockUtils;
|
import jadx.core.utils.BlockUtils;
|
||||||
import jadx.core.utils.ErrorsCounter;
|
import jadx.core.utils.CodeGenUtils;
|
||||||
import jadx.core.utils.RegionUtils;
|
import jadx.core.utils.RegionUtils;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
@@ -50,56 +57,30 @@ public class RegionGen extends InsnGen {
|
|||||||
super(mgen, false);
|
super(mgen, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void makeRegion(CodeWriter code, IContainer cont) throws CodegenException {
|
public void makeRegion(ICodeWriter code, IContainer cont) throws CodegenException {
|
||||||
if (cont instanceof IBlock) {
|
declareVars(code, cont);
|
||||||
makeSimpleBlock((IBlock) cont, code);
|
cont.generate(this, code);
|
||||||
} else if (cont instanceof IRegion) {
|
|
||||||
if (cont instanceof Region) {
|
|
||||||
makeSimpleRegion(code, (Region) cont);
|
|
||||||
} else {
|
|
||||||
declareVars(code, cont);
|
|
||||||
if (cont instanceof IfRegion) {
|
|
||||||
makeIf((IfRegion) cont, code, true);
|
|
||||||
} else if (cont instanceof SwitchRegion) {
|
|
||||||
makeSwitch((SwitchRegion) cont, code);
|
|
||||||
} else if (cont instanceof LoopRegion) {
|
|
||||||
makeLoop((LoopRegion) cont, code);
|
|
||||||
} else if (cont instanceof TryCatchRegion) {
|
|
||||||
makeTryCatch((TryCatchRegion) cont, code);
|
|
||||||
} else if (cont instanceof SynchronizedRegion) {
|
|
||||||
makeSynchronizedRegion((SynchronizedRegion) cont, code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new CodegenException("Not processed container: " + cont);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void declareVars(CodeWriter code, IContainer cont) {
|
private void declareVars(ICodeWriter code, IContainer cont) {
|
||||||
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
|
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
|
||||||
if (declVars != null) {
|
if (declVars != null) {
|
||||||
for (CodeVar v : declVars.getVars()) {
|
for (CodeVar v : declVars.getVars()) {
|
||||||
code.startLine();
|
code.startLine();
|
||||||
declareVar(code, v);
|
declareVar(code, v);
|
||||||
code.add(';');
|
code.add(';');
|
||||||
|
CodeGenUtils.addCodeComments(code, mth, v.getAnySsaVar().getAssign());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException {
|
private void makeRegionIndent(ICodeWriter code, IContainer region) throws CodegenException {
|
||||||
declareVars(code, region);
|
|
||||||
for (IContainer c : region.getSubBlocks()) {
|
|
||||||
makeRegion(code, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void makeRegionIndent(CodeWriter code, IContainer region) throws CodegenException {
|
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
makeRegion(code, region);
|
makeRegion(code, region);
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
|
public void makeSimpleBlock(IBlock block, ICodeWriter code) throws CodegenException {
|
||||||
if (block.contains(AFlag.DONT_GENERATE)) {
|
if (block.contains(AFlag.DONT_GENERATE)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -115,28 +96,35 @@ public class RegionGen extends InsnGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeIf(IfRegion region, CodeWriter code, boolean newLine) throws CodegenException {
|
public void makeIf(IfRegion region, ICodeWriter code, boolean newLine) throws CodegenException {
|
||||||
if (newLine) {
|
if (newLine) {
|
||||||
code.startLineWithNum(region.getSourceLine());
|
code.startLineWithNum(region.getSourceLine());
|
||||||
} else {
|
} else {
|
||||||
code.attachSourceLine(region.getSourceLine());
|
code.attachSourceLine(region.getSourceLine());
|
||||||
}
|
}
|
||||||
if (attachInsns) {
|
boolean comment = region.contains(AFlag.COMMENT_OUT);
|
||||||
List<BlockNode> conditionBlocks = region.getConditionBlocks();
|
if (comment) {
|
||||||
if (!conditionBlocks.isEmpty()) {
|
code.add("// ");
|
||||||
BlockNode blockNode = conditionBlocks.get(0);
|
|
||||||
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
|
|
||||||
if (lastInsn != null) {
|
|
||||||
code.attachLineAnnotation(lastInsn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
code.add("if (");
|
code.add("if (");
|
||||||
new ConditionGen(this).add(code, region.getCondition());
|
new ConditionGen(this).add(code, region.getCondition());
|
||||||
code.add(") {");
|
code.add(") {");
|
||||||
|
if (code.isMetadataSupported()) {
|
||||||
|
List<BlockNode> conditionBlocks = region.getConditionBlocks();
|
||||||
|
if (!conditionBlocks.isEmpty()) {
|
||||||
|
BlockNode blockNode = conditionBlocks.get(0);
|
||||||
|
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
|
||||||
|
InsnCodeOffset.attach(code, lastInsn);
|
||||||
|
CodeGenUtils.addCodeComments(code, mth, lastInsn);
|
||||||
|
}
|
||||||
|
}
|
||||||
makeRegionIndent(code, region.getThenRegion());
|
makeRegionIndent(code, region.getThenRegion());
|
||||||
code.startLine('}');
|
if (comment) {
|
||||||
|
code.startLine("// }");
|
||||||
|
} else {
|
||||||
|
code.startLine('}');
|
||||||
|
}
|
||||||
|
|
||||||
IContainer els = region.getElseRegion();
|
IContainer els = region.getElseRegion();
|
||||||
if (RegionUtils.notEmpty(els)) {
|
if (RegionUtils.notEmpty(els)) {
|
||||||
@@ -146,14 +134,18 @@ public class RegionGen extends InsnGen {
|
|||||||
}
|
}
|
||||||
code.add('{');
|
code.add('{');
|
||||||
makeRegionIndent(code, els);
|
makeRegionIndent(code, els);
|
||||||
code.startLine('}');
|
if (comment) {
|
||||||
|
code.startLine("// }");
|
||||||
|
} else {
|
||||||
|
code.startLine('}');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect if-else-if block
|
* Connect if-else-if block
|
||||||
*/
|
*/
|
||||||
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
|
private boolean connectElseIf(ICodeWriter code, IContainer els) throws CodegenException {
|
||||||
if (els.contains(AFlag.ELSE_IF_CHAIN) && els instanceof Region) {
|
if (els.contains(AFlag.ELSE_IF_CHAIN) && els instanceof Region) {
|
||||||
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
|
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
|
||||||
if (subBlocks.size() == 1) {
|
if (subBlocks.size() == 1) {
|
||||||
@@ -168,134 +160,144 @@ public class RegionGen extends InsnGen {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException {
|
public void makeLoop(LoopRegion region, ICodeWriter code) throws CodegenException {
|
||||||
BlockNode header = region.getHeader();
|
code.startLineWithNum(region.getConditionSourceLine());
|
||||||
if (header != null) {
|
|
||||||
List<InsnNode> headerInsns = header.getInstructions();
|
|
||||||
if (headerInsns.size() > 1) {
|
|
||||||
ErrorsCounter.methodWarn(mth, "Found not inlined instructions from loop header");
|
|
||||||
int last = headerInsns.size() - 1;
|
|
||||||
for (int i = 0; i < last; i++) {
|
|
||||||
InsnNode insn = headerInsns.get(i);
|
|
||||||
makeInsn(insn, code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
|
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
|
||||||
if (labelAttr != null) {
|
if (labelAttr != null) {
|
||||||
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
|
code.add(mgen.getNameGen().getLoopLabel(labelAttr)).add(": ");
|
||||||
}
|
}
|
||||||
|
|
||||||
IfCondition condition = region.getCondition();
|
IfCondition condition = region.getCondition();
|
||||||
if (condition == null) {
|
if (condition == null) {
|
||||||
// infinite loop
|
// infinite loop
|
||||||
code.startLine("while (true) {");
|
code.add("while (true) {");
|
||||||
makeRegionIndent(code, region.getBody());
|
makeRegionIndent(code, region.getBody());
|
||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
return code;
|
return;
|
||||||
}
|
}
|
||||||
|
InsnNode condInsn = condition.getFirstInsn();
|
||||||
|
InsnCodeOffset.attach(code, condInsn);
|
||||||
|
|
||||||
ConditionGen conditionGen = new ConditionGen(this);
|
ConditionGen conditionGen = new ConditionGen(this);
|
||||||
LoopType type = region.getType();
|
LoopType type = region.getType();
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
if (type instanceof ForLoop) {
|
if (type instanceof ForLoop) {
|
||||||
ForLoop forLoop = (ForLoop) type;
|
ForLoop forLoop = (ForLoop) type;
|
||||||
code.startLine("for (");
|
code.add("for (");
|
||||||
makeInsn(forLoop.getInitInsn(), code, Flags.INLINE);
|
makeInsn(forLoop.getInitInsn(), code, Flags.INLINE);
|
||||||
code.add("; ");
|
code.add("; ");
|
||||||
conditionGen.add(code, condition);
|
conditionGen.add(code, condition);
|
||||||
code.add("; ");
|
code.add("; ");
|
||||||
makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE);
|
makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE);
|
||||||
code.add(") {");
|
code.add(") {");
|
||||||
|
CodeGenUtils.addCodeComments(code, mth, condInsn);
|
||||||
makeRegionIndent(code, region.getBody());
|
makeRegionIndent(code, region.getBody());
|
||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
return code;
|
return;
|
||||||
}
|
}
|
||||||
if (type instanceof ForEachLoop) {
|
if (type instanceof ForEachLoop) {
|
||||||
ForEachLoop forEachLoop = (ForEachLoop) type;
|
ForEachLoop forEachLoop = (ForEachLoop) type;
|
||||||
code.startLine("for (");
|
code.add("for (");
|
||||||
declareVar(code, forEachLoop.getVarArg());
|
declareVar(code, forEachLoop.getVarArg());
|
||||||
code.add(" : ");
|
code.add(" : ");
|
||||||
addArg(code, forEachLoop.getIterableArg(), false);
|
addArg(code, forEachLoop.getIterableArg(), false);
|
||||||
code.add(") {");
|
code.add(") {");
|
||||||
|
CodeGenUtils.addCodeComments(code, mth, condInsn);
|
||||||
makeRegionIndent(code, region.getBody());
|
makeRegionIndent(code, region.getBody());
|
||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
return code;
|
return;
|
||||||
}
|
}
|
||||||
throw new JadxRuntimeException("Unknown loop type: " + type.getClass());
|
throw new JadxRuntimeException("Unknown loop type: " + type.getClass());
|
||||||
}
|
}
|
||||||
if (region.isConditionAtEnd()) {
|
if (region.isConditionAtEnd()) {
|
||||||
code.startLine("do {");
|
code.add("do {");
|
||||||
|
CodeGenUtils.addCodeComments(code, mth, condInsn);
|
||||||
makeRegionIndent(code, region.getBody());
|
makeRegionIndent(code, region.getBody());
|
||||||
code.startLineWithNum(region.getConditionSourceLine());
|
code.startLineWithNum(region.getConditionSourceLine());
|
||||||
code.add("} while (");
|
code.add("} while (");
|
||||||
conditionGen.add(code, condition);
|
conditionGen.add(code, condition);
|
||||||
code.add(");");
|
code.add(");");
|
||||||
} else {
|
} else {
|
||||||
code.startLineWithNum(region.getConditionSourceLine());
|
|
||||||
code.add("while (");
|
code.add("while (");
|
||||||
conditionGen.add(code, condition);
|
conditionGen.add(code, condition);
|
||||||
code.add(") {");
|
code.add(") {");
|
||||||
|
CodeGenUtils.addCodeComments(code, mth, condInsn);
|
||||||
makeRegionIndent(code, region.getBody());
|
makeRegionIndent(code, region.getBody());
|
||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
}
|
}
|
||||||
return code;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeSynchronizedRegion(SynchronizedRegion cont, CodeWriter code) throws CodegenException {
|
public void makeSynchronizedRegion(SynchronizedRegion cont, ICodeWriter code) throws CodegenException {
|
||||||
code.startLine("synchronized (");
|
code.startLine("synchronized (");
|
||||||
addArg(code, cont.getEnterInsn().getArg(0));
|
InsnNode monitorEnterInsn = cont.getEnterInsn();
|
||||||
|
addArg(code, monitorEnterInsn.getArg(0));
|
||||||
code.add(") {");
|
code.add(") {");
|
||||||
|
|
||||||
|
InsnCodeOffset.attach(code, monitorEnterInsn);
|
||||||
|
CodeGenUtils.addCodeComments(code, mth, monitorEnterInsn);
|
||||||
|
|
||||||
makeRegionIndent(code, cont.getRegion());
|
makeRegionIndent(code, cont.getRegion());
|
||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
|
public void makeSwitch(SwitchRegion sw, ICodeWriter code) throws CodegenException {
|
||||||
SwitchNode insn = (SwitchNode) BlockUtils.getLastInsn(sw.getHeader());
|
SwitchInsn insn = (SwitchInsn) BlockUtils.getLastInsn(sw.getHeader());
|
||||||
Objects.requireNonNull(insn, "Switch insn not found in header");
|
Objects.requireNonNull(insn, "Switch insn not found in header");
|
||||||
InsnArg arg = insn.getArg(0);
|
InsnArg arg = insn.getArg(0);
|
||||||
code.startLine("switch (");
|
code.startLine("switch (");
|
||||||
addArg(code, arg, false);
|
addArg(code, arg, false);
|
||||||
code.add(") {");
|
code.add(") {");
|
||||||
|
InsnCodeOffset.attach(code, insn);
|
||||||
|
CodeGenUtils.addCodeComments(code, mth, insn);
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
|
|
||||||
int size = sw.getKeys().size();
|
for (CaseInfo caseInfo : sw.getCases()) {
|
||||||
for (int i = 0; i < size; i++) {
|
List<Object> keys = caseInfo.getKeys();
|
||||||
List<Object> keys = sw.getKeys().get(i);
|
IContainer c = caseInfo.getContainer();
|
||||||
IContainer c = sw.getCases().get(i);
|
|
||||||
for (Object k : keys) {
|
for (Object k : keys) {
|
||||||
code.startLine("case ");
|
if (k == SwitchRegion.DEFAULT_CASE_KEY) {
|
||||||
if (k instanceof FieldNode) {
|
code.startLine("default:");
|
||||||
FieldNode fn = (FieldNode) k;
|
|
||||||
if (fn.getParentClass().isEnum()) {
|
|
||||||
code.add(fn.getAlias());
|
|
||||||
} else {
|
|
||||||
staticField(code, fn.getFieldInfo());
|
|
||||||
// print original value, sometimes replace with incorrect field
|
|
||||||
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
|
|
||||||
if (valueAttr != null && valueAttr.getValue() != null) {
|
|
||||||
code.add(" /*").add(valueAttr.getValue().toString()).add("*/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (k instanceof Integer) {
|
|
||||||
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
|
|
||||||
} else {
|
} else {
|
||||||
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
code.startLine("case ");
|
||||||
|
addCaseKey(code, arg, k);
|
||||||
|
code.add(':');
|
||||||
}
|
}
|
||||||
code.add(':');
|
|
||||||
}
|
}
|
||||||
makeRegionIndent(code, c);
|
makeRegionIndent(code, c);
|
||||||
}
|
}
|
||||||
if (sw.getDefaultCase() != null) {
|
|
||||||
code.startLine("default:");
|
|
||||||
makeRegionIndent(code, sw.getDefaultCase());
|
|
||||||
}
|
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
return code;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
|
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) {
|
||||||
|
if (k instanceof FieldNode) {
|
||||||
|
FieldNode fn = (FieldNode) k;
|
||||||
|
if (fn.getParentClass().isEnum()) {
|
||||||
|
code.add(fn.getAlias());
|
||||||
|
} else {
|
||||||
|
staticField(code, fn.getFieldInfo());
|
||||||
|
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
|
||||||
|
// print original value, sometimes replaced with incorrect field
|
||||||
|
EncodedValue constVal = fn.get(JadxAttrType.CONSTANT_VALUE);
|
||||||
|
if (constVal != null && constVal.getValue() != null) {
|
||||||
|
code.add(" /* ").add(constVal.getValue().toString()).add(" */");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (k instanceof Integer) {
|
||||||
|
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
|
||||||
|
} else {
|
||||||
|
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void makeTryCatch(TryCatchRegion region, ICodeWriter code) throws CodegenException {
|
||||||
code.startLine("try {");
|
code.startLine("try {");
|
||||||
|
|
||||||
|
InsnNode insn = BlockUtils.getFirstInsn(Utils.first(region.getTryCatchBlock().getBlocks()));
|
||||||
|
InsnCodeOffset.attach(code, insn);
|
||||||
|
CodeGenUtils.addCodeComments(code, mth, insn);
|
||||||
|
|
||||||
makeRegionIndent(code, region.getTryRegion());
|
makeRegionIndent(code, region.getTryRegion());
|
||||||
// TODO: move search of 'allHandler' to 'TryCatchRegion'
|
// TODO: move search of 'allHandler' to 'TryCatchRegion'
|
||||||
ExceptionHandler allHandler = null;
|
ExceptionHandler allHandler = null;
|
||||||
@@ -321,14 +323,14 @@ public class RegionGen extends InsnGen {
|
|||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler) throws CodegenException {
|
private void makeCatchBlock(ICodeWriter code, ExceptionHandler handler) throws CodegenException {
|
||||||
IContainer region = handler.getHandlerRegion();
|
IContainer region = handler.getHandlerRegion();
|
||||||
if (region == null) {
|
if (region == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
code.startLine("} catch (");
|
code.startLine("} catch (");
|
||||||
if (handler.isCatchAll()) {
|
if (handler.isCatchAll()) {
|
||||||
code.add("Throwable");
|
useClass(code, ArgType.THROWABLE);
|
||||||
} else {
|
} else {
|
||||||
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
|
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
@@ -341,13 +343,24 @@ public class RegionGen extends InsnGen {
|
|||||||
}
|
}
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
InsnArg arg = handler.getArg();
|
InsnArg arg = handler.getArg();
|
||||||
if (arg instanceof RegisterArg) {
|
if (arg == null) {
|
||||||
RegisterArg reg = (RegisterArg) arg;
|
code.add("unknown"); // throwing exception is too late at this point
|
||||||
code.add(mgen.getNameGen().assignArg(reg.getSVar().getCodeVar()));
|
} else if (arg instanceof RegisterArg) {
|
||||||
|
CodeVar codeVar = ((RegisterArg) arg).getSVar().getCodeVar();
|
||||||
|
if (code.isMetadataSupported()) {
|
||||||
|
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
|
||||||
|
}
|
||||||
|
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||||
} else if (arg instanceof NamedArg) {
|
} else if (arg instanceof NamedArg) {
|
||||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||||
|
} else {
|
||||||
|
throw new JadxRuntimeException("Unexpected arg type in catch block: " + arg + ", class: " + arg.getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
code.add(") {");
|
code.add(") {");
|
||||||
|
|
||||||
|
InsnCodeOffset.attach(code, handler.getHandlerOffset());
|
||||||
|
CodeGenUtils.addCodeComments(code, mth, handler.getHandlerBlock());
|
||||||
|
|
||||||
makeRegionIndent(code, region);
|
makeRegionIndent(code, region);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package jadx.core.codegen;
|
package jadx.core.codegen;
|
||||||
|
|
||||||
|
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.core.dex.attributes.AFlag;
|
||||||
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.nodes.IDexNode;
|
import jadx.core.dex.nodes.IDexNode;
|
||||||
import jadx.core.utils.StringUtils;
|
import jadx.core.utils.StringUtils;
|
||||||
@@ -28,16 +30,26 @@ public class TypeGen {
|
|||||||
return stype.getShortName();
|
return stype.getShortName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert literal arg to string (preferred method)
|
||||||
|
*/
|
||||||
|
public static String literalToString(LiteralArg arg, IDexNode dexNode, boolean fallback) {
|
||||||
|
return literalToString(arg.getLiteral(), arg.getType(),
|
||||||
|
dexNode.root().getStringUtils(),
|
||||||
|
fallback,
|
||||||
|
arg.contains(AFlag.EXPLICIT_PRIMITIVE_TYPE));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert literal value to string according to value type
|
* Convert literal value to string according to value type
|
||||||
*
|
*
|
||||||
* @throws JadxRuntimeException for incorrect type or literal value
|
* @throws JadxRuntimeException for incorrect type or literal value
|
||||||
*/
|
*/
|
||||||
public static String literalToString(long lit, ArgType type, IDexNode dexNode, boolean fallback) {
|
public static String literalToString(long lit, ArgType type, IDexNode dexNode, boolean fallback) {
|
||||||
return literalToString(lit, type, dexNode.root().getStringUtils(), fallback);
|
return literalToString(lit, type, dexNode.root().getStringUtils(), fallback, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String literalToString(long lit, ArgType type, StringUtils stringUtils, boolean fallback) {
|
public static String literalToString(long lit, ArgType type, StringUtils stringUtils, boolean fallback, boolean cast) {
|
||||||
if (type == null || !type.isTypeKnown()) {
|
if (type == null || !type.isTypeKnown()) {
|
||||||
String n = Long.toString(lit);
|
String n = Long.toString(lit);
|
||||||
if (fallback && Math.abs(lit) > 100) {
|
if (fallback && Math.abs(lit) > 100) {
|
||||||
@@ -59,19 +71,15 @@ public class TypeGen {
|
|||||||
case BOOLEAN:
|
case BOOLEAN:
|
||||||
return lit == 0 ? "false" : "true";
|
return lit == 0 ? "false" : "true";
|
||||||
case CHAR:
|
case CHAR:
|
||||||
char ch = (char) lit;
|
return stringUtils.unescapeChar((char) lit, cast);
|
||||||
if (!NameMapper.isPrintableChar(ch)) {
|
|
||||||
return Integer.toString(ch);
|
|
||||||
}
|
|
||||||
return stringUtils.unescapeChar(ch);
|
|
||||||
case BYTE:
|
case BYTE:
|
||||||
return formatByte(lit);
|
return formatByte(lit, cast);
|
||||||
case SHORT:
|
case SHORT:
|
||||||
return formatShort(lit);
|
return formatShort(lit, cast);
|
||||||
case INT:
|
case INT:
|
||||||
return formatInteger(lit);
|
return formatInteger(lit, cast);
|
||||||
case LONG:
|
case LONG:
|
||||||
return formatLong(lit);
|
return formatLong(lit, cast);
|
||||||
case FLOAT:
|
case FLOAT:
|
||||||
return formatFloat(Float.intBitsToFloat((int) lit));
|
return formatFloat(Float.intBitsToFloat((int) lit));
|
||||||
case DOUBLE:
|
case DOUBLE:
|
||||||
@@ -90,37 +98,77 @@ public class TypeGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatShort(long l) {
|
@Nullable
|
||||||
|
public static String literalToRawString(LiteralArg arg) {
|
||||||
|
ArgType type = arg.getType();
|
||||||
|
if (type == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
long lit = arg.getLiteral();
|
||||||
|
switch (type.getPrimitiveType()) {
|
||||||
|
case BOOLEAN:
|
||||||
|
return lit == 0 ? "false" : "true";
|
||||||
|
case CHAR:
|
||||||
|
return String.valueOf((char) lit);
|
||||||
|
|
||||||
|
case BYTE:
|
||||||
|
case SHORT:
|
||||||
|
case INT:
|
||||||
|
case LONG:
|
||||||
|
return Long.toString(lit);
|
||||||
|
|
||||||
|
case FLOAT:
|
||||||
|
return Float.toString(Float.intBitsToFloat((int) lit));
|
||||||
|
case DOUBLE:
|
||||||
|
return Double.toString(Double.longBitsToDouble(lit));
|
||||||
|
|
||||||
|
case OBJECT:
|
||||||
|
case ARRAY:
|
||||||
|
if (lit != 0) {
|
||||||
|
LOG.warn("Wrong object literal: {} for type: {}", lit, type);
|
||||||
|
return Long.toString(lit);
|
||||||
|
}
|
||||||
|
return "null";
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatShort(long l, boolean cast) {
|
||||||
if (l == Short.MAX_VALUE) {
|
if (l == Short.MAX_VALUE) {
|
||||||
return "Short.MAX_VALUE";
|
return "Short.MAX_VALUE";
|
||||||
}
|
}
|
||||||
if (l == Short.MIN_VALUE) {
|
if (l == Short.MIN_VALUE) {
|
||||||
return "Short.MIN_VALUE";
|
return "Short.MIN_VALUE";
|
||||||
}
|
}
|
||||||
return Long.toString(l);
|
String str = Long.toString(l);
|
||||||
|
return cast ? "(short) " + str : str;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatByte(long l) {
|
public static String formatByte(long l, boolean cast) {
|
||||||
if (l == Byte.MAX_VALUE) {
|
if (l == Byte.MAX_VALUE) {
|
||||||
return "Byte.MAX_VALUE";
|
return "Byte.MAX_VALUE";
|
||||||
}
|
}
|
||||||
if (l == Byte.MIN_VALUE) {
|
if (l == Byte.MIN_VALUE) {
|
||||||
return "Byte.MIN_VALUE";
|
return "Byte.MIN_VALUE";
|
||||||
}
|
}
|
||||||
return Long.toString(l);
|
String str = Long.toString(l);
|
||||||
|
return cast ? "(byte) " + str : str;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatInteger(long l) {
|
public static String formatInteger(long l, boolean cast) {
|
||||||
if (l == Integer.MAX_VALUE) {
|
if (l == Integer.MAX_VALUE) {
|
||||||
return "Integer.MAX_VALUE";
|
return "Integer.MAX_VALUE";
|
||||||
}
|
}
|
||||||
if (l == Integer.MIN_VALUE) {
|
if (l == Integer.MIN_VALUE) {
|
||||||
return "Integer.MIN_VALUE";
|
return "Integer.MIN_VALUE";
|
||||||
}
|
}
|
||||||
return Long.toString(l);
|
String str = Long.toString(l);
|
||||||
|
return cast ? "(int) " + str : str;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatLong(long l) {
|
public static String formatLong(long l, boolean cast) {
|
||||||
if (l == Long.MAX_VALUE) {
|
if (l == Long.MAX_VALUE) {
|
||||||
return "Long.MAX_VALUE";
|
return "Long.MAX_VALUE";
|
||||||
}
|
}
|
||||||
@@ -128,8 +176,8 @@ public class TypeGen {
|
|||||||
return "Long.MIN_VALUE";
|
return "Long.MIN_VALUE";
|
||||||
}
|
}
|
||||||
String str = Long.toString(l);
|
String str = Long.toString(l);
|
||||||
if (Math.abs(l) >= Integer.MAX_VALUE) {
|
if (cast || Math.abs(l) >= Integer.MAX_VALUE) {
|
||||||
str += 'L';
|
return str + 'L';
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,13 @@ import com.google.gson.Gson;
|
|||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
import jadx.api.CodePosition;
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.data.annotations.InsnCodeOffset;
|
||||||
|
import jadx.api.impl.AnnotatedCodeWriter;
|
||||||
|
import jadx.api.impl.SimpleCodeWriter;
|
||||||
import jadx.core.codegen.ClassGen;
|
import jadx.core.codegen.ClassGen;
|
||||||
import jadx.core.codegen.CodeWriter;
|
|
||||||
import jadx.core.codegen.MethodGen;
|
import jadx.core.codegen.MethodGen;
|
||||||
import jadx.core.codegen.json.cls.JsonClass;
|
import jadx.core.codegen.json.cls.JsonClass;
|
||||||
import jadx.core.codegen.json.cls.JsonCodeLine;
|
import jadx.core.codegen.json.cls.JsonCodeLine;
|
||||||
@@ -26,7 +30,6 @@ import jadx.core.dex.info.ClassInfo;
|
|||||||
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;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
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;
|
||||||
@@ -67,7 +70,7 @@ public class JsonCodeGen {
|
|||||||
|
|
||||||
JsonClass jsonCls = new JsonClass();
|
JsonClass jsonCls = new JsonClass();
|
||||||
jsonCls.setPkg(classInfo.getAliasPkg());
|
jsonCls.setPkg(classInfo.getAliasPkg());
|
||||||
jsonCls.setDex(cls.dex().getDexFile().getName());
|
jsonCls.setDex(cls.getInputFileName());
|
||||||
jsonCls.setName(classInfo.getFullName());
|
jsonCls.setName(classInfo.getFullName());
|
||||||
if (classInfo.hasAlias()) {
|
if (classInfo.hasAlias()) {
|
||||||
jsonCls.setAlias(classInfo.getAliasFullName());
|
jsonCls.setAlias(classInfo.getAliasFullName());
|
||||||
@@ -81,11 +84,10 @@ public class JsonCodeGen {
|
|||||||
jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), this::getTypeAlias));
|
jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), this::getTypeAlias));
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeWriter cw = new CodeWriter();
|
ICodeWriter cw = new SimpleCodeWriter();
|
||||||
CodeGenUtils.addComments(cw, cls);
|
CodeGenUtils.addErrorsAndComments(cw, cls);
|
||||||
classGen.insertDecompilationProblems(cw, cls);
|
|
||||||
classGen.addClassDeclaration(cw);
|
classGen.addClassDeclaration(cw);
|
||||||
jsonCls.setDeclaration(cw.finish().toString());
|
jsonCls.setDeclaration(cw.getCodeStr());
|
||||||
|
|
||||||
addFields(cls, jsonCls, classGen);
|
addFields(cls, jsonCls, classGen);
|
||||||
addMethods(cls, jsonCls, classGen);
|
addMethods(cls, jsonCls, classGen);
|
||||||
@@ -126,11 +128,10 @@ public class JsonCodeGen {
|
|||||||
jsonField.setAlias(field.getAlias());
|
jsonField.setAlias(field.getAlias());
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeWriter cw = new CodeWriter();
|
ICodeWriter cw = new SimpleCodeWriter();
|
||||||
classGen.addField(cw, field);
|
classGen.addField(cw, field);
|
||||||
jsonField.setDeclaration(cw.finish().toString());
|
jsonField.setDeclaration(cw.getCodeStr());
|
||||||
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
|
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
|
||||||
|
|
||||||
jsonCls.getFields().add(jsonField);
|
jsonCls.getFields().add(jsonField);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,9 +152,9 @@ public class JsonCodeGen {
|
|||||||
jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), this::getTypeAlias));
|
jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), this::getTypeAlias));
|
||||||
|
|
||||||
MethodGen mthGen = new MethodGen(classGen, mth);
|
MethodGen mthGen = new MethodGen(classGen, mth);
|
||||||
CodeWriter cw = new CodeWriter();
|
ICodeWriter cw = new AnnotatedCodeWriter();
|
||||||
mthGen.addDefinition(cw);
|
mthGen.addDefinition(cw);
|
||||||
jsonMth.setDeclaration(cw.finish().toString());
|
jsonMth.setDeclaration(cw.getCodeStr());
|
||||||
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
|
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
|
||||||
jsonMth.setLines(fillMthCode(mth, mthGen));
|
jsonMth.setLines(fillMthCode(mth, mthGen));
|
||||||
jsonMth.setOffset("0x" + Long.toHexString(mth.getMethodCodeOffset()));
|
jsonMth.setOffset("0x" + Long.toHexString(mth.getMethodCodeOffset()));
|
||||||
@@ -166,19 +167,19 @@ public class JsonCodeGen {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeWriter code = new CodeWriter();
|
ICodeWriter cw = mth.root().makeCodeWriter();
|
||||||
try {
|
try {
|
||||||
mthGen.addInstructions(code);
|
mthGen.addInstructions(cw);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new JadxRuntimeException("Method generation error", e);
|
throw new JadxRuntimeException("Method generation error", e);
|
||||||
}
|
}
|
||||||
code.finish();
|
ICodeInfo code = cw.finish();
|
||||||
String codeStr = code.toString();
|
String codeStr = code.getCodeStr();
|
||||||
if (codeStr.isEmpty()) {
|
if (codeStr.isEmpty()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] lines = codeStr.split(CodeWriter.NL);
|
String[] lines = codeStr.split(ICodeWriter.NL);
|
||||||
Map<Integer, Integer> lineMapping = code.getLineMapping();
|
Map<Integer, Integer> lineMapping = code.getLineMapping();
|
||||||
Map<CodePosition, Object> annotations = code.getAnnotations();
|
Map<CodePosition, Object> annotations = code.getAnnotations();
|
||||||
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
|
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
|
||||||
@@ -191,9 +192,9 @@ public class JsonCodeGen {
|
|||||||
JsonCodeLine jsonCodeLine = new JsonCodeLine();
|
JsonCodeLine jsonCodeLine = new JsonCodeLine();
|
||||||
jsonCodeLine.setCode(codeLine);
|
jsonCodeLine.setCode(codeLine);
|
||||||
jsonCodeLine.setSourceLine(lineMapping.get(line));
|
jsonCodeLine.setSourceLine(lineMapping.get(line));
|
||||||
Object obj = annotations.get(new CodePosition(line, 0));
|
Object obj = annotations.get(new CodePosition(line));
|
||||||
if (obj instanceof InsnNode) {
|
if (obj instanceof InsnCodeOffset) {
|
||||||
int offset = ((InsnNode) obj).getOffset();
|
long offset = ((InsnCodeOffset) obj).getOffset();
|
||||||
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
|
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
|
||||||
}
|
}
|
||||||
codeLines.add(jsonCodeLine);
|
codeLines.add(jsonCodeLine);
|
||||||
|
|||||||
@@ -1,38 +1,69 @@
|
|||||||
package jadx.core.deobf;
|
package jadx.core.deobf;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.JadxArgs;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
class DeobfPresets {
|
public class DeobfPresets {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DeobfPresets.class);
|
private static final Logger LOG = LoggerFactory.getLogger(DeobfPresets.class);
|
||||||
|
|
||||||
private static final Charset MAP_FILE_CHARSET = UTF_8;
|
private static final Charset MAP_FILE_CHARSET = UTF_8;
|
||||||
|
|
||||||
private final Deobfuscator deobfuscator;
|
|
||||||
private final Path deobfMapFile;
|
private final Path deobfMapFile;
|
||||||
|
|
||||||
|
private final Map<String, String> pkgPresetMap = new HashMap<>();
|
||||||
private final Map<String, String> clsPresetMap = new HashMap<>();
|
private final Map<String, String> clsPresetMap = new HashMap<>();
|
||||||
private final Map<String, String> fldPresetMap = new HashMap<>();
|
private final Map<String, String> fldPresetMap = new HashMap<>();
|
||||||
private final Map<String, String> mthPresetMap = new HashMap<>();
|
private final Map<String, String> mthPresetMap = new HashMap<>();
|
||||||
|
|
||||||
public DeobfPresets(Deobfuscator deobfuscator, Path deobfMapFile) {
|
@Nullable
|
||||||
this.deobfuscator = deobfuscator;
|
public static DeobfPresets build(RootNode root) {
|
||||||
|
Path deobfMapPath = getPathDeobfMapPath(root);
|
||||||
|
if (deobfMapPath == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
|
||||||
|
return new DeobfPresets(deobfMapPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static Path getPathDeobfMapPath(RootNode root) {
|
||||||
|
JadxArgs jadxArgs = root.getArgs();
|
||||||
|
File deobfMapFile = jadxArgs.getDeobfuscationMapFile();
|
||||||
|
if (deobfMapFile != null) {
|
||||||
|
return deobfMapFile.toPath();
|
||||||
|
}
|
||||||
|
List<File> inputFiles = jadxArgs.getInputFiles();
|
||||||
|
if (inputFiles.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Path inputFilePath = inputFiles.get(0).getAbsoluteFile().toPath();
|
||||||
|
String baseName = FileUtils.getPathBaseName(inputFilePath);
|
||||||
|
return inputFilePath.getParent().resolve(baseName + ".jobf");
|
||||||
|
}
|
||||||
|
|
||||||
|
private DeobfPresets(Path deobfMapFile) {
|
||||||
this.deobfMapFile = deobfMapFile;
|
this.deobfMapFile = deobfMapFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,17 +88,25 @@ class DeobfPresets {
|
|||||||
}
|
}
|
||||||
String origName = va[0];
|
String origName = va[0];
|
||||||
String alias = va[1];
|
String alias = va[1];
|
||||||
if (l.startsWith("p ")) {
|
switch (l.charAt(0)) {
|
||||||
deobfuscator.addPackagePreset(origName, alias);
|
case 'p':
|
||||||
} else if (l.startsWith("c ")) {
|
pkgPresetMap.put(origName, alias);
|
||||||
clsPresetMap.put(origName, alias);
|
break;
|
||||||
} else if (l.startsWith("f ")) {
|
case 'c':
|
||||||
fldPresetMap.put(origName, alias);
|
clsPresetMap.put(origName, alias);
|
||||||
} else if (l.startsWith("m ")) {
|
break;
|
||||||
mthPresetMap.put(origName, alias);
|
case 'f':
|
||||||
|
fldPresetMap.put(origName, alias);
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
mthPresetMap.put(origName, alias);
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
// deprecated
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
|
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,75 +119,52 @@ class DeobfPresets {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save(boolean forceSave) {
|
public void save() throws IOException {
|
||||||
try {
|
|
||||||
if (Files.exists(deobfMapFile)) {
|
|
||||||
if (forceSave) {
|
|
||||||
dumpMapping();
|
|
||||||
} else {
|
|
||||||
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
|
|
||||||
deobfMapFile.toAbsolutePath());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dumpMapping();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves DefaultDeobfuscator presets
|
|
||||||
*/
|
|
||||||
private void dumpMapping() throws IOException {
|
|
||||||
List<String> list = new ArrayList<>();
|
List<String> list = new ArrayList<>();
|
||||||
// packages
|
for (Map.Entry<String, String> pkgEntry : pkgPresetMap.entrySet()) {
|
||||||
for (PackageNode p : deobfuscator.getRootPackage().getInnerPackages()) {
|
list.add(String.format("p %s = %s", pkgEntry.getKey(), pkgEntry.getValue()));
|
||||||
for (PackageNode pp : p.getInnerPackages()) {
|
|
||||||
dfsPackageName(list, p.getName(), pp);
|
|
||||||
}
|
|
||||||
if (p.hasAlias()) {
|
|
||||||
list.add(String.format("p %s = %s", p.getName(), p.getAlias()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// classes
|
for (Map.Entry<String, String> clsEntry : clsPresetMap.entrySet()) {
|
||||||
for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) {
|
list.add(String.format("c %s = %s", clsEntry.getKey(), clsEntry.getValue()));
|
||||||
if (deobfClsInfo.getAlias() != null) {
|
|
||||||
list.add(String.format("c %s = %s",
|
|
||||||
deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (FieldInfo fld : deobfuscator.getFldMap().keySet()) {
|
for (Map.Entry<String, String> fldEntry : fldPresetMap.entrySet()) {
|
||||||
list.add(String.format("f %s = %s", fld.getRawFullId(), fld.getAlias()));
|
list.add(String.format("f %s = %s", fldEntry.getKey(), fldEntry.getValue()));
|
||||||
}
|
}
|
||||||
for (MethodInfo mth : deobfuscator.getMthMap().keySet()) {
|
for (Map.Entry<String, String> mthEntry : mthPresetMap.entrySet()) {
|
||||||
list.add(String.format("m %s = %s", mth.getRawFullId(), mth.getAlias()));
|
list.add(String.format("m %s = %s", mthEntry.getKey(), mthEntry.getValue()));
|
||||||
}
|
}
|
||||||
Collections.sort(list);
|
Collections.sort(list);
|
||||||
Files.write(deobfMapFile, list, MAP_FILE_CHARSET);
|
if (list.isEmpty()) {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Deobfuscation map is empty, not saving it");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Files.write(deobfMapFile, list, MAP_FILE_CHARSET,
|
||||||
|
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
|
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
|
|
||||||
for (PackageNode pp : node.getInnerPackages()) {
|
|
||||||
dfsPackageName(list, prefix + '.' + node.getName(), pp);
|
|
||||||
}
|
|
||||||
if (node.hasAlias()) {
|
|
||||||
list.add(String.format("p %s.%s = %s", prefix, node.getName(), node.getAlias()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getForCls(ClassInfo cls) {
|
public String getForCls(ClassInfo cls) {
|
||||||
|
if (clsPresetMap.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return clsPresetMap.get(cls.makeRawFullName());
|
return clsPresetMap.get(cls.makeRawFullName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getForFld(FieldInfo fld) {
|
public String getForFld(FieldInfo fld) {
|
||||||
|
if (fldPresetMap.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return fldPresetMap.get(fld.getRawFullId());
|
return fldPresetMap.get(fld.getRawFullId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getForMth(MethodInfo mth) {
|
public String getForMth(MethodInfo mth) {
|
||||||
|
if (mthPresetMap.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return mthPresetMap.get(mth.getRawFullId());
|
return mthPresetMap.get(mth.getRawFullId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +174,14 @@ class DeobfPresets {
|
|||||||
mthPresetMap.clear();
|
mthPresetMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Path getDeobfMapFile() {
|
||||||
|
return deobfMapFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getPkgPresetMap() {
|
||||||
|
return pkgPresetMap;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getClsPresetMap() {
|
public Map<String, String> getClsPresetMap() {
|
||||||
return clsPresetMap;
|
return clsPresetMap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,35 @@
|
|||||||
package jadx.core.deobf;
|
package jadx.core.deobf;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.NavigableSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
|
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
|
||||||
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.SourceFileAttr;
|
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
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;
|
||||||
import jadx.core.dex.nodes.DexNode;
|
|
||||||
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.utils.kotlin.KotlinMetadataUtils;
|
||||||
|
|
||||||
public class Deobfuscator {
|
public class Deobfuscator {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
|
||||||
@@ -39,51 +40,98 @@ public class Deobfuscator {
|
|||||||
public static final String INNER_CLASS_SEPARATOR = "$";
|
public static final String INNER_CLASS_SEPARATOR = "$";
|
||||||
|
|
||||||
private final JadxArgs args;
|
private final JadxArgs args;
|
||||||
@NotNull
|
private final RootNode root;
|
||||||
private final List<DexNode> dexNodes;
|
|
||||||
private final DeobfPresets deobfPresets;
|
private final DeobfPresets deobfPresets;
|
||||||
|
|
||||||
private final Map<ClassInfo, DeobfClsInfo> clsMap = new LinkedHashMap<>();
|
private final Map<ClassInfo, DeobfClsInfo> clsMap = new LinkedHashMap<>();
|
||||||
private final Map<FieldInfo, String> fldMap = new HashMap<>();
|
private final Map<FieldInfo, String> fldMap = new HashMap<>();
|
||||||
private final Map<MethodInfo, String> mthMap = new HashMap<>();
|
private final Map<MethodInfo, String> mthMap = new HashMap<>();
|
||||||
|
|
||||||
private final Map<MethodInfo, OverridedMethodsNode> ovrdMap = new HashMap<>();
|
|
||||||
private final List<OverridedMethodsNode> ovrd = new ArrayList<>();
|
|
||||||
|
|
||||||
private final PackageNode rootPackage = new PackageNode("");
|
private final PackageNode rootPackage = new PackageNode("");
|
||||||
private final Set<String> pkgSet = new TreeSet<>();
|
private final Set<String> pkgSet = new TreeSet<>();
|
||||||
private final Set<String> reservedClsNames = new HashSet<>();
|
private final Set<String> reservedClsNames = new HashSet<>();
|
||||||
|
|
||||||
|
private final NavigableSet<MethodNode> mthProcessQueue = new TreeSet<>();
|
||||||
|
|
||||||
private final int maxLength;
|
private final int maxLength;
|
||||||
private final int minLength;
|
private final int minLength;
|
||||||
private final boolean useSourceNameAsAlias;
|
private final boolean useSourceNameAsAlias;
|
||||||
|
private final boolean parseKotlinMetadata;
|
||||||
|
|
||||||
private int pkgIndex = 0;
|
private int pkgIndex = 0;
|
||||||
private int clsIndex = 0;
|
private int clsIndex = 0;
|
||||||
private int fldIndex = 0;
|
private int fldIndex = 0;
|
||||||
private int mthIndex = 0;
|
private int mthIndex = 0;
|
||||||
|
|
||||||
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, Path deobfMapFile) {
|
public Deobfuscator(RootNode root) {
|
||||||
this.args = args;
|
this.root = root;
|
||||||
this.dexNodes = dexNodes;
|
this.args = root.getArgs();
|
||||||
|
|
||||||
this.minLength = args.getDeobfuscationMinLength();
|
this.minLength = args.getDeobfuscationMinLength();
|
||||||
this.maxLength = args.getDeobfuscationMaxLength();
|
this.maxLength = args.getDeobfuscationMaxLength();
|
||||||
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
|
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
|
||||||
|
this.parseKotlinMetadata = args.isParseKotlinMetadata();
|
||||||
|
|
||||||
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
|
this.deobfPresets = DeobfPresets.build(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void execute() {
|
public void execute() {
|
||||||
if (!args.isDeobfuscationForceSave()) {
|
if (!args.isDeobfuscationForceSave()) {
|
||||||
deobfPresets.load();
|
deobfPresets.load();
|
||||||
|
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
|
||||||
|
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
|
||||||
|
}
|
||||||
|
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
|
||||||
initIndexes();
|
initIndexes();
|
||||||
}
|
}
|
||||||
process();
|
process();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void savePresets() {
|
public void savePresets() {
|
||||||
deobfPresets.save(args.isDeobfuscationForceSave());
|
Path deobfMapFile = deobfPresets.getDeobfMapFile();
|
||||||
|
if (Files.exists(deobfMapFile) && !args.isDeobfuscationForceSave()) {
|
||||||
|
LOG.info("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
|
||||||
|
deobfMapFile.toAbsolutePath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
deobfPresets.clear();
|
||||||
|
fillDeobfPresets();
|
||||||
|
deobfPresets.save();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Failed to save deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillDeobfPresets() {
|
||||||
|
for (PackageNode p : getRootPackage().getInnerPackages()) {
|
||||||
|
for (PackageNode pp : p.getInnerPackages()) {
|
||||||
|
dfsPackageName(p.getName(), pp);
|
||||||
|
}
|
||||||
|
if (p.hasAlias()) {
|
||||||
|
deobfPresets.getPkgPresetMap().put(p.getName(), p.getAlias());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
|
||||||
|
if (deobfClsInfo.getAlias() != null) {
|
||||||
|
deobfPresets.getClsPresetMap().put(deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (FieldInfo fld : fldMap.keySet()) {
|
||||||
|
deobfPresets.getFldPresetMap().put(fld.getRawFullId(), fld.getAlias());
|
||||||
|
}
|
||||||
|
for (MethodInfo mth : mthMap.keySet()) {
|
||||||
|
deobfPresets.getMthPresetMap().put(mth.getRawFullId(), mth.getAlias());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dfsPackageName(String prefix, PackageNode node) {
|
||||||
|
for (PackageNode pp : node.getInnerPackages()) {
|
||||||
|
dfsPackageName(prefix + '.' + node.getName(), pp);
|
||||||
|
}
|
||||||
|
if (node.hasAlias()) {
|
||||||
|
deobfPresets.getPkgPresetMap().put(node.getName(), node.getAlias());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
@@ -91,9 +139,6 @@ public class Deobfuscator {
|
|||||||
clsMap.clear();
|
clsMap.clear();
|
||||||
fldMap.clear();
|
fldMap.clear();
|
||||||
mthMap.clear();
|
mthMap.clear();
|
||||||
|
|
||||||
ovrd.clear();
|
|
||||||
ovrdMap.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initIndexes() {
|
private void initIndexes() {
|
||||||
@@ -104,15 +149,11 @@ public class Deobfuscator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void preProcess() {
|
private void preProcess() {
|
||||||
for (DexNode dexNode : dexNodes) {
|
for (ClassNode cls : root.getClasses()) {
|
||||||
for (ClassNode cls : dexNode.getClasses()) {
|
Collections.addAll(reservedClsNames, cls.getPackage().split("\\."));
|
||||||
Collections.addAll(reservedClsNames, cls.getPackage().split("\\."));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (DexNode dexNode : dexNodes) {
|
for (ClassNode cls : root.getClasses()) {
|
||||||
for (ClassNode cls : dexNode.getClasses()) {
|
preProcessClass(cls);
|
||||||
preProcessClass(cls);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,104 +162,15 @@ public class Deobfuscator {
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
dumpAlias();
|
dumpAlias();
|
||||||
}
|
}
|
||||||
for (DexNode dexNode : dexNodes) {
|
for (ClassNode cls : root.getClasses()) {
|
||||||
for (ClassNode cls : dexNode.getClasses()) {
|
processClass(cls);
|
||||||
processClass(cls);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
postProcess();
|
while (true) {
|
||||||
}
|
MethodNode next = mthProcessQueue.pollLast();
|
||||||
|
if (next == null) {
|
||||||
private void postProcess() {
|
break;
|
||||||
int id = 1;
|
|
||||||
for (OverridedMethodsNode o : ovrd) {
|
|
||||||
boolean aliasFromPreset = false;
|
|
||||||
String aliasToUse = null;
|
|
||||||
for (MethodInfo mth : o.getMethods()) {
|
|
||||||
if (mth.isAliasFromPreset()) {
|
|
||||||
aliasToUse = mth.getAlias();
|
|
||||||
aliasFromPreset = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (MethodInfo mth : o.getMethods()) {
|
|
||||||
if (aliasToUse == null) {
|
|
||||||
if (mth.hasAlias() && !mth.isAliasFromPreset()) {
|
|
||||||
mth.setAlias(String.format("mo%d%s", id, prepareNamePart(mth.getName())));
|
|
||||||
}
|
|
||||||
aliasToUse = mth.getAlias();
|
|
||||||
}
|
|
||||||
mth.setAlias(aliasToUse);
|
|
||||||
mth.setAliasFromPreset(aliasFromPreset);
|
|
||||||
}
|
|
||||||
id++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resolveOverriding(MethodNode mth) {
|
|
||||||
Set<ClassNode> clsParents = new LinkedHashSet<>();
|
|
||||||
collectClassHierarchy(mth.getParentClass(), clsParents);
|
|
||||||
|
|
||||||
String mthSignature = mth.getMethodInfo().makeSignature(false);
|
|
||||||
Set<MethodInfo> overrideSet = new LinkedHashSet<>();
|
|
||||||
for (ClassNode classNode : clsParents) {
|
|
||||||
MethodInfo methodInfo = getMthOverride(classNode.getMethods(), mthSignature);
|
|
||||||
if (methodInfo != null) {
|
|
||||||
overrideSet.add(methodInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (overrideSet.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
OverridedMethodsNode overrideNode = getOverrideMethodsNode(overrideSet);
|
|
||||||
if (overrideNode == null) {
|
|
||||||
overrideNode = new OverridedMethodsNode(overrideSet);
|
|
||||||
ovrd.add(overrideNode);
|
|
||||||
}
|
|
||||||
for (MethodInfo overrideMth : overrideSet) {
|
|
||||||
if (!ovrdMap.containsKey(overrideMth)) {
|
|
||||||
ovrdMap.put(overrideMth, overrideNode);
|
|
||||||
overrideNode.add(overrideMth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private OverridedMethodsNode getOverrideMethodsNode(Set<MethodInfo> overrideSet) {
|
|
||||||
for (MethodInfo overrideMth : overrideSet) {
|
|
||||||
OverridedMethodsNode node = ovrdMap.get(overrideMth);
|
|
||||||
if (node != null) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MethodInfo getMthOverride(List<MethodNode> methods, String mthSignature) {
|
|
||||||
for (MethodNode m : methods) {
|
|
||||||
MethodInfo mthInfo = m.getMethodInfo();
|
|
||||||
if (mthInfo.getShortId().startsWith(mthSignature)) {
|
|
||||||
return mthInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void collectClassHierarchy(ClassNode cls, Set<ClassNode> collected) {
|
|
||||||
boolean added = collected.add(cls);
|
|
||||||
if (added) {
|
|
||||||
ArgType superClass = cls.getSuperClass();
|
|
||||||
if (superClass != null) {
|
|
||||||
ClassNode superNode = cls.dex().resolveClass(superClass);
|
|
||||||
if (superNode != null) {
|
|
||||||
collectClassHierarchy(superNode, collected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ArgType argType : cls.getInterfaces()) {
|
|
||||||
ClassNode interfaceNode = cls.dex().resolveClass(argType);
|
|
||||||
if (interfaceNode != null) {
|
|
||||||
collectClassHierarchy(interfaceNode, collected);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
renameMethod(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,9 +199,8 @@ public class Deobfuscator {
|
|||||||
}
|
}
|
||||||
renameField(field);
|
renameField(field);
|
||||||
}
|
}
|
||||||
for (MethodNode mth : cls.getMethods()) {
|
mthProcessQueue.addAll(cls.getMethods());
|
||||||
renameMethod(mth);
|
|
||||||
}
|
|
||||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||||
processClass(innerCls);
|
processClass(innerCls);
|
||||||
}
|
}
|
||||||
@@ -270,20 +221,35 @@ public class Deobfuscator {
|
|||||||
private void renameMethod(MethodNode mth) {
|
private void renameMethod(MethodNode mth) {
|
||||||
String alias = getMethodAlias(mth);
|
String alias = getMethodAlias(mth);
|
||||||
if (alias != null) {
|
if (alias != null) {
|
||||||
mth.getMethodInfo().setAlias(alias);
|
applyMethodAlias(mth, alias);
|
||||||
}
|
|
||||||
if (mth.isVirtual()) {
|
|
||||||
resolveOverriding(mth);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void forceRenameMethod(MethodNode mth) {
|
public void forceRenameMethod(MethodNode mth) {
|
||||||
mth.getMethodInfo().setAlias(makeMethodAlias(mth));
|
String alias = makeMethodAlias(mth);
|
||||||
if (mth.isVirtual()) {
|
applyMethodAlias(mth, alias);
|
||||||
resolveOverriding(mth);
|
}
|
||||||
|
|
||||||
|
private void applyMethodAlias(MethodNode mth, String alias) {
|
||||||
|
setSingleMethodAlias(mth, alias);
|
||||||
|
|
||||||
|
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||||
|
if (overrideAttr != null) {
|
||||||
|
for (MethodNode ovrdMth : overrideAttr.getRelatedMthNodes()) {
|
||||||
|
if (ovrdMth != mth) {
|
||||||
|
setSingleMethodAlias(ovrdMth, alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setSingleMethodAlias(MethodNode mth, String alias) {
|
||||||
|
MethodInfo mthInfo = mth.getMethodInfo();
|
||||||
|
mthInfo.setAlias(alias);
|
||||||
|
mthMap.put(mthInfo, alias);
|
||||||
|
mthProcessQueue.remove(mth);
|
||||||
|
}
|
||||||
|
|
||||||
public void addPackagePreset(String origPkgName, String pkgAlias) {
|
public void addPackagePreset(String origPkgName, String pkgAlias) {
|
||||||
PackageNode pkg = getPackageNode(origPkgName, true);
|
PackageNode pkg = getPackageNode(origPkgName, true);
|
||||||
pkg.setAlias(pkgAlias);
|
pkg.setAlias(pkgAlias);
|
||||||
@@ -292,8 +258,10 @@ public class Deobfuscator {
|
|||||||
/**
|
/**
|
||||||
* Gets package node for full package name
|
* Gets package node for full package name
|
||||||
*
|
*
|
||||||
* @param fullPkgName full package name
|
* @param fullPkgName
|
||||||
* @param create if {@code true} then will create all absent objects
|
* full package name
|
||||||
|
* @param create
|
||||||
|
* if {@code true} then will create all absent objects
|
||||||
* @return package node object or {@code null} if no package found and <b>create</b> set to
|
* @return package node object or {@code null} if no package found and <b>create</b> set to
|
||||||
* {@code false}
|
* {@code false}
|
||||||
*/
|
*/
|
||||||
@@ -346,7 +314,7 @@ public class Deobfuscator {
|
|||||||
ClassInfo classInfo = cls.getClassInfo();
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
String pkgFullName = classInfo.getPackage();
|
String pkgFullName = classInfo.getPackage();
|
||||||
PackageNode pkg = getPackageNode(pkgFullName, true);
|
PackageNode pkg = getPackageNode(pkgFullName, true);
|
||||||
doPkg(pkg, pkgFullName);
|
processPackageFull(pkg, pkgFullName);
|
||||||
|
|
||||||
String alias = deobfPresets.getForCls(classInfo);
|
String alias = deobfPresets.getForCls(classInfo);
|
||||||
if (alias != null) {
|
if (alias != null) {
|
||||||
@@ -354,9 +322,9 @@ public class Deobfuscator {
|
|||||||
} else {
|
} else {
|
||||||
if (!clsMap.containsKey(classInfo)) {
|
if (!clsMap.containsKey(classInfo)) {
|
||||||
String clsShortName = classInfo.getShortName();
|
String clsShortName = classInfo.getShortName();
|
||||||
if (shouldRename(clsShortName) || reservedClsNames.contains(clsShortName)) {
|
boolean badName = shouldRename(clsShortName)
|
||||||
makeClsAlias(cls);
|
|| (args.isRenameValid() && reservedClsNames.contains(clsShortName));
|
||||||
}
|
makeClsAlias(cls, badName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||||
@@ -369,29 +337,133 @@ public class Deobfuscator {
|
|||||||
if (deobfClsInfo != null) {
|
if (deobfClsInfo != null) {
|
||||||
return deobfClsInfo.getAlias();
|
return deobfClsInfo.getAlias();
|
||||||
}
|
}
|
||||||
return makeClsAlias(cls);
|
return makeClsAlias(cls, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String makeClsAlias(ClassNode cls) {
|
public String getPkgAlias(ClassNode cls) {
|
||||||
ClassInfo classInfo = cls.getClassInfo();
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
String alias = null;
|
if (classInfo.hasAliasPkg()) {
|
||||||
|
// already renamed
|
||||||
|
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
|
||||||
|
// update all parts of package
|
||||||
|
String[] aliasParts = classInfo.getAliasPkg().split("\\.");
|
||||||
|
PackageNode subPkg = pkg;
|
||||||
|
for (int i = aliasParts.length - 1; i >= 0; i--) {
|
||||||
|
String aliasPart = aliasParts[i];
|
||||||
|
if (!subPkg.getName().equals(aliasPart)) {
|
||||||
|
subPkg.setAlias(aliasPart);
|
||||||
|
}
|
||||||
|
subPkg = subPkg.getParentPackage();
|
||||||
|
}
|
||||||
|
return pkg.getFullAlias();
|
||||||
|
}
|
||||||
|
PackageNode pkg;
|
||||||
|
DeobfClsInfo deobfClsInfo = clsMap.get(classInfo);
|
||||||
|
if (deobfClsInfo != null) {
|
||||||
|
pkg = deobfClsInfo.getPkg();
|
||||||
|
} else {
|
||||||
|
String fullPkgName = classInfo.getPackage();
|
||||||
|
pkg = getPackageNode(fullPkgName, true);
|
||||||
|
processPackageFull(pkg, fullPkgName);
|
||||||
|
}
|
||||||
|
if (pkg.hasAnyAlias()) {
|
||||||
|
return pkg.getFullAlias();
|
||||||
|
} else {
|
||||||
|
return pkg.getFullName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.useSourceNameAsAlias) {
|
private String makeClsAlias(ClassNode cls, boolean badName) {
|
||||||
|
String alias = null;
|
||||||
|
String pkgName = null;
|
||||||
|
if (this.parseKotlinMetadata) {
|
||||||
|
ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls);
|
||||||
|
if (kotlinCls != null) {
|
||||||
|
alias = prepareNameFull(kotlinCls.getShortName(), "C");
|
||||||
|
pkgName = kotlinCls.getPackage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (alias == null && this.useSourceNameAsAlias) {
|
||||||
alias = getAliasFromSourceFile(cls);
|
alias = getAliasFromSourceFile(cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
if (alias == null) {
|
if (alias == null) {
|
||||||
String clsName = classInfo.getShortName();
|
if (badName) {
|
||||||
alias = String.format("C%04d%s", clsIndex++, prepareNamePart(clsName));
|
String clsName = classInfo.getShortName();
|
||||||
|
String prefix = makeClsPrefix(cls);
|
||||||
|
alias = String.format("%sC%04d%s", prefix, clsIndex++, prepareNamePart(clsName));
|
||||||
|
} else {
|
||||||
|
// rename not needed
|
||||||
|
return classInfo.getShortName();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
|
if (pkgName == null) {
|
||||||
|
pkgName = classInfo.getPackage();
|
||||||
|
}
|
||||||
|
PackageNode pkg = getPackageNode(pkgName, true);
|
||||||
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
||||||
return alias;
|
return alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a prefix for a class name that bases on certain class properties, certain
|
||||||
|
* extended superclasses or implemented interfaces.
|
||||||
|
*/
|
||||||
|
private String makeClsPrefix(ClassNode cls) {
|
||||||
|
if (cls.isEnum()) {
|
||||||
|
return "Enum";
|
||||||
|
}
|
||||||
|
String result = "";
|
||||||
|
if (cls.getAccessFlags().isAbstract()) {
|
||||||
|
result += "Abstract";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process current class and all super classes
|
||||||
|
ClassNode currentCls = cls;
|
||||||
|
outerLoop: while (currentCls != null) {
|
||||||
|
if (currentCls.getSuperClass() != null) {
|
||||||
|
String superClsName = currentCls.getSuperClass().getObject();
|
||||||
|
if (superClsName.startsWith("android.app.")) {
|
||||||
|
// e.g. Activity or Fragment
|
||||||
|
result += superClsName.substring(12);
|
||||||
|
break;
|
||||||
|
} else if (superClsName.startsWith("android.os.")) {
|
||||||
|
// e.g. AsyncTask
|
||||||
|
result += superClsName.substring(11);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (ArgType intf : cls.getInterfaces()) {
|
||||||
|
String intfClsName = intf.getObject();
|
||||||
|
if (intfClsName.equals("java.lang.Runnable")) {
|
||||||
|
result += "Runnable";
|
||||||
|
break outerLoop;
|
||||||
|
} else if (intfClsName.startsWith("java.util.concurrent.")) {
|
||||||
|
// e.g. Callable
|
||||||
|
result += intfClsName.substring(21);
|
||||||
|
break outerLoop;
|
||||||
|
} else if (intfClsName.startsWith("android.view.")) {
|
||||||
|
// e.g. View.OnClickListener
|
||||||
|
result += intfClsName.substring(13);
|
||||||
|
break outerLoop;
|
||||||
|
} else if (intfClsName.startsWith("android.content.")) {
|
||||||
|
// e.g. DialogInterface.OnClickListener
|
||||||
|
result += intfClsName.substring(16);
|
||||||
|
break outerLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentCls.getSuperClass() == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
currentCls = cls.root().resolveClass(currentCls.getSuperClass());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String getAliasFromSourceFile(ClassNode cls) {
|
private String getAliasFromSourceFile(ClassNode cls) {
|
||||||
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
|
SourceFileAttr sourceFileAttr = cls.get(JadxAttrType.SOURCE_FILE);
|
||||||
if (sourceFileAttr == null) {
|
if (sourceFileAttr == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -412,11 +484,11 @@ public class Deobfuscator {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClassNode otherCls = cls.root().searchClassByName(cls.getPackage() + '.' + name);
|
ClassNode otherCls = cls.root().resolveClass(cls.getPackage() + '.' + name);
|
||||||
if (otherCls != null) {
|
if (otherCls != null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
cls.remove(AType.SOURCE_FILE);
|
cls.remove(JadxAttrType.SOURCE_FILE);
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,26 +512,32 @@ public class Deobfuscator {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String getMethodAlias(MethodNode mth) {
|
private String getMethodAlias(MethodNode mth) {
|
||||||
|
if (mth.contains(AFlag.DONT_RENAME)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
MethodInfo methodInfo = mth.getMethodInfo();
|
MethodInfo methodInfo = mth.getMethodInfo();
|
||||||
if (methodInfo.isClassInit() || methodInfo.isConstructor()) {
|
if (methodInfo.isClassInit() || methodInfo.isConstructor()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String alias = mthMap.get(methodInfo);
|
String alias = getAssignedAlias(methodInfo);
|
||||||
if (alias != null) {
|
if (alias != null) {
|
||||||
return alias;
|
return alias;
|
||||||
}
|
}
|
||||||
alias = deobfPresets.getForMth(methodInfo);
|
|
||||||
if (alias != null) {
|
|
||||||
mthMap.put(methodInfo, alias);
|
|
||||||
methodInfo.setAliasFromPreset(true);
|
|
||||||
return alias;
|
|
||||||
}
|
|
||||||
if (shouldRename(mth.getName())) {
|
if (shouldRename(mth.getName())) {
|
||||||
return makeMethodAlias(mth);
|
return makeMethodAlias(mth);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String getAssignedAlias(MethodInfo methodInfo) {
|
||||||
|
String alias = mthMap.get(methodInfo);
|
||||||
|
if (alias != null) {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
return deobfPresets.getForMth(methodInfo);
|
||||||
|
}
|
||||||
|
|
||||||
public String makeFieldAlias(FieldNode field) {
|
public String makeFieldAlias(FieldNode field) {
|
||||||
String alias = String.format("f%d%s", fldIndex++, prepareNamePart(field.getName()));
|
String alias = String.format("f%d%s", fldIndex++, prepareNamePart(field.getName()));
|
||||||
fldMap.put(field.getFieldInfo(), alias);
|
fldMap.put(field.getFieldInfo(), alias);
|
||||||
@@ -467,12 +545,16 @@ public class Deobfuscator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String makeMethodAlias(MethodNode mth) {
|
public String makeMethodAlias(MethodNode mth) {
|
||||||
String alias = String.format("m%d%s", mthIndex++, prepareNamePart(mth.getName()));
|
String prefix;
|
||||||
mthMap.put(mth.getMethodInfo(), alias);
|
if (mth.contains(AType.METHOD_OVERRIDE)) {
|
||||||
return alias;
|
prefix = "mo";
|
||||||
|
} else {
|
||||||
|
prefix = "m";
|
||||||
|
}
|
||||||
|
return String.format("%s%d%s", prefix, mthIndex++, prepareNamePart(mth.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doPkg(PackageNode pkg, String fullName) {
|
private void processPackageFull(PackageNode pkg, String fullName) {
|
||||||
if (pkgSet.contains(fullName)) {
|
if (pkgSet.contains(fullName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -482,15 +564,19 @@ public class Deobfuscator {
|
|||||||
PackageNode parentPkg = pkg.getParentPackage();
|
PackageNode parentPkg = pkg.getParentPackage();
|
||||||
while (!parentPkg.getName().isEmpty()) {
|
while (!parentPkg.getName().isEmpty()) {
|
||||||
if (!parentPkg.hasAlias()) {
|
if (!parentPkg.hasAlias()) {
|
||||||
doPkg(parentPkg, parentPkg.getFullName());
|
processPackageFull(parentPkg, parentPkg.getFullName());
|
||||||
}
|
}
|
||||||
parentPkg = parentPkg.getParentPackage();
|
parentPkg = parentPkg.getParentPackage();
|
||||||
}
|
}
|
||||||
|
|
||||||
String pkgName = pkg.getName();
|
if (!pkg.hasAlias()) {
|
||||||
if (!pkg.hasAlias() && shouldRename(pkgName)) {
|
String pkgName = pkg.getName();
|
||||||
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkgName));
|
if ((args.isDeobfuscationOn() && shouldRename(pkgName))
|
||||||
pkg.setAlias(pkgAlias);
|
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName))
|
||||||
|
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
|
||||||
|
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
|
||||||
|
pkg.setAlias(pkgAlias);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,6 +592,24 @@ public class Deobfuscator {
|
|||||||
return NameMapper.removeInvalidCharsMiddle(name);
|
return NameMapper.removeInvalidCharsMiddle(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String prepareNameFull(String name, String prefix) {
|
||||||
|
if (name.length() > maxLength) {
|
||||||
|
return makeHashName(name, prefix);
|
||||||
|
}
|
||||||
|
String result = NameMapper.removeInvalidChars(name, prefix);
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
return makeHashName(name, prefix);
|
||||||
|
}
|
||||||
|
if (NameMapper.isReserved(result)) {
|
||||||
|
return prefix + result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String makeHashName(String name, String invalidPrefix) {
|
||||||
|
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
|
||||||
|
}
|
||||||
|
|
||||||
private void dumpClassAlias(ClassNode cls) {
|
private void dumpClassAlias(ClassNode cls) {
|
||||||
PackageNode pkg = getPackageNode(cls.getPackage(), false);
|
PackageNode pkg = getPackageNode(cls.getPackage(), false);
|
||||||
|
|
||||||
@@ -519,10 +623,8 @@ public class Deobfuscator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void dumpAlias() {
|
private void dumpAlias() {
|
||||||
for (DexNode dexNode : dexNodes) {
|
for (ClassNode cls : root.getClasses()) {
|
||||||
for (ClassNode cls : dexNode.getClasses()) {
|
dumpClassAlias(cls);
|
||||||
dumpClassAlias(cls);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user